From 45b31ae98ccf3201bfe5410c9171d8fdb166e8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Tue, 6 Oct 2026 09:01:09 -0400 Subject: [PATCH] Abort transactions that would replace the pinned local builds and stage the refresh hook for new users --- README.md | 2 +- .../omarchy-iso/orchestrator/phases_impl.py | 50 ++++++++++++++----- test/unit/test_pin_local_packages.py | 11 +++- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b2ba49a..8ca3e57 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ For local development, build the ISO from sibling checkouts: Despite the local folder name, the first argument is the Omarchy source checkout (runtime commands, configs, setup scripts, themes, shell, migrations). The installer itself lives in this ISO repo. -A local-source install pins `omarchy-dev`, `omarchy-settings-dev` and `rime-ice-installer` with `IgnorePkg` in `/etc/pacman.conf`, since the published builds lack the local changes. A `pre-refresh-pacman` hook in the user's `~/.config/omarchy/hooks` puts the line back after `omarchy refresh pacman`. Remove both to take the published packages again. +A local-source install pins `omarchy-dev`, `omarchy-settings-dev` and `rime-ice-installer`, since the published builds share their version number but lack the local changes. `IgnorePkg` in `/etc/pacman.conf` makes updates skip them, and a `pre-refresh-pacman` hook (in the user's `~/.config/omarchy/hooks` and in `/etc/skel`) puts that line back after `omarchy refresh pacman`. `/etc/pacman.d/hooks/00-omarchy-pin-local-builds.hook` aborts any transaction that would still replace them. Remove all three to take the published packages. Use `--dev` or `--rc` to build against those package channels. Both `--dev` and `--edge` select the dev packages from the edge mirror. 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 9bcef37..6276812 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py @@ -1383,13 +1383,26 @@ PIN_HOOK_SCRIPT = """#!/bin/bash line="{line}" grep -qxF "$line" /etc/pacman.conf || sudo sed -i "/^\\[options\\]/a $line" /etc/pacman.conf """ +PIN_GUARD = """[Trigger] +Operation = Upgrade +Type = Package +{targets} + +[Action] +Description = Keeping the local Omarchy builds... +When = PreTransaction +Exec = /usr/bin/sh -c 'echo "This would replace the local builds of {names}. Put \\"{line}\\" back under [options] in /etc/pacman.conf, or delete /etc/pacman.d/hooks/00-omarchy-pin-local-builds.hook to take the published packages." >&2; exit 1' +AbortOnFail +""" +# Local-source builds share the published version number, so updates would replace them. def pin_local_packages(ctx: InstallContext) -> None: if not PINNED_PACKAGES.exists(): return - line = "IgnorePkg = " + " ".join(PINNED_PACKAGES.read_text().split()) + packages = PINNED_PACKAGES.read_text().split() + line = "IgnorePkg = " + " ".join(packages) info(f"› {line}") pacman_conf = ctx.target / "etc" / "pacman.conf" conf = pacman_conf.read_text() @@ -1398,18 +1411,31 @@ def pin_local_packages(ctx: InstallContext) -> None: raise RuntimeError(f"{pacman_conf} has no [options] section to pin local builds in") pacman_conf.write_text(conf.replace("[options]\n", f"[options]\n{line}\n", 1)) - if ctx.defer_provisioning: - return + guard = ctx.target / "etc/pacman.d/hooks/00-omarchy-pin-local-builds.hook" + guard.parent.mkdir(parents=True, exist_ok=True) + guard.write_text(PIN_GUARD.format( + targets="\n".join(f"Target = {package}" for package in packages), + names=", ".join(packages), + line=line, + )) + + hook_path = ".config/omarchy/hooks/pre-refresh-pacman.d/pin-local-packages" + homes = [ctx.target / "etc/skel"] + if not ctx.defer_provisioning: + homes.append(ctx.target / "home" / ctx.username) + for home in homes: + hook = home / hook_path + hook.parent.mkdir(parents=True, exist_ok=True) + hook.write_text(PIN_HOOK_SCRIPT.format(line=line)) + hook.chmod(0o755) + + if not ctx.defer_provisioning: + subprocess.run( + ["arch-chroot", str(ctx.target), "chown", "-R", f"{ctx.username}:{ctx.username}", + f"/home/{ctx.username}/.config/omarchy/hooks"], + check=True, + ) - hooks = f"/home/{ctx.username}/.config/omarchy/hooks" - hook = ctx.target / hooks.lstrip("/") / "pre-refresh-pacman.d" / "pin-local-packages" - hook.parent.mkdir(parents=True, exist_ok=True) - hook.write_text(PIN_HOOK_SCRIPT.format(line=line)) - hook.chmod(0o755) - subprocess.run( - ["arch-chroot", str(ctx.target), "chown", "-R", f"{ctx.username}:{ctx.username}", hooks], - check=True, - ) def configure_dns_resolver(ctx: InstallContext) -> None: """Put the installed system in systemd-resolved stub mode. diff --git a/test/unit/test_pin_local_packages.py b/test/unit/test_pin_local_packages.py index dabcb1a..4bb6d1f 100644 --- a/test/unit/test_pin_local_packages.py +++ b/test/unit/test_pin_local_packages.py @@ -42,6 +42,12 @@ class PinLocalPackagesTest(unittest.TestCase): hook = root / "home/test/.config/omarchy/hooks/pre-refresh-pacman.d/pin-local-packages" self.assertIn(f'line="{LINE}"', hook.read_text()) self.assertTrue(hook.stat().st_mode & 0o111) + guard = (root / "etc/pacman.d/hooks/00-omarchy-pin-local-builds.hook").read_text() + for package in ("omarchy-dev", "omarchy-settings-dev", "rime-ice-installer"): + self.assertIn(f"Target = {package}\n", guard) + self.assertIn("AbortOnFail", guard) + skel = root / "etc/skel/.config/omarchy/hooks/pre-refresh-pacman.d/pin-local-packages" + self.assertEqual(skel.read_text(), hook.read_text()) run.assert_called_with( ["arch-chroot", str(root), "chown", "-R", "test:test", "/home/test/.config/omarchy/hooks"], check=True, @@ -59,11 +65,13 @@ class PinLocalPackagesTest(unittest.TestCase): subprocess.run(["bash", "-c", script], check=True) self.assertEqual(conf.read_text(), CONF.replace("[options]\n", f"[options]\n{LINE}\n")) - def test_deferred_install_pins_without_a_user_hook(self): + def test_deferred_install_stages_the_hook_for_the_first_owner(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) run = self.run_pin(root, pinned=True, defer=True) self.assertIn(LINE, (root / "etc/pacman.conf").read_text()) + self.assertTrue((root / "etc/pacman.d/hooks/00-omarchy-pin-local-builds.hook").exists()) + self.assertTrue((root / "etc/skel/.config/omarchy/hooks/pre-refresh-pacman.d/pin-local-packages").exists()) self.assertFalse((root / "home").exists()) run.assert_not_called() @@ -72,6 +80,7 @@ class PinLocalPackagesTest(unittest.TestCase): root = Path(tmp) self.run_pin(root, pinned=False) self.assertEqual((root / "etc/pacman.conf").read_text(), CONF) + self.assertFalse((root / "etc/pacman.d").exists()) if __name__ == "__main__":