From 6d6b51622152f84ab70a924b38d0246e70d7acbc Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 21 May 2026 17:52:12 -0400 Subject: [PATCH] orchestrator: always unwind bind mounts on exit Bind mounts set up in run_chroot_finalizer used to be torn down in finish, but finish doesn't run when a phase between the two raises. Move the unmount loop into a dedicated cleanup_bind_mounts function and call it from main()'s finally so it runs on success, failure, and KeyboardInterrupt. --- .../share/omarchy-iso/orchestrator/main.py | 25 +++++++++++-------- .../omarchy-iso/orchestrator/phases_impl.py | 16 +++++++++--- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/main.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/main.py index ca53878..fa33ac8 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/main.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/main.py @@ -66,17 +66,22 @@ def main(argv=None) -> int: info(f"Installing Omarchy for {ctx.username} → {ctx.target}") - try: - run(ctx, build_phases()) - except PhaseError: - error("Installation halted.") - return 1 - except KeyboardInterrupt: - error("Installation interrupted.") - return 130 + from .phases_impl import cleanup_bind_mounts - info("Installation complete.") - return 0 + try: + try: + run(ctx, build_phases()) + except PhaseError: + error("Installation halted.") + return 1 + except KeyboardInterrupt: + error("Installation interrupted.") + return 130 + + info("Installation complete.") + return 0 + finally: + cleanup_bind_mounts(ctx) if __name__ == "__main__": diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py index 0b2ea5e..5b5bc61 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py @@ -359,13 +359,21 @@ def validate_boot(ctx: InstallContext) -> None: # ───────────────────────────────────────────────────────────────────────────── -# finish: unwind bind mounts, prompt for reboot. +# cleanup_bind_mounts: invoked from main()'s finally so bind mounts get +# unwound on success, failure, or interrupt. Idempotent. +# ───────────────────────────────────────────────────────────────────────────── + +def cleanup_bind_mounts(ctx: InstallContext) -> None: + for mount_point in ctx.state.get("bind_mounts", []): + subprocess.run(["umount", mount_point], check=False, capture_output=True) + ctx.state["bind_mounts"] = [] + + +# ───────────────────────────────────────────────────────────────────────────── +# finish: prompt for reboot. Bind mounts are unwound in main()'s finally. # ───────────────────────────────────────────────────────────────────────────── def finish(ctx: InstallContext) -> None: - for mount_point in ctx.state.get("bind_mounts", []): - subprocess.run(["umount", mount_point], check=False, capture_output=True) - info("Installation finished. Reboot when ready.") if confirm("Reboot now?", default=True): os.system("reboot")