-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathBuildmacOSInstallApp.py
More file actions
191 lines (154 loc) · 6.61 KB
/
BuildmacOSInstallApp.py
File metadata and controls
191 lines (154 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/usr/bin/env python3
import os
import sys
import plistlib
import subprocess
import shutil
def run_command(cmd):
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
stdout, stderr = process.communicate()
return stdout, stderr, process.returncode
def mount_dmg(dmg_path, no_browse=False):
args = ["/usr/bin/hdiutil", "attach", dmg_path, "-plist", "-noverify"]
if no_browse:
args.append("-nobrowse")
stdout, stderr, returncode = run_command(args)
if returncode != 0:
raise Exception(f"{os.path.basename(dmg_path)} failed to mount:\n{stderr}")
try:
with open('/tmp/mount_output.plist', 'w') as f:
f.write(stdout)
with open('/tmp/mount_output.plist', 'rb') as f:
plist_data = plistlib.load(f)
mounts = [x["mount-point"] for x in plist_data.get("system-entities", []) if "mount-point" in x]
return mounts
except Exception as e:
raise Exception(f"No mount points returned from {os.path.basename(dmg_path)}: {str(e)}")
def unmount_dmg(mount_point):
if not isinstance(mount_point, list):
mount_point = [mount_point]
unmounted = []
for m in mount_point:
args = ["/usr/bin/hdiutil", "detach", m]
stdout, stderr, returncode = run_command(args)
if returncode != 0:
args.append("-force")
stdout, stderr, returncode = run_command(args)
if returncode != 0:
print(stderr)
continue
unmounted.append(m)
return unmounted
def build_installer(folder_path):
target_files = [
"BaseSystem.dmg",
"BaseSystem.chunklist",
"InstallESDDmg.pkg",
"InstallInfo.plist",
"AppleDiagnostics.dmg",
"AppleDiagnostics.chunklist"
]
# Check if all files exist
for file in target_files:
file_path = os.path.join(folder_path, file)
if not os.path.exists(file_path):
print(f"Missing required file: {file}")
return False
# Process starts here
base_mounts = []
try:
print("Taking ownership of downloaded files...")
for file in target_files:
file_path = os.path.join(folder_path, file)
print(f" - {file}...")
run_command(["chmod", "a+x", file_path])
print("Mounting BaseSystem.dmg...")
base_system_path = os.path.join(folder_path, "BaseSystem.dmg")
base_mounts = mount_dmg(base_system_path)
if not base_mounts:
raise Exception("No mount points were returned from BaseSystem.dmg")
base_mount = base_mounts[0]
print("Locating Installer app...")
install_app = None
for item in os.listdir(base_mount):
item_path = os.path.join(base_mount, item)
if (os.path.isdir(item_path) and
item.lower().endswith(".app") and
not item.startswith(".")):
install_app = item
break
if not install_app:
raise Exception(f"Installer app not located in {base_mount}")
print(f" - Found {install_app}")
# Copy the .app over
source_app = os.path.join(base_mount, install_app)
dest_app = os.path.join(folder_path, install_app)
stdout, stderr, returncode = run_command(["cp", "-R", source_app, dest_app])
if returncode != 0:
raise Exception(f"Copy Failed! {stderr}")
print("Unmounting BaseSystem.dmg...")
for mount in base_mounts:
unmount_dmg(mount)
base_mounts = []
shared_support = os.path.join(dest_app, "Contents", "SharedSupport")
if not os.path.exists(shared_support):
print("Creating SharedSupport directory...")
os.makedirs(shared_support)
print("Copying files to SharedSupport...")
for file in target_files:
source_file = os.path.join(folder_path, file)
# InstallESDDmg.pkg gets renamed to InstallESD.dmg - all others stay the same
dest_file_name = "InstallESD.dmg" if file.lower() == "installesddmg.pkg" else file
dest_file = os.path.join(shared_support, dest_file_name)
rename_msg = f" --> {dest_file_name}" if dest_file_name != file else ""
print(f" - {file}{rename_msg}")
stdout, stderr, returncode = run_command(["cp", "-R", source_file, dest_file])
if returncode != 0:
raise Exception(f"Copy Failed! {stderr}")
print("Patching InstallInfo.plist...")
plist_path = os.path.join(shared_support, "InstallInfo.plist")
with open(plist_path, "rb") as f:
p = plistlib.load(f)
if "Payload Image Info" in p:
pii = p["Payload Image Info"]
if "URL" in pii:
pii["URL"] = pii["URL"].replace("InstallESDDmg.pkg", "InstallESD.dmg")
if "id" in pii:
pii["id"] = pii["id"].replace("com.apple.pkg.InstallESDDmg", "com.apple.dmg.InstallESD")
if "chunklistURL" in pii:
del pii["chunklistURL"]
if "chunklistid" in pii:
del pii["chunklistid"]
with open(plist_path, "wb") as f:
plistlib.dump(p, f)
print(f"\nCreated: {install_app}")
print(f"Saved to: {dest_app}")
return dest_app
except Exception as e:
print(f"An error occurred: {str(e)}")
if base_mounts:
for mount in base_mounts:
print(f" - Unmounting {mount}...")
unmount_dmg(mount)
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 BuildmacOSInstallApp.py /path/to/downloaded/files")
sys.exit(1)
folder_path = sys.argv[1]
if not os.path.isdir(folder_path):
print(f"Error: {folder_path} is not a directory")
sys.exit(1)
output_path = sys.argv[2] if len(sys.argv) > 2 else folder_path
if not os.path.isdir(output_path):
os.makedirs(output_path)
result = build_installer(folder_path)
if result:
app_name = os.path.basename(result)
if output_path != folder_path:
# Move the result to the output path if different
shutil.move(result, os.path.join(output_path, app_name))
print(f"Moved to: {os.path.join(output_path, app_name)}")
sys.exit(0)
else:
sys.exit(1)