@@ -90,6 +90,9 @@ def __init__(self):
9090 self .data = json .load (open ("installer_data.json" ))
9191 self .credentials_validated = False
9292 self .expert = False
93+ self .ipsw = None
94+ self .osins = None
95+ self .osi = None
9396
9497 def input (self ):
9598 self .flush_input ()
@@ -306,11 +309,42 @@ def action_install_into_free(self, avail_free):
306309
307310 self .do_install (os_size )
308311
312+ def action_resume (self , oses ):
313+ choices = {str (i ): f"{ p .desc } \n { str (o )} " for i , (p , o ) in enumerate (oses )}
314+
315+ if len (choices ) > 1 :
316+ print ()
317+ p_question ("Choose an incomplete install to resume:" )
318+ idx = self .choice ("Installed OS" , choices )
319+ else :
320+ idx = list (choices .keys ())[0 ]
321+
322+ self .part , self .osi = oses [int (idx )]
323+
324+ p_progress (f"Resuming installation into { self .part .name } ({ self .part .label } )" )
325+
326+ self .dutil .remount_rw (self .osi .system )
327+
328+ # Unhide the SystemVersion, if hidden
329+ cs = os .path .join (self .osi .system , "System/Library/CoreServices" )
330+ sv_path = os .path .join (cs , "SystemVersion.plist" )
331+ sv_dis_path = os .path .join (cs , "SystemVersion-disabled.plist" )
332+ if not os .path .exists (sv_path ):
333+ if not os .path .exists (sv_dis_path ):
334+ p_error ("Could not find SystemVersion.plist." )
335+ p_error ("Cannot resume this installation." )
336+ return False
337+ os .rename (sv_dis_path , sv_path )
338+
339+ # Go for step2 again
340+ self .step2 ()
341+
309342 def do_install (self , total_size = None ):
310343 p_progress (f"Installing stub macOS into { self .part .name } ({ self .part .label } )" )
311344
312345 self .ins .prepare_volume (self .part )
313346 self .ins .check_volume ()
347+ self .osi = self .ins .osi
314348 self .ins .install_files (self .cur_os )
315349
316350 self .osins .partition_disk (self .part .name , total_size )
@@ -379,7 +413,7 @@ def set_reduced_security(self):
379413 print ()
380414 p_progress ("Preparing the new OS for booting in Reduced Security mode..." )
381415 try :
382- subprocess .run (["bputil" , "-g" , "-v" , self .ins . osi .vgid ,
416+ subprocess .run (["bputil" , "-g" , "-v" , self .osi .vgid ,
383417 "-u" , self .admin_user , "-p" , self .admin_password ], check = True )
384418 break
385419 except subprocess .CalledProcessError :
@@ -396,7 +430,7 @@ def bless(self):
396430 p_progress ("Setting the new OS as the default boot volume..." )
397431 try :
398432 subprocess .run (["bless" , "--setBoot" ,
399- "--device" , "/dev/" + self .ins . osi .sys_volume ,
433+ "--device" , "/dev/" + self .osi .sys_volume ,
400434 "--user" , self .admin_user , "--stdinpass" ],
401435 input = self .admin_password .encode ("utf-8" ),
402436 check = True )
@@ -409,7 +443,7 @@ def bless(self):
409443 p_warning ("Let's try a different way. Sorry, you'll have to type it in again." )
410444 try :
411445 subprocess .run (["bless" , "--setBoot" ,
412- "--device" , "/dev/" + self .ins . osi .sys_volume ,
446+ "--device" , "/dev/" + self .osi .sys_volume ,
413447 "--user" , self .admin_user ], check = True )
414448 print ()
415449 return
@@ -424,7 +458,10 @@ def bless(self):
424458 def step2 (self ):
425459 is_1tr = self .sysinfo .boot_mode == "one true recoveryOS"
426460 is_recovery = "recoveryOS" in self .sysinfo .boot_mode
427- bootpicker_works = split_ver (self .sysinfo .macos_ver ) >= split_ver (self .ipsw .min_macos )
461+ sys_ver = split_ver (self .sysinfo .macos_ver )
462+ bootpicker_works = sys_ver >= (12 , 3 )
463+ if not bootpicker_works and self .ipsw :
464+ bootpicker_works = sys_ver >= split_ver (self .ipsw .min_macos )
428465
429466 if is_1tr and self .is_sfr_recovery and self .ipsw .paired_sfr :
430467 subprocess .run ([self .ins .step2_sh ], check = True )
@@ -454,15 +491,16 @@ def flush_input(self):
454491
455492 def step2_indirect (self ):
456493 # Hide the new volume until step2 is done
457- os .rename (self .ins .systemversion_path ,
458- self .ins .systemversion_path .replace ("SystemVersion.plist" ,
459- "SystemVersion-disabled.plist" ))
494+ cs = os .path .join (self .osi .system , "System/Library/CoreServices" )
495+ sv_path = os .path .join (cs , "SystemVersion.plist" )
496+ sv_dis_path = os .path .join (cs , "SystemVersion-disabled.plist" )
497+ os .rename (sv_path , sv_dis_path )
460498
461499 p_success ( "Installation successful!" )
462500 print ()
463501 p_progress ("Install information:" )
464- p_info ( f" APFS VGID: { col ()} { self .ins . osi .vgid } " )
465- if self .osins .efi_part :
502+ p_info ( f" APFS VGID: { col ()} { self .osi .vgid } " )
503+ if self .osins and self . osins .efi_part :
466504 p_info (f" EFI PARTUUID: { col ()} { self .osins .efi_part .uuid .lower ()} " )
467505 print ()
468506 p_message ( "To be able to boot your new OS, you will need to complete one more step." )
@@ -761,6 +799,7 @@ def main_loop(self):
761799 parts_free = []
762800 parts_empty_apfs = []
763801 parts_resizable = []
802+ oses_incomplete = []
764803
765804 for i , p in enumerate (self .parts ):
766805 if p .type in ("Apple_APFS_ISC" ,):
@@ -822,6 +861,8 @@ def main_loop(self):
822861 else :
823862 state += " "
824863 p_plain (f" OS: [{ state } ] { os } " )
864+ if os .stub and not (os .bp and os .bp .get ("coih" , None )):
865+ oses_incomplete .append ((p , os ))
825866
826867 print ()
827868 p_plain (f" [{ b } ] = Booted OS, [{ r } ] = Booted recovery, [{ u } ] = Unknown" )
@@ -835,6 +876,9 @@ def main_loop(self):
835876 actions = {}
836877
837878 default = None
879+ if oses_incomplete :
880+ actions ["p" ] = "Resume an incomplete installation (reboot step)"
881+ default = default or "p"
838882 if parts_free :
839883 actions ["f" ] = "Install an OS into free space"
840884 default = default or "f"
@@ -866,6 +910,8 @@ def main_loop(self):
866910 elif act == "m" :
867911 p_error ("Unimplemented" )
868912 sys .exit (1 )
913+ elif act == "p" :
914+ return self .action_resume (oses_incomplete )
869915 elif act == "q" :
870916 return False
871917
0 commit comments