From 17aeb00515f4dba4e4871c52523848341f15a780 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Wed, 10 Jun 2026 12:49:09 +0200 Subject: [PATCH 1/3] Revert "bugs: Ban installing on Golden Gate until we figure out new validation requirements" This reverts commit 1df6c53bac6ea74dea3ddaaf7d025393cda4690a. Signed-off-by: Sasha Finkelstein --- src/bugs.py | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/src/bugs.py b/src/bugs.py index 92ca0a9..33f89b3 100644 --- a/src/bugs.py +++ b/src/bugs.py @@ -9,8 +9,6 @@ # Allow the recoveryOS we use ALLOWED_RECOVERYOS_VERSIONS = ("13.5",) -GGATE_VALIDATION_MIN = "27.0" - PROMOTION_DEVICES = { "j314cap", "j314sap", @@ -79,31 +77,7 @@ def request_upgrade(): p_message("macOS Sonoma versions prior to 14.2 on this machine.") print() -def run_checks_ggate_validation(main): - if main.sysinfo.macos_ver and split_ver(main.sysinfo.macos_ver) < split_ver(GGATE_VALIDATION_MIN): - logging.info("bugs: Tahoe or lower") - return - p_error("macOS Golden Gate detected!") - print() - p_warning("Apple has changed how the boot picker and Startup Disk applications detect") - p_warning('"valid" macOS installs. When using either from macOS 27, your Asahi partition') - p_warning("will not be visible! We believe this to be a bug, and have filed a report.") - print() - p_message("If you are running the installer because you have noticed that your Asahi partition") - p_message("has disappeared, do not stress. Your Asahi partition is still there, and you have") - p_message("not lost any data. The Apple Silicon boot picker is a macOS application running") - p_message("in the default boot volume's recovery environment and therefore still works if") - p_message("your default boot disk is macOS 26 and below or Asahi.") - print() - p_message("If you have a secondary installation of macOS 26 or below, set this as your default") - p_message("Startup Disk to restore access to Asahi. If this is your only install, it is possible") - p_message("that the Fallback recoveryOS is still using macOS 26 or older, and in that case you can") - p_message("enter it by turning off the machine, rapidly pressing and releasing the power button, then") - p_message('holding it until the "Loading startup options..." message appears') - print() - exit(1) - -def run_checks_promotion(main): +def run_checks(main): if main.sysinfo.device_class not in PROMOTION_DEVICES: logging.info("bugs: Not a ProMotion device") return @@ -126,7 +100,3 @@ def run_checks_promotion(main): request_upgrade() sys.exit(1) - -def run_checks(main): - run_checks_ggate_validation(main) - run_checks_promotion(main) From 79d2947d03d805d3e0dce030b70019c98df4d865 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Wed, 10 Jun 2026 12:50:17 +0200 Subject: [PATCH 2/3] main: Set VolBootable flag. Boot picker started caring about it in macOS 27. Set it for new installs and add an action to apply it on old ones. Signed-off-by: Sasha Finkelstein --- src/main.py | 31 +++++++++++++++++++++++++++++++ src/osenum.py | 6 +++++- src/stub.py | 2 ++ src/util.py | 19 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 254307a..9a525c5 100644 --- a/src/main.py +++ b/src/main.py @@ -437,6 +437,29 @@ def action_repair_or_upgrade(self, oses, upgrade): # Go for step2 again self.step2() + def action_set_vol_bootable(self, oses): + choices = {str(i): f"{p.desc}\n {str(o)}" for i, (p, o) in enumerate(oses)} + + if len(choices) > 1: + print() + p_question("Choose an install to fix macOS 27 compatibility on:") + idx = self.choice("Installed OS", choices) + else: + idx = list(choices.keys())[0] + + self.part, osi = oses[int(idx)] + + self.dutil.remount_rw(osi.system) + fsctl_set_bootable(osi.system, True) + print() + p_success(f"macOS 27 fix applied. System Settings caches the bootability state,") + p_success(f"so you may need to reboot before the os becomes visible") + p_success(f"Press enter to continue.") + self.input() + print() + + return True + def action_rebuild_vendorfw(self, oses): choices = {str(i): f"{p.desc}\n {str(o)}" for i, (p, o) in enumerate(oses)} @@ -977,6 +1000,7 @@ def main_loop(self): parts_empty_apfs = [] parts_resizable = [] oses_incomplete = [] + oses_non_vol_boot = [] oses_upgradable = [] oses_vendorfw = [] @@ -1055,6 +1079,8 @@ def main_loop(self): p_plain(f" Extra partitions: {' '.join('#' + str(i.index) for i in os.attached_partitions)}") if os.stub and os.m1n1_ver: oses_vendorfw.append((p, os)) + if os.stub and not os.sys_vol_bootable: + oses_non_vol_boot.append((p, os)) if os.stub and os.m1n1_ver and os.m1n1_ver != self.m1n1_ver: oses_upgradable.append((p, os)) elif os.stub and not (os.bp and os.bp.get("coih", None)): @@ -1095,6 +1121,9 @@ def main_loop(self): if oses_vendorfw: actions["v"] = "Rebuild vendor firmware package" default = default or "v" + if oses_non_vol_boot: + actions["7"] = "Fix macOS 27 boot picker compatibility" + default = default or "7" if not actions: p_error("No actions available on this system.") @@ -1125,6 +1154,8 @@ def main_loop(self): return self.action_wipe() elif act == "v": return self.action_rebuild_vendorfw(oses_vendorfw) + elif act == "7": + return self.action_set_vol_bootable(oses_non_vol_boot) elif act == "q": return False diff --git a/src/osenum.py b/src/osenum.py index 3d0b03c..d99a1e1 100644 --- a/src/osenum.py +++ b/src/osenum.py @@ -28,6 +28,7 @@ class OSInfo: paired: bool = False admin_users: object = None attached_partitions: list = None + sys_vol_bootable: bool = False def update_admin_users(self): try: @@ -225,7 +226,10 @@ def collect_os(self, part, volumes, vgid): except FileNotFoundError: logging.info(f" Not Found") continue - + try: + osi.sys_vol_bootable = fsctl_is_bootable(mounts["System"]) + except Exception as e: + logging.warning(f" Failed to get VolBootable: {e}") osi.update_admin_users() try: diff --git a/src/stub.py b/src/stub.py index 6ad4673..26ac692 100644 --- a/src/stub.py +++ b/src/stub.py @@ -287,6 +287,8 @@ def install_files(self, cur_os): except: p_error("Failed to apply extended attributes, logo will not work.") + fsctl_set_bootable(self.osi.system, True) + p_progress("Setting up Data volume...") logging.info("Setting up Data volume") diff --git a/src/util.py b/src/util.py index c89e7a7..c68c57b 100644 --- a/src/util.py +++ b/src/util.py @@ -356,3 +356,22 @@ def extract_tree(self, src, dest): if self.verbose: self.flush_progress() +c_fsctl = None +def fsctl_apfs_bootable(path, cmd): + global c_fsctl + if c_fsctl is None: + sysdll = CDLL("libSystem.B.dylib", use_errno=True) + func = sysdll.fsctl + func.restype = c_int + func.argtypes = (c_char_p, c_ulong, POINTER(c_int), c_uint) + c_fsctl = func + c_cmd = c_int(cmd) + err = c_fsctl(path.encode(), 0xc0044a57, byref(c_cmd), 0) + if err == -1: + raise Exception(f'fsctl call failed, errno: {get_errno()}') + return c_cmd.value + +def fsctl_is_bootable(path): + return fsctl_apfs_bootable(path, 0) == 1 +def fsctl_set_bootable(path, val): + fsctl_apfs_bootable(path, 1 if val else -1) From a0d0752df50ca2263d085009bebc37995e8bf426 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Wed, 10 Jun 2026 12:51:39 +0200 Subject: [PATCH 3/3] main: Add an ability to install in a vm Does not provide a functional install, but simplifies testing some installer features. Signed-off-by: Sasha Finkelstein --- src/main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.py b/src/main.py index 9a525c5..f116e10 100644 --- a/src/main.py +++ b/src/main.py @@ -73,6 +73,12 @@ class Device: "j180dap": Device("13.4", False), # Mac Pro (M2 Ultra, 2023) } +# Asahi Linux does not support running in a virtual machine, this option +# exists only for development of the installer itself. +if os.environ.get("ALLOW_VM", None): + CHIP_MIN_VER[0xfe00] = "12.0" + DEVICES["vma2macosap"] = Device("12.0", False) + IPSW_VERSIONS = [ IPSW("12.3.1", "12.1",