Skip to content

Commit 1638ca4

Browse files
committed
stub: Support (minimally) verifying existing installations
This helps out with partial installs, but also paves the way for recovery actions. Signed-off-by: Hector Martin <[email protected]>
1 parent b8787c7 commit 1638ca4

2 files changed

Lines changed: 73 additions & 30 deletions

File tree

src/main.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ def action_install_into_container(self, avail_parts):
222222
ipsw = self.choose_ipsw(template.get("supported_fw", None))
223223
logging.info(f"Chosen IPSW version: {ipsw.version}")
224224

225-
self.ins = stub.StubInstaller(self.sysinfo, self.dutil, self.osinfo, ipsw)
225+
self.ins = stub.StubInstaller(self.sysinfo, self.dutil, self.osinfo)
226+
self.ins.load_ipsw(ipsw)
226227
self.osins = osinstall.OSInstaller(self.dutil, self.data, template)
227228
self.osins.load_package()
228229

@@ -297,7 +298,8 @@ def action_install_into_free(self, avail_free):
297298

298299
ipsw = self.choose_ipsw(template.get("supported_fw", None))
299300
logging.info(f"Chosen IPSW version: {ipsw.version}")
300-
self.ins = stub.StubInstaller(self.sysinfo, self.dutil, self.osinfo, ipsw)
301+
self.ins = stub.StubInstaller(self.sysinfo, self.dutil, self.osinfo)
302+
self.ins.load_ipsw(ipsw)
301303

302304
p_progress(f"Creating new stub macOS named {label}")
303305
logging.info(f"Creating stub macOS: {label}")
@@ -315,22 +317,22 @@ def action_resume(self, oses):
315317
else:
316318
idx = list(choices.keys())[0]
317319

318-
self.part, self.osi = oses[int(idx)]
320+
self.part, osi = oses[int(idx)]
319321

320322
p_progress(f"Resuming installation into {self.part.name} ({self.part.label})")
321323

324+
self.ins = stub.StubInstaller(self.sysinfo, self.dutil, self.osinfo)
325+
if not self.ins.check_existing_install(osi):
326+
p_error("The existing installation is missing files.")
327+
p_message("This tool can only resume installations that completed the first")
328+
p_message("stage of the installation process. If it was interrupted, please")
329+
p_message("delete the partitions manually and reinstall from scratch.")
330+
return True
331+
322332
self.dutil.remount_rw(self.osi.system)
323333

324334
# Unhide the SystemVersion, if hidden
325-
cs = os.path.join(self.osi.system, "System/Library/CoreServices")
326-
sv_path = os.path.join(cs, "SystemVersion.plist")
327-
sv_dis_path = os.path.join(cs, "SystemVersion-disabled.plist")
328-
if not os.path.exists(sv_path):
329-
if not os.path.exists(sv_dis_path):
330-
p_error("Could not find SystemVersion.plist.")
331-
p_error("Cannot resume this installation.")
332-
return False
333-
os.rename(sv_dis_path, sv_path)
335+
self.ins.prepare_for_bless()
334336

335337
# Go for step2 again
336338
self.step2()
@@ -486,10 +488,7 @@ def flush_input(self):
486488

487489
def step2_indirect(self):
488490
# Hide the new volume until step2 is done
489-
cs = os.path.join(self.osi.system, "System/Library/CoreServices")
490-
sv_path = os.path.join(cs, "SystemVersion.plist")
491-
sv_dis_path = os.path.join(cs, "SystemVersion-disabled.plist")
492-
os.rename(sv_path, sv_dis_path)
491+
self.ins.prepare_for_step2()
493492

494493
p_success( "Installation successful!")
495494
print()

src/stub.py

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@
88
from util import *
99

1010
class StubInstaller(PackageInstaller):
11-
def __init__(self, sysinfo, dutil, osinfo, ipsw_info):
11+
def __init__(self, sysinfo, dutil, osinfo):
1212
super().__init__()
1313
self.dutil = dutil
1414
self.sysinfo = sysinfo
1515
self.osinfo = osinfo
16-
self.install_version = ipsw_info.version.split(maxsplit=1)[0]
1716
self.ucache = None
1817
self.copy_idata = []
1918
self.stub_info = {}
19+
self.ipsw = None
20+
self.pkg = None
21+
22+
def load_ipsw(self, ipsw):
23+
self.install_version = ipsw_info.version.split(maxsplit=1)[0]
2024

2125
base = os.environ.get("IPSW_BASE", None)
2226
url = ipsw_info.url
@@ -102,6 +106,45 @@ def chflags(self, flags, path):
102106
logging.info(f"chflags {flags} {path}")
103107
subprocess.run(["chflags", flags, path], check=True)
104108

109+
def get_paths(self):
110+
self.resources = os.path.join(self.osi.system, "Finish Installation.app/Contents/Resources")
111+
self.step2_sh = os.path.join(self.resources, "step2.sh")
112+
self.boot_obj_path = os.path.join(self.resources, "boot.bin")
113+
self.iapm_path = os.path.join(self.osi.system, ".IAPhysicalMedia")
114+
self.iapm_dis_path = os.path.join(self.osi.system, "IAPhysicalMedia-disabled.plist")
115+
self.core_services = os.path.join(self.osi.system, "System/Library/CoreServices")
116+
self.sv_path = os.path.join(self.core_services, "SystemVersion.plist")
117+
self.sv_dis_path = os.path.join(self.core_services, "SystemVersion-disabled.plist")
118+
119+
def check_existing_install(self, osi):
120+
self.osi = osi
121+
self.get_paths()
122+
123+
if not os.path.exists(self.step2_sh):
124+
logging.error("step2.sh is missing")
125+
return False
126+
if not os.path.exists(self.boot_obj_path):
127+
logging.error("boot.bin is missing")
128+
return False
129+
if not any(os.path.exists(i) for i in (self.iapm_path, self.iapm_dis_path)):
130+
logging.error(".IAPhysicalMedia is missing")
131+
return False
132+
if not any(os.path.exists(i) for i in (self.sv_path, self.sv_dis_path)):
133+
logging.error("SystemVersion is missing")
134+
return False
135+
136+
return True
137+
138+
def prepare_for_bless(self):
139+
if not os.path.exists(self.sv_path):
140+
os.replace(self.sv_dis_path, self.sv_path)
141+
142+
def prepare_for_step2(self):
143+
if os.path.exists(self.sv_path):
144+
os.replace(self.sv_path, self.sv_dis_path)
145+
if not os.path.exists(self.iapm_path):
146+
os.replace(self.iapm_dis_path, self.iapm_path)
147+
105148
def install_files(self, cur_os):
106149
logging.info("StubInstaller.install_files()")
107150
logging.info(f"VGID: {self.osi.vgid}")
@@ -110,6 +153,8 @@ def install_files(self, cur_os):
110153
p_progress("Beginning stub OS install...")
111154
ipsw = self.pkg
112155

156+
self.get_paths()
157+
113158
logging.info("Parsing metadata...")
114159

115160
sysver = plistlib.load(ipsw.open("SystemVersion.plist"))
@@ -258,32 +303,31 @@ def install_files(self, cur_os):
258303
os.path.join(basesystem_path, "arm64eBaseSystem.dmg"))
259304
self.flush_progress()
260305

261-
self.systemversion_path = os.path.join(cs, "SystemVersion.plist")
262-
263306
p_progress("Wrapping up...")
264307

265308
logging.info("Writing SystemVersion.plist")
266-
with open(self.systemversion_path, "wb") as fd:
309+
with open(self.sv_path, "wb") as fd:
267310
plistlib.dump(sysver, fd)
268-
self.copy_idata.append((self.systemversion_path, "SystemVersion.plist"))
311+
self.copy_idata.append((self.sv_path, "SystemVersion.plist"))
312+
313+
if os.path.exists(self.sv_dis_path):
314+
os.remove(self.sv_dis_path)
269315

270316
logging.info("Copying Finish Installation.app")
271317
shutil.copytree("step2/Finish Installation.app",
272318
os.path.join(self.osi.system, "Finish Installation.app"))
273319

274320
logging.info("Writing step2.sh")
275321
step2_sh = open("step2/step2.sh").read().replace("##VGID##", self.osi.vgid)
276-
resources = os.path.join(self.osi.system, "Finish Installation.app/Contents/Resources")
277-
step2_sh_dst = os.path.join(resources, "step2.sh")
278-
with open(step2_sh_dst, "w") as fd:
322+
with open(self.step2_sh, "w") as fd:
279323
fd.write(step2_sh)
280-
os.chmod(step2_sh_dst, 0o755)
281-
self.step2_sh = step2_sh_dst
282-
self.boot_obj_path = os.path.join(resources, "boot.bin")
324+
os.chmod(self.step2_sh, 0o755)
283325

284326
logging.info("Copying .IAPhysicalMedia")
285-
shutil.copy("step2/IAPhysicalMedia.plist",
286-
os.path.join(self.osi.system, ".IAPhysicalMedia"))
327+
shutil.copy("step2/IAPhysicalMedia.plist", self.iapm_path)
328+
329+
if os.path.exists(self.iapm_dis_path):
330+
os.remove(self.iapm_dis_path)
287331

288332
print()
289333
p_success("Stub OS installation complete.")

0 commit comments

Comments
 (0)