Skip to content

Commit 70ab98e

Browse files
committed
wip
1 parent 893eccc commit 70ab98e

File tree

3 files changed

+282
-5
lines changed

3 files changed

+282
-5
lines changed

.github/workflows/release.yaml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: Build and Release JamfMigrationTool
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
build:
10+
name: Build JamfMigrationTool
11+
runs-on: macos-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Xcode
18+
uses: maxim-lobanov/setup-xcode@v1
19+
with:
20+
xcode-version: latest-stable
21+
22+
- name: Import developer id installer certificate
23+
uses: apple-actions/import-codesign-certs@v1
24+
with:
25+
p12-file-base64: ${{ secrets.INSTALLER_CERTIFICATE_P12 }}
26+
p12-password: ${{ secrets.CERTIFICATE_PASSWORD }}
27+
keychain: build
28+
keychain-password: "temp-password"
29+
30+
- name: Import developer id application certificate
31+
uses: apple-actions/import-codesign-certs@v1
32+
with:
33+
p12-file-base64: ${{ secrets.CERTIFICATE_P12 }}
34+
p12-password: ${{ secrets.CERTIFICATE_PASSWORD }}
35+
create-keychain: false
36+
keychain: build
37+
keychain-password: "temp-password"
38+
39+
- name: Build the project
40+
run: |
41+
xcodebuild -scheme JamfMigrationTool -configuration Release clean build CODE_SIGN_IDENTITY='Developer ID Application' CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO OTHER_CODE_SIGN_FLAGS='--timestamp' -derivedDataPath build
42+
43+
- name: Create .pkg installer
44+
run: |
45+
rm -rf package-root
46+
mkdir -p package-root/usr/local/bin
47+
cp build/Build/Products/Release/JamfMigrationTool package-root/usr/local/bin/
48+
pkgbuild \
49+
--root "package-root" \
50+
--install-location "/" \
51+
--identifier "tech.rocketman.jamfmigrationtool" \
52+
--version "${{ github.ref_name }}" \
53+
--sign "${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }}" \
54+
"JamfMigrationTool-${{ github.ref_name }}.pkg"
55+
56+
- name: Verify Signature
57+
run: |
58+
pkgutil --check-signature "JamfMigrationTool-${{ github.ref_name }}.pkg"
59+
60+
- name: Notarize .pkg installer
61+
run: |
62+
xcrun notarytool store-credentials "notarytool-profile" \
63+
--apple-id "${{ secrets.APPLE_ID }}" \
64+
--password "${{ secrets.APPLE_APP_PASSWORD }}" \
65+
--team-id "${{ secrets.TEAM_ID }}"
66+
67+
output=$(xcrun notarytool submit "JamfMigrationTool-${{ github.ref_name }}.pkg" --keychain-profile "notarytool-profile" --wait)
68+
echo $output
69+
70+
extracted_id=$(echo "$output" | awk '/status: Invalid/ {getline; if ($1 == "id:") print $2}')
71+
72+
if [ -n "$extracted_id" ]; then
73+
echo "ID: $extracted_id"
74+
xcrun notarytool log $extracted_id --keychain-profile "notarytool-profile"
75+
exit 1
76+
else
77+
# Staple the notarization
78+
xcrun stapler staple "JamfMigrationTool-${{ github.ref_name }}.pkg"
79+
fi
80+
81+
- name: Upload artifacts
82+
uses: actions/upload-artifact@v4
83+
with:
84+
name: JamfMigrationTool
85+
path: |
86+
build/Build/Products/Release/JamfMigrationTool
87+
JamfMigrationTool-${{ github.ref_name }}.pkg
88+
89+
release:
90+
name: Create GitHub Release
91+
needs: build
92+
runs-on: ubuntu-latest
93+
94+
steps:
95+
- name: Checkout code
96+
uses: actions/checkout@v4
97+
98+
- name: Download artifacts
99+
uses: actions/download-artifact@v4
100+
with:
101+
name: JamfMigrationTool
102+
103+
- name: Create GitHub Release
104+
uses: softprops/action-gh-release@v1
105+
with:
106+
files: |
107+
JamfMigrationTool
108+
JamfMigrationTool-${{ github.ref_name }}.pkg
109+
env:
110+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

JamfMigrationTool.xcodeproj/project.pbxproj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,21 +248,29 @@
248248
91356BD62DCE660300F9415E /* Debug */ = {
249249
isa = XCBuildConfiguration;
250250
buildSettings = {
251-
CODE_SIGN_STYLE = Automatic;
252-
DEVELOPMENT_TEAM = 538U58ZTWN;
251+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Installer";
252+
CODE_SIGN_STYLE = Manual;
253+
DEVELOPMENT_TEAM = "";
254+
"DEVELOPMENT_TEAM[sdk=macosx*]" = 538U58ZTWN;
253255
ENABLE_HARDENED_RUNTIME = YES;
256+
PRODUCT_BUNDLE_IDENTIFIER = tech.rocketman.jamfmigrationtool;
254257
PRODUCT_NAME = "$(TARGET_NAME)";
258+
PROVISIONING_PROFILE_SPECIFIER = "";
255259
SWIFT_VERSION = 5.0;
256260
};
257261
name = Debug;
258262
};
259263
91356BD72DCE660300F9415E /* Release */ = {
260264
isa = XCBuildConfiguration;
261265
buildSettings = {
262-
CODE_SIGN_STYLE = Automatic;
263-
DEVELOPMENT_TEAM = 538U58ZTWN;
266+
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Installer";
267+
CODE_SIGN_STYLE = Manual;
268+
DEVELOPMENT_TEAM = "";
269+
"DEVELOPMENT_TEAM[sdk=macosx*]" = 538U58ZTWN;
264270
ENABLE_HARDENED_RUNTIME = YES;
271+
PRODUCT_BUNDLE_IDENTIFIER = tech.rocketman.jamfmigrationtool;
265272
PRODUCT_NAME = "$(TARGET_NAME)";
273+
PROVISIONING_PROFILE_SPECIFIER = "";
266274
SWIFT_VERSION = 5.0;
267275
};
268276
name = Release;

JamfMigrationTool/main.swift

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,164 @@
77

88
import Foundation
99

10-
print("Hello, World!")
10+
// Constants
11+
let abeProfileIdentifier = "com.apple.profile.mdm"
12+
let logoPath = "/Library/Application Support/Rocketman/funcfi-logo-small.png"
1113

14+
// Check if ABE profile exists
15+
func checkProfileExists(identifier: String) -> Bool {
16+
let process = Process()
17+
let pipe = Pipe()
18+
19+
process.executableURL = URL(fileURLWithPath: "/usr/bin/profiles")
20+
process.arguments = ["list"]
21+
process.standardOutput = pipe
22+
23+
do {
24+
try process.run()
25+
process.waitUntilExit()
26+
27+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
28+
if let output = String(data: data, encoding: .utf8) {
29+
return output.contains(identifier)
30+
}
31+
} catch {
32+
print("Error checking profiles: \(error)")
33+
}
34+
35+
return false
36+
}
37+
38+
// Try to remove ABE profile
39+
func removeProfile(identifier: String) -> Bool {
40+
let process = Process()
41+
42+
process.executableURL = URL(fileURLWithPath: "/usr/bin/profiles")
43+
process.arguments = ["remove", "-identifier", identifier]
44+
45+
do {
46+
try process.run()
47+
process.waitUntilExit()
48+
49+
// Wait a moment for the system to update
50+
sleep(2)
51+
52+
// Check if profile was successfully removed
53+
return !checkProfileExists(identifier: identifier)
54+
} catch {
55+
print("Error removing profile: \(error)")
56+
return false
57+
}
58+
}
59+
60+
// Show dialog to user
61+
func showDialog(title: String, message: String, buttonText: String, iconPath: String) -> Bool {
62+
let process = Process()
63+
let dialogPath = "/usr/local/bin/dialog"
64+
65+
// Check if dialog exists
66+
if !FileManager.default.fileExists(atPath: dialogPath) {
67+
print("SwiftDialog not found at \(dialogPath)")
68+
return false
69+
}
70+
71+
process.executableURL = URL(fileURLWithPath: dialogPath)
72+
process.arguments = [
73+
"--title", title,
74+
"--message", message,
75+
"--icon", iconPath,
76+
"--button1text", buttonText,
77+
]
78+
79+
do {
80+
try process.run()
81+
process.waitUntilExit()
82+
return process.terminationStatus == 0
83+
} catch {
84+
print("Error showing dialog: \(error)")
85+
return false
86+
}
87+
}
88+
89+
// Start Jamf enrollment
90+
func startJamfEnrollment() {
91+
let process = Process()
92+
93+
process.executableURL = URL(fileURLWithPath: "/usr/bin/profiles")
94+
process.arguments = ["renew", "-type", "enrollment"]
95+
96+
do {
97+
try process.run()
98+
process.waitUntilExit()
99+
} catch {
100+
print("Error starting Jamf enrollment: \(error)")
101+
}
102+
}
103+
104+
// Check if running with admin privileges
105+
func isRunningAsAdmin() -> Bool {
106+
let process = Process()
107+
let pipe = Pipe()
108+
109+
process.executableURL = URL(fileURLWithPath: "/usr/bin/id")
110+
process.arguments = ["-u"]
111+
process.standardOutput = pipe
112+
113+
do {
114+
try process.run()
115+
process.waitUntilExit()
116+
117+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
118+
if let output = String(data: data, encoding: .utf8)?.trimmingCharacters(
119+
in: .whitespacesAndNewlines),
120+
let uid = Int(output)
121+
{
122+
return uid == 0
123+
}
124+
} catch {
125+
print("Error checking privileges: \(error)")
126+
}
127+
128+
return false
129+
}
130+
131+
// Main function
132+
func main() {
133+
print("Starting migration from Apple Business Essentials to Jamf Pro...")
134+
135+
// Check for admin privileges
136+
guard isRunningAsAdmin() else {
137+
print("Error: This tool must be run with administrator privileges")
138+
exit(1)
139+
}
140+
141+
// Try to remove ABE profile
142+
let removed = removeProfile(identifier: abeProfileIdentifier)
143+
144+
if removed {
145+
print("ABE profile successfully removed.")
146+
147+
// Show dialog to user
148+
let dialogShown = showDialog(
149+
title: "Migrating to Jamf Pro",
150+
message: "Your Mac needs to be migrated to Jamf Pro. Please press Continue when ready.",
151+
buttonText: "Continue",
152+
iconPath: logoPath
153+
)
154+
155+
if dialogShown {
156+
// Start Jamf enrollment
157+
print("Starting Jamf enrollment...")
158+
startJamfEnrollment()
159+
} else {
160+
print("Failed to show dialog. Please enroll in Jamf Pro manually.")
161+
}
162+
} else {
163+
print(
164+
"ABE profile could not be removed directly."
165+
)
166+
}
167+
}
168+
169+
// Run the main function
170+
main()

0 commit comments

Comments
 (0)