From 4a1667203a67ae2fefd40049048db084a5fd5ed9 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 10 Oct 2026 11:14:53 +1000 Subject: [PATCH 1/7] Take the runtime's omarchy-aarch64-apple.packages as the Apple list omacom/omarchy e1b0e5e9b names the list for the platform. The builder, the candidate-set check and inspection read it first, then 5397950a2's omarchy-apple-silicon.packages, then the oldest omarchy-apple.packages. --- image-builder/bin/build-mac-image | 8 +++++--- image-builder/builder/candidate_set.py | 11 ++++++++--- image-builder/builder/inspection.py | 8 ++++---- image-builder/test/build-mac-image-test.sh | 23 ++++++++++++++-------- image-builder/test/test_candidate_set.py | 16 +++++++++++++-- image-builder/test/test_inspection.py | 16 +++++++++++++++ 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/image-builder/bin/build-mac-image b/image-builder/bin/build-mac-image index 9e3332ea..7c7cd3ff 100755 --- a/image-builder/bin/build-mac-image +++ b/image-builder/bin/build-mac-image @@ -643,19 +643,21 @@ runtime_list() { } # The runtime's Apple list, read once before anything installs: -# omarchy-apple-silicon.packages, else an older runtime's +# omarchy-aarch64-apple.packages, named for the platform, else an older +# runtime's omarchy-apple-silicon.packages, else an even older one's # omarchy-apple.packages. A link is passed over: bsdtar reads it as empty, and # the list it names is a member of its own. read_apple_list() { local archive name list="" text archive=$candidates/$(candidate_archive omarchy) - for name in omarchy-apple-silicon omarchy-apple; do + for name in omarchy-aarch64-apple omarchy-apple-silicon omarchy-apple; do if [[ $(bsdtar -tvf "$archive" "usr/share/omarchy/install/$name.packages" 2>/dev/null) == -* ]]; then list=usr/share/omarchy/install/$name.packages break fi done - [[ -n $list ]] || fail "the runtime ships no Apple package list (omarchy-apple-silicon.packages or omarchy-apple.packages)" + [[ -n $list ]] || + fail "the runtime ships no Apple package list (omarchy-aarch64-apple.packages, omarchy-apple-silicon.packages or omarchy-apple.packages)" text=$(bsdtar -xOf "$archive" "$list") || fail "could not read the runtime's ${list##*/}" mapfile -t apple_names < <(awk 'NF && $1 !~ /^#/ { print $1 }' <<<"$text") ((${#apple_names[@]})) || fail "the runtime's ${list##*/} names no package" diff --git a/image-builder/builder/candidate_set.py b/image-builder/builder/candidate_set.py index b5b5aa95..119c8825 100755 --- a/image-builder/builder/candidate_set.py +++ b/image-builder/builder/candidate_set.py @@ -44,9 +44,14 @@ HEX40 = re.compile(r'[0-9a-f]{40}') HEX64 = re.compile(r'[0-9a-f]{64}') ARCHIVE = re.compile(r'[A-Za-z0-9@._+:-]+\.pkg\.tar\.(xz|zst)') -# The runtime's Apple package list, by upstream's name, then an older runtime's. -APPLE_LISTS = ('usr/share/omarchy/install/omarchy-apple-silicon.packages', +# The runtime's Apple package list: omarchy-aarch64-apple.packages, named for +# the platform (omacom/omarchy e1b0e5e9b), else an older runtime's +# omarchy-apple-silicon.packages, else an even older one's omarchy-apple.packages. +APPLE_LISTS = ('usr/share/omarchy/install/omarchy-aarch64-apple.packages', + 'usr/share/omarchy/install/omarchy-apple-silicon.packages', 'usr/share/omarchy/install/omarchy-apple.packages') +NO_APPLE_LIST = ('ships no Apple package list (omarchy-aarch64-apple.packages, ' + 'omarchy-apple-silicon.packages or omarchy-apple.packages)') # Where each runtime package records the commit it was built from. REVISION_FILES = { 'omarchy': 'usr/share/doc/omarchy/source-revision', @@ -183,7 +188,7 @@ def apple_list(package): entry = subprocess.run(['bsdtar', '-tvf', str(package), name], capture_output=True, text=True) if entry.returncode == 0 and entry.stdout.startswith('-'): return name, member(package, name).decode() - raise ValueError('the runtime ships no Apple package list (omarchy-apple-silicon.packages or omarchy-apple.packages)') + raise ValueError('the runtime ' + NO_APPLE_LIST) def payload_paths(package): diff --git a/image-builder/builder/inspection.py b/image-builder/builder/inspection.py index 4fd44201..063f5f2d 100644 --- a/image-builder/builder/inspection.py +++ b/image-builder/builder/inspection.py @@ -158,12 +158,12 @@ def check_runtime_sources(root: Path, candidates: Candidates, report: dict) -> s def check_apple_packages(root: Path, report: dict) -> str: """Every package the image's Apple list names is installed, reading the list - build-mac-image took: omarchy-apple-silicon.packages, else an older - runtime's omarchy-apple.packages, never a link.""" + build-mac-image took: omarchy-aarch64-apple.packages, else an older + runtime's omarchy-apple-silicon.packages or omarchy-apple.packages, never a + link.""" path = next((root / name for name in candidate_set.APPLE_LISTS if (root / name).is_file() and not (root / name).is_symlink()), None) - require(path is not None, - "the image ships no Apple package list (omarchy-apple-silicon.packages or omarchy-apple.packages)") + require(path is not None, "the image " + candidate_set.NO_APPLE_LIST) names = [fields[0] for fields in map(str.split, path.read_text().splitlines()) if fields and not fields[0].startswith("#")] require(bool(names), f"{path.name} names no package") diff --git a/image-builder/test/build-mac-image-test.sh b/image-builder/test/build-mac-image-test.sh index 6b1d0323..e1a25216 100755 --- a/image-builder/test/build-mac-image-test.sh +++ b/image-builder/test/build-mac-image-test.sh @@ -91,10 +91,11 @@ printf '# aarch64\nzram-generator\n' >"$scratch/runtime/usr/share/omarchy/instal fail "the aarch64 additions follow the base list" pass "the runtime's base list, then its aarch64 additions, as omarchy-pkg-defaults composes them" -# The Apple list by upstream's name, else an older runtime's; never a link, which bsdtar reads as empty. +# The Apple list by the platform's name (omacom/omarchy e1b0e5e9b), else an older runtime's; never a +# link, which bsdtar reads as empty. install_dir=$scratch/runtime/usr/share/omarchy/install apple_layout() { - rm -f "$install_dir"/omarchy-apple*.packages + rm -f "$install_dir"/omarchy-apple*.packages "$install_dir"/omarchy-aarch64-apple.packages while (($#)); do if [[ $2 == @* ]]; then ln -s "${2#@}" "$install_dir/$1" @@ -108,17 +109,23 @@ apple_layout() { chosen_apple() { (fail() { builder_fail "$@"; }; read_apple_list && echo "${apple_names[*]}") } +apple_layout omarchy-aarch64-apple.packages 'omarchy-mac omarchy-mac-boot' +[[ $(chosen_apple) == "omarchy-mac omarchy-mac-boot" ]] || fail "an e1b0e5e9b runtime's omarchy-aarch64-apple.packages is the Apple list" apple_layout omarchy-apple-silicon.packages 'omarchy-mac wf-recorder' -[[ $(chosen_apple) == "omarchy-mac wf-recorder" ]] || fail "an upstream runtime's omarchy-apple-silicon.packages is the Apple list" +[[ $(chosen_apple) == "omarchy-mac wf-recorder" ]] || fail "a 5397950a2 runtime's omarchy-apple-silicon.packages is the Apple list" apple_layout omarchy-apple.packages omarchy-mac [[ $(chosen_apple) == omarchy-mac ]] || fail "an older runtime's omarchy-apple.packages is the Apple list" +apple_layout omarchy-aarch64-apple.packages 'omarchy-mac omarchy-mac-boot' omarchy-apple-silicon.packages 'omarchy-mac wf-recorder' +[[ $(chosen_apple) == "omarchy-mac omarchy-mac-boot" ]] || fail "the platform's name wins over apple-silicon" +apple_layout omarchy-aarch64-apple.packages @omarchy-apple-silicon.packages omarchy-apple-silicon.packages 'omarchy-mac wf-recorder' +[[ $(chosen_apple) == "omarchy-mac wf-recorder" ]] || fail "a link by the platform's name is passed over for the list it names" apple_layout omarchy-apple-silicon.packages 'omarchy-mac wf-recorder' omarchy-apple.packages omarchy-mac -[[ $(chosen_apple) == "omarchy-mac wf-recorder" ]] || fail "upstream's name wins when a runtime ships both" +[[ $(chosen_apple) == "omarchy-mac wf-recorder" ]] || fail "apple-silicon wins over the oldest name when a runtime ships both" apple_layout omarchy-apple-silicon.packages 'omarchy-mac wf-recorder' omarchy-apple.packages @omarchy-apple-silicon.packages [[ $(chosen_apple) == "omarchy-mac wf-recorder" ]] || fail "a compatibility link beside the list changes nothing" apple_layout omarchy-apple.packages omarchy-mac omarchy-apple-silicon.packages @omarchy-apple.packages [[ $(chosen_apple) == omarchy-mac ]] || fail "a link by upstream's name is passed over for the list it names" -pass "the Apple list is omarchy-apple-silicon.packages, else an older runtime's omarchy-apple.packages, never a link" +pass "the Apple list is omarchy-aarch64-apple.packages, else omarchy-apple-silicon.packages, else omarchy-apple.packages, never a link" refused_apple() { local output if output=$(chosen_apple 2>&1); then @@ -127,10 +134,10 @@ refused_apple() { [[ $output == "build-mac-image: $1" ]] || fail "$2 is refused with: $1 (got: $output)" } apple_layout -refused_apple "the runtime ships no Apple package list (omarchy-apple-silicon.packages or omarchy-apple.packages)" \ +refused_apple "the runtime ships no Apple package list (omarchy-aarch64-apple.packages, omarchy-apple-silicon.packages or omarchy-apple.packages)" \ "a runtime with no Apple list" -apple_layout omarchy-apple.packages @omarchy-apple-silicon.packages -refused_apple "the runtime ships no Apple package list (omarchy-apple-silicon.packages or omarchy-apple.packages)" \ +apple_layout omarchy-aarch64-apple.packages @omarchy-apple.packages omarchy-apple.packages @omarchy-apple-silicon.packages +refused_apple "the runtime ships no Apple package list (omarchy-aarch64-apple.packages, omarchy-apple-silicon.packages or omarchy-apple.packages)" \ "a runtime whose only Apple list is a link" apple_layout omarchy-apple-silicon.packages '' refused_apple "the runtime's omarchy-apple-silicon.packages names no package" "a runtime whose Apple list names nothing" diff --git a/image-builder/test/test_candidate_set.py b/image-builder/test/test_candidate_set.py index c63f42e4..4246ea87 100755 --- a/image-builder/test/test_candidate_set.py +++ b/image-builder/test/test_candidate_set.py @@ -294,8 +294,11 @@ def apple_layout(self, directory, lists): def test_apple_package_list_by_either_name(self): apple = b"# Apple\nomarchy-mac\nomarchy-mac-boot\n" layouts = { - "upstream": {"omarchy-apple-silicon.packages": apple}, + "e1b0e5e9b": {"omarchy-aarch64-apple.packages": apple}, + "5397950a2": {"omarchy-apple-silicon.packages": apple}, "older": {"omarchy-apple.packages": apple}, + "platform-link": {"omarchy-apple-silicon.packages": apple, + "omarchy-aarch64-apple.packages": "omarchy-apple-silicon.packages"}, "compatibility-link": {"omarchy-apple-silicon.packages": apple, "omarchy-apple.packages": "omarchy-apple-silicon.packages"}, "reverse-link": {"omarchy-apple.packages": apple, @@ -314,9 +317,18 @@ def test_apple_package_list_prefers_upstreams_name(self): "omarchy-apple.packages": b"omarchy-mac\nomarchy-mac-boot\n"}) with self.assertRaisesRegex(ValueError, "lacks the add-on or boot package"): self.verify(self.work / "both-stale", receipt) + # The platform's name (omacom/omarchy e1b0e5e9b) wins over apple-silicon's. + receipt = self.apple_layout("platform", {"omarchy-aarch64-apple.packages": b"omarchy-mac\nomarchy-mac-boot\n", + "omarchy-apple-silicon.packages": b"omarchy-mac\n"}) + self.verify(self.work / "platform", receipt) + receipt = self.apple_layout("platform-stale", {"omarchy-aarch64-apple.packages": b"omarchy-mac\n", + "omarchy-apple-silicon.packages": b"omarchy-mac\nomarchy-mac-boot\n"}) + with self.assertRaisesRegex(ValueError, "lacks the add-on or boot package"): + self.verify(self.work / "platform-stale", receipt) def test_apple_package_list_missing(self): - for directory, lists in (("none", {}), ("link-only", {"omarchy-apple.packages": "omarchy-apple-silicon.packages"})): + for directory, lists in (("none", {}), ("link-only", {"omarchy-apple.packages": "omarchy-apple-silicon.packages"}), + ("platform-link-only", {"omarchy-aarch64-apple.packages": "omarchy-apple.packages"})): with self.subTest(layout=directory): receipt = self.apple_layout(directory, lists) with self.assertRaisesRegex(ValueError, "the runtime ships no Apple package list"): diff --git a/image-builder/test/test_inspection.py b/image-builder/test/test_inspection.py index dc887e77..07d48b00 100755 --- a/image-builder/test/test_inspection.py +++ b/image-builder/test/test_inspection.py @@ -305,6 +305,15 @@ def test_apple_package_list_by_either_name(self): self.assertEqual(report["checks"]["apple-packages"]["result"], "passed", report["checks"]["apple-packages"]) self.assertEqual(report["apple_package_list"], "omarchy-apple.packages") self.assertIn("omarchy-apple.packages", report["checks"]["apple-packages"]["detail"]) + # The platform's name (omacom/omarchy e1b0e5e9b) alone. + (install / "omarchy-apple.packages").rename(install / "omarchy-aarch64-apple.packages") + report = self.inspect() + self.assertEqual(report["checks"]["apple-packages"]["result"], "passed", report["checks"]["apple-packages"]) + self.assertEqual(report["apple_package_list"], "omarchy-aarch64-apple.packages") + # A link by the platform's name is passed over for the list it names. + (install / "omarchy-aarch64-apple.packages").rename(install / "omarchy-apple-silicon.packages") + (install / "omarchy-aarch64-apple.packages").symlink_to("omarchy-apple-silicon.packages") + self.assertEqual(self.inspect()["apple_package_list"], "omarchy-apple-silicon.packages") def test_apple_package_list_prefers_upstreams_name(self): install = self.root / "usr/share/omarchy/install" @@ -313,6 +322,13 @@ def test_apple_package_list_prefers_upstreams_name(self): self.assertEqual(report["checks"]["apple-packages"]["result"], "passed", report["checks"]["apple-packages"]) (install / "omarchy-apple-silicon.packages").write_text("# Apple\nomarchy-mac\n # indented\nwf-recorder\n") self.assertFails("apple-packages", "omarchy-apple-silicon.packages names packages that are not installed: wf-recorder$") + # The platform's name wins over apple-silicon's. + (install / "omarchy-aarch64-apple.packages").write_text("omarchy-mac\n") + report = self.inspect() + self.assertEqual(report["checks"]["apple-packages"]["result"], "passed", report["checks"]["apple-packages"]) + self.assertEqual(report["apple_package_list"], "omarchy-aarch64-apple.packages") + (install / "omarchy-aarch64-apple.packages").write_text("omarchy-mac\nnot-installed\n") + self.assertFails("apple-packages", "omarchy-aarch64-apple.packages names packages that are not installed: not-installed$") def test_apple_package_list_missing(self): install = self.root / "usr/share/omarchy/install" From 068e00a88d638323b84d1ffe9159a4d451c5dca1 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 10 Oct 2026 11:15:11 +1000 Subject: [PATCH 2/7] Install the runtime's aarch64-apple pacman templates e1b0e5e9b moved the Apple Silicon templates to default/pacman/aarch64-apple, with a mirror list of its own (the HTTPS Arch Linux ARM mirror). The builder fell back to the plain aarch64 template, without asahi-alarm. It now takes aarch64-apple and the mirror list beside it, then omarchy-mac's, apple-silicon or aarch64 as before; inspection checks the same choice. --- image-builder/bin/build-mac-image | 17 +++++---- image-builder/builder/inspection.py | 22 +++++++----- image-builder/test/build-mac-image-test.sh | 41 ++++++++++++++++++++++ image-builder/test/test_inspection.py | 27 ++++++++++++++ 4 files changed, 91 insertions(+), 16 deletions(-) diff --git a/image-builder/bin/build-mac-image b/image-builder/bin/build-mac-image index 7c7cd3ff..31185600 100755 --- a/image-builder/bin/build-mac-image +++ b/image-builder/bin/build-mac-image @@ -846,11 +846,12 @@ release_build_pacman_config() { rmdir "$target/var/cache/omarchy-image-repositories" "$target/var/cache/omarchy-image-packages" } -# The installed configuration for an Apple Silicon Mac: omarchy-mac's -# template for the channel the inputs name (an older set's runtime -# apple-silicon one, or an even older runtime's aarch64 one), with the -# runtime's aarch64 mirror list (per channel, or the single -# mirrorlist-aarch64 of a runtime that keeps its own repositories). TEST IMAGES ONLY: a +# The installed configuration for an Apple Silicon Mac: the runtime's +# aarch64-apple template for the channel the inputs name (else omarchy-mac's, +# an older runtime's apple-silicon one, or an even older runtime's aarch64 +# one), with the mirror list beside the runtime's aarch64-apple template, else +# its aarch64 one (per channel, or the single mirrorlist-aarch64 of a runtime +# that keeps its own repositories). TEST IMAGES ONLY: a # test candidate set's runtime sorts below the channel's omarchy, so its # [options] gain a marked IgnorePkg for the packages built from the set's # source commit, which keeps a pacman -Syu from replacing them @@ -858,10 +859,12 @@ release_build_pacman_config() { write_installed_config() { local channel templates=$target/usr/share/omarchy/default/pacman repositories mirrorlist channel=$(field omarchy_channel) - repositories=$target/usr/share/omarchy-mac/pacman + repositories=$templates/aarch64-apple + [[ -f $repositories/pacman-$channel.conf ]] || repositories=$target/usr/share/omarchy-mac/pacman [[ -f $repositories/pacman-$channel.conf ]] || repositories=$templates/apple-silicon [[ -f $repositories/pacman-$channel.conf ]] || repositories=$templates/aarch64 - mirrorlist=$templates/aarch64/mirrorlist-$channel + mirrorlist=$templates/aarch64-apple/mirrorlist-$channel + [[ -f $mirrorlist ]] || mirrorlist=$templates/aarch64/mirrorlist-$channel [[ -f $mirrorlist ]] || mirrorlist=$templates/mirrorlist-aarch64 [[ -f $repositories/pacman-$channel.conf ]] || fail "the image ships no Apple Silicon pacman configuration for $channel" [[ -f $mirrorlist ]] || fail "the runtime ships no aarch64 mirror list for $channel" diff --git a/image-builder/builder/inspection.py b/image-builder/builder/inspection.py index 063f5f2d..9caf2479 100644 --- a/image-builder/builder/inspection.py +++ b/image-builder/builder/inspection.py @@ -517,30 +517,34 @@ def check_first_boot(root: Path, report: dict) -> str: def pacman_templates(root: Path, channel: str) -> tuple[str, Path, Path]: - # The Apple template as build-mac-image picks it: omarchy-mac's, else an - # older runtime's apple-silicon one, else an even older runtime's aarch64 - # one; and the runtime's aarch64 mirror list, per channel or single. + # The Apple template as build-mac-image picks it: the runtime's + # aarch64-apple one, else omarchy-mac's, else an older runtime's + # apple-silicon one, else an even older runtime's aarch64 one; and the + # mirror list beside the runtime's aarch64-apple template, else its aarch64 + # one, per channel or single. runtime = root / "usr/share/omarchy/default/pacman" - choices = (("omarchy-mac's apple-silicon", root / f"usr/share/omarchy-mac/pacman/pacman-{channel}.conf"), + choices = (("the runtime's aarch64-apple", runtime / f"aarch64-apple/pacman-{channel}.conf"), + ("omarchy-mac's apple-silicon", root / f"usr/share/omarchy-mac/pacman/pacman-{channel}.conf"), ("the runtime's apple-silicon", runtime / f"apple-silicon/pacman-{channel}.conf"), ("the runtime's aarch64", runtime / f"aarch64/pacman-{channel}.conf")) kind, template = next((choice for choice in choices if choice[1].is_file()), choices[-1]) - mirrorlist = runtime / f"aarch64/mirrorlist-{channel}" - if not mirrorlist.is_file(): - mirrorlist = runtime / "mirrorlist-aarch64" + mirrorlist = next((path for path in (runtime / f"aarch64-apple/mirrorlist-{channel}", + runtime / f"aarch64/mirrorlist-{channel}") if path.is_file()), + runtime / "mirrorlist-aarch64") return kind, template, mirrorlist def check_pacman_config(root: Path, channel: str, candidates: Candidates) -> str: kind, template, mirrorlist = pacman_templates(root, channel) + mirrors = "aarch64-apple" if mirrorlist.parent.name == "aarch64-apple" else "aarch64" require(template.is_file(), f"the image ships no Apple Silicon pacman configuration for {channel}") pinned = test_image_pin.pinned(candidates.summary) require((root / "etc/pacman.conf").read_bytes() == test_image_pin.render(limine.regular(template), pinned), f"/etc/pacman.conf is not {kind} {channel} configuration" + (" with the test image's pin" if pinned else "")) require((root / "etc/pacman.d/mirrorlist").read_bytes() == limine.regular(mirrorlist), - f"/etc/pacman.d/mirrorlist is not the runtime's aarch64 {channel} mirror list") - return (f"{kind} {channel} pacman.conf and the runtime's aarch64 mirror list" + f"/etc/pacman.d/mirrorlist is not the runtime's {mirrors} {channel} mirror list") + return (f"{kind} {channel} pacman.conf and the runtime's {mirrors} mirror list" + (f"; test image pin: IgnorePkg = {' '.join(pinned)}" if pinned else "")) diff --git a/image-builder/test/build-mac-image-test.sh b/image-builder/test/build-mac-image-test.sh index e1a25216..cbd9c9ba 100755 --- a/image-builder/test/build-mac-image-test.sh +++ b/image-builder/test/build-mac-image-test.sh @@ -147,6 +147,47 @@ grep -A3 '^ prepare_repositories$' "$here/bin/build-mac-image" | grep -Fxq ' r fail "the build reads the Apple list before it creates the images" pass "the build reads the Apple list once, before it creates the images" +# The installed pacman configuration, from each runtime layout: e1b0e5e9b's aarch64-apple templates, +# 5397950a2's apple-silicon template with its aarch64 mirror list, and older ones. +installed_config() { + local layout + target=$scratch/installed + rm -rf "$target" + mkdir -p "$target/etc" + for layout in "$@"; do + mkdir -p "$target/${layout%/*}" + printf '[options]\n# %s\n' "$layout" >"$target/$layout" + done + (fail() { builder_fail "$@"; }; write_installed_config) || return + printf '%s %s\n' "$(sed -n 2p "$target/etc/pacman.conf")" "$(sed -n 2p "$target/etc/pacman.d/mirrorlist")" +} +templates=usr/share/omarchy/default/pacman +e1b0e5e9b=("$templates"/aarch64/{pacman-edge.conf,mirrorlist-edge} "$templates"/aarch64-apple/{pacman-edge.conf,mirrorlist-edge}) +r5397950a2=("$templates"/aarch64/{pacman-edge.conf,mirrorlist-edge} "$templates"/apple-silicon/{pacman-edge.conf,mirrorlist-edge}) +[[ $(installed_config "${e1b0e5e9b[@]}") == "# $templates/aarch64-apple/pacman-edge.conf # $templates/aarch64-apple/mirrorlist-edge" ]] || + fail "an e1b0e5e9b runtime installs its aarch64-apple template and mirror list" +[[ $(installed_config "${e1b0e5e9b[@]}" usr/share/omarchy-mac/pacman/pacman-edge.conf) == \ + "# $templates/aarch64-apple/pacman-edge.conf # $templates/aarch64-apple/mirrorlist-edge" ]] || + fail "the runtime's aarch64-apple template wins over omarchy-mac's" +[[ $(installed_config "${r5397950a2[@]}") == "# $templates/apple-silicon/pacman-edge.conf # $templates/aarch64/mirrorlist-edge" ]] || + fail "a 5397950a2 runtime installs its apple-silicon template with its aarch64 mirror list" +[[ $(installed_config "${r5397950a2[@]}" usr/share/omarchy-mac/pacman/pacman-edge.conf) == \ + "# usr/share/omarchy-mac/pacman/pacman-edge.conf # $templates/aarch64/mirrorlist-edge" ]] || + fail "omarchy-mac's template wins over a 5397950a2 runtime's apple-silicon one" +[[ $(installed_config "$templates"/aarch64/pacman-edge.conf "$templates"/mirrorlist-aarch64) == \ + "# $templates/aarch64/pacman-edge.conf # $templates/mirrorlist-aarch64" ]] || + fail "an older runtime installs its aarch64 template and single mirror list" +[[ $(installed_config "$templates"/aarch64-apple/pacman-edge.conf "$templates"/aarch64/mirrorlist-edge) == \ + "# $templates/aarch64-apple/pacman-edge.conf # $templates/aarch64/mirrorlist-edge" ]] || + fail "an aarch64-apple template without its own mirror list takes the aarch64 one" +if output=$(installed_config "$templates"/aarch64-apple/mirrorlist-edge 2>&1); then + fail "a runtime with no Apple Silicon template for the channel is refused" +fi +[[ $output == "build-mac-image: the image ships no Apple Silicon pacman configuration for edge" ]] || + fail "a runtime with no template is refused with its reason (got: $output)" +unset target +pass "the installed pacman.conf is the runtime's aarch64-apple template with its mirror list, else omarchy-mac's, apple-silicon or aarch64 with the aarch64 list" + runtime_list() { case $1 in base) printf 'hyprland\nobs-studio\n' ;; diff --git a/image-builder/test/test_inspection.py b/image-builder/test/test_inspection.py index 07d48b00..883f8ff2 100755 --- a/image-builder/test/test_inspection.py +++ b/image-builder/test/test_inspection.py @@ -546,6 +546,33 @@ def test_pacman_config_prefers_omarchy_macs_template(self): self.assertEqual(report["checks"]["pacman-config"]["result"], "passed", report["checks"]["pacman-config"]) self.assertIn("omarchy-mac's apple-silicon edge pacman.conf", report["checks"]["pacman-config"]["detail"]) + def test_pacman_config_follows_the_runtimes_aarch64_apple_templates(self): + runtime = self.root / "usr/share/omarchy/default/pacman" + apple = "[options]\nArchitecture = auto\n\n[omarchy]\nServer = https://pkgs.omarchy.org/edge/$arch\n" + apple += "\n[asahi-alarm]\nServer = https://github.com/asahi-alarm/asahi-alarm/releases/download/aarch64\n" + apple += "".join(f"\n[{r}]\nInclude = /etc/pacman.d/mirrorlist\n" for r in ("core", "extra", "alarm", "aur")) + for directory in (runtime / "apple-silicon", self.root / "usr/share/omarchy-mac/pacman"): + directory.mkdir(parents=True) + (directory / "pacman-edge.conf").write_text(apple + f"\n# {directory.name}\n") + (runtime / "aarch64-apple").mkdir() + (runtime / "aarch64-apple/pacman-edge.conf").write_text(apple) + (runtime / "aarch64-apple/mirrorlist-edge").write_text("Server = https://arm-mirror.omarchy.org/$arch/$repo\n") + pinned = fixtures.test_image_pin.pinned(self.summary) + self.assertFails("pacman-config", "is not the runtime's aarch64-apple edge configuration") + (self.root / "etc/pacman.conf").write_bytes(fixtures.test_image_pin.render(apple.encode(), pinned)) + self.assertFails("pacman-config", "mirrorlist is not the runtime's aarch64-apple edge mirror list") + (self.root / "etc/pacman.d/mirrorlist").write_text("Server = https://arm-mirror.omarchy.org/$arch/$repo\n") + report = self.inspect() + self.assertEqual(report["checks"]["pacman-config"]["result"], "passed", report["checks"]["pacman-config"]) + self.assertIn("the runtime's aarch64-apple edge pacman.conf and the runtime's aarch64-apple mirror list", + report["checks"]["pacman-config"]["detail"]) + # Without a mirror list of its own, the aarch64-apple template takes the aarch64 one. + (runtime / "aarch64-apple/mirrorlist-edge").unlink() + self.assertFails("pacman-config", "mirrorlist is not the runtime's aarch64 edge mirror list") + (self.root / "etc/pacman.d/mirrorlist").write_bytes((runtime / "aarch64/mirrorlist-edge").read_bytes()) + report = self.inspect() + self.assertEqual(report["checks"]["pacman-config"]["result"], "passed", report["checks"]["pacman-config"]) + def test_pacman_config_needs_a_template(self): (self.root / "usr/share/omarchy/default/pacman/aarch64/pacman-edge.conf").unlink() self.assertFails("pacman-config", "the image ships no Apple Silicon pacman configuration for edge") From 22cfa6947dd0b576d174c79c14908f4cbcb22cb5 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 10 Oct 2026 11:15:42 +1000 Subject: [PATCH 3/7] Test sets and images shaped like both runtime layouts Fixtures gain e1b0e5e9b's layout (omarchy-aarch64-apple.packages, aarch64-apple templates) and 5397950a2's (omarchy-apple-silicon.packages, apple-silicon templates); each must verify and pass inspection, and neither may install the plain aarch64 template. --- image-builder/test/fixtures.py | 37 +++++++++++++++---- image-builder/test/test_candidate_set.py | 10 ++++++ image-builder/test/test_inspection.py | 46 ++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/image-builder/test/fixtures.py b/image-builder/test/fixtures.py index 5dedcbc6..1659b155 100755 --- a/image-builder/test/fixtures.py +++ b/image-builder/test/fixtures.py @@ -25,6 +25,17 @@ BOOT_SOURCE = "b" * 40 RELEASE = "7.0.0-1-ARCH" MODELS = ("t6000-j314s", "t8103-j274") +# The runtime layouts the builder takes, by the omacom/omarchy commit that has +# them: the Apple package list and the pacman template directories it ships, +# and the directories build-mac-image installs pacman.conf and the mirror list +# from. Without a layout, a fixture is an older runtime's: apple-silicon's list +# and aarch64 templates alone. +RUNTIME_LAYOUTS = { + "e1b0e5e9b": {"apple_list": "omarchy-aarch64-apple.packages", "templates": ("aarch64", "aarch64-apple"), + "installed": ("aarch64-apple", "aarch64-apple")}, + "5397950a2": {"apple_list": "omarchy-apple-silicon.packages", "templates": ("aarch64", "apple-silicon"), + "installed": ("apple-silicon", "aarch64")}, +} def load(name: str, relative: str): @@ -56,7 +67,7 @@ def dtb(model: str) -> bytes: return b"\xd0\x0d\xfe\xed" + struct.pack(">I", 8 + len(body)) + body -def default_contents() -> dict[str, dict[str, bytes]]: +def default_contents(layout: str | None = None) -> dict[str, dict[str, bytes]]: first_boot = b"#!/bin/bash\ncmp -s \"$steps\" <(printf '%s\\n' 'install/hardware/apple/limine-boot.sh')\n" contents = { "omarchy": { @@ -89,6 +100,12 @@ def default_contents() -> dict[str, dict[str, bytes]]: }, "limine-mkinitcpio-hook": {"usr/share/libalpm/scripts/limine-apple-gate": b"#!/bin/bash\n"}, } + if layout: + install = "usr/share/omarchy/install/" + runtime = contents["omarchy"] + runtime[install + RUNTIME_LAYOUTS[layout]["apple_list"]] = runtime.pop(install + "omarchy-apple-silicon.packages") + runtime[install + "omarchy-aarch64.packages"] = b"# aarch64\nzram-generator\n" + runtime[install + "omarchy-x86_64-only.packages"] = b"# x86_64 only\nobs-studio\n" return contents @@ -302,8 +319,9 @@ def image_target(summary: dict, profile: str = "test") -> str: f"package_set_sha256={PACKAGE_SET_SHA256}\nbuilt={BUILT}\n") -def make_root(root: Path, candidates: Path) -> None: - """An image root that passes inspection against the imported set CANDIDATES.""" +def make_root(root: Path, candidates: Path, layout: str | None = None) -> None: + """An image root that passes inspection against the imported set CANDIDATES, + with the pacman templates of the runtime LAYOUT (RUNTIME_LAYOUTS).""" summary = json.loads((candidates / "import.json").read_text()) def member(name: str, path: str) -> bytes: @@ -378,10 +396,15 @@ def member(name: str, path: str) -> bytes: template = "[options]\nArchitecture = auto\n\n[asahi-alarm]\nServer = https://github.com/asahi-alarm/asahi-alarm/releases/download/aarch64\n" template += "".join(f"\n[{r}]\nInclude = /etc/pacman.d/mirrorlist\n" for r in ("core", "extra", "alarm", "aur")) template += "\n[omarchy]\nServer = https://pkgs.omarchy.org/edge/$arch\n" - write(root, "usr/share/omarchy/default/pacman/aarch64/pacman-edge.conf", template) - write(root, "usr/share/omarchy/default/pacman/aarch64/mirrorlist-edge", "Server = https://mirror.invalid/$arch/$repo\n") - write(root, "etc/pacman.conf", test_image_pin.render(template.encode(), test_image_pin.pinned(summary))) - write(root, "etc/pacman.d/mirrorlist", "Server = https://mirror.invalid/$arch/$repo\n") + templates = {"aarch64": (template, "Server = https://mirror.invalid/$arch/$repo\n")} + for name in RUNTIME_LAYOUTS[layout]["templates"] if layout else (): + templates.setdefault(name, (f"# {name}\n" + template, f"Server = https://{name}.mirror.invalid/$arch/$repo\n")) + for name, (conf, mirrors) in templates.items(): + write(root, f"usr/share/omarchy/default/pacman/{name}/pacman-edge.conf", conf) + write(root, f"usr/share/omarchy/default/pacman/{name}/mirrorlist-edge", mirrors) + conf, mirrors = RUNTIME_LAYOUTS[layout]["installed"] if layout else ("aarch64", "aarch64") + write(root, "etc/pacman.conf", test_image_pin.render(templates[conf][0].encode(), test_image_pin.pinned(summary))) + write(root, "etc/pacman.d/mirrorlist", templates[mirrors][1]) # What the installed-system checks read. write(root, "usr/lib/NetworkManager/conf.d/20-omarchy-mac-wifi.conf", "[device]\nwifi.backend=iwd\n") write(root, "etc/systemd/system/omarchy-vendor-firmware.service", diff --git a/image-builder/test/test_candidate_set.py b/image-builder/test/test_candidate_set.py index 4246ea87..d19a4844 100755 --- a/image-builder/test/test_candidate_set.py +++ b/image-builder/test/test_candidate_set.py @@ -326,6 +326,16 @@ def test_apple_package_list_prefers_upstreams_name(self): with self.assertRaisesRegex(ValueError, "lacks the add-on or boot package"): self.verify(self.work / "platform-stale", receipt) + def test_each_runtime_layout_verifies(self): + for layout in fixtures.RUNTIME_LAYOUTS: + with self.subTest(layout=layout): + output = self.work / "output" + if output.exists(): + output.chmod(0o700) + shutil.rmtree(output) + receipt = fixtures.make_set(self.work / layout, self.signer, contents=fixtures.default_contents(layout)) + self.assertEqual(self.verify(self.work / layout, receipt)["source_commit"], fixtures.SOURCE) + def test_apple_package_list_missing(self): for directory, lists in (("none", {}), ("link-only", {"omarchy-apple.packages": "omarchy-apple-silicon.packages"}), ("platform-link-only", {"omarchy-aarch64-apple.packages": "omarchy-apple.packages"})): diff --git a/image-builder/test/test_inspection.py b/image-builder/test/test_inspection.py index 883f8ff2..f01a8424 100755 --- a/image-builder/test/test_inspection.py +++ b/image-builder/test/test_inspection.py @@ -642,6 +642,52 @@ def test_runtime_may_change_a_closure_packages_files(self): path.chmod(0o700) +class RuntimeLayoutInspectionTest(unittest.TestCase): + """Images built from each runtime layout the builder takes (fixtures.RUNTIME_LAYOUTS).""" + + EXPECTED = { + "e1b0e5e9b": ("omarchy-aarch64-apple.packages", + "the runtime's aarch64-apple edge pacman.conf and the runtime's aarch64-apple mirror list"), + "5397950a2": ("omarchy-apple-silicon.packages", + "the runtime's apple-silicon edge pacman.conf and the runtime's aarch64 mirror list"), + } + + def test_each_runtime_layout_passes_inspection(self): + with tempfile.TemporaryDirectory() as scratch: + base = Path(scratch) + signer = fixtures.Signer(base / "signer") + try: + for layout, (apple_list, pacman) in self.EXPECTED.items(): + with self.subTest(layout=layout): + work = base / layout + receipt = fixtures.make_set(work / "set", signer, contents=fixtures.default_contents(layout)) + candidates = work / "candidates" + fixtures.import_set(work / "set", candidates, signer, receipt) + summary = __import__("json").loads((candidates / "import.json").read_text()) + root = work / "root" + fixtures.make_root(root, candidates, layout) + factory = work / "factory" + fixtures.make_factory(factory, root, summary) + SUBVOLUMES.clear() + SUBVOLUMES.add(root / ".snapshots") + report = inspection.inspect(root, candidates, "edge", factory, trust=signer.trust) + failed = {name: check for name, check in report["checks"].items() if check["result"] != "passed"} + self.assertEqual(report["result"], "passed", failed) + self.assertEqual(report["apple_package_list"], apple_list) + self.assertIn(pacman, report["checks"]["pacman-config"]["detail"]) + # The plain aarch64 template, without asahi-alarm, is not what the image installs. + template = root / "usr/share/omarchy/default/pacman/aarch64/pacman-edge.conf" + (root / "etc/pacman.conf").write_bytes( + fixtures.test_image_pin.render(template.read_bytes(), fixtures.test_image_pin.pinned(summary))) + report = inspection.inspect(root, candidates, "edge", factory, trust=signer.trust) + self.assertEqual(report["checks"]["pacman-config"]["result"], "failed") + finally: + signer.close() + for path in base.rglob("*"): + if path.is_dir() and not path.is_symlink(): + path.chmod(0o700) + + class TestImagePinTest(unittest.TestCase): TEMPLATE = b"# pacman\n\n[options]\nArchitecture = auto\n\n[core]\nInclude = /etc/pacman.d/mirrorlist\n" From 7d010b312aedc036d3b358a35d9d89cd6f9cfdc7 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 10 Oct 2026 11:15:43 +1000 Subject: [PATCH 4/7] Say why the image manifest keeps platform=apple-silicon e1b0e5e9b reads apple-silicon as aarch64-apple, but 5397950a2, in released images and packages, refuses aarch64-apple. --- image-builder/bin/build-mac-image | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/image-builder/bin/build-mac-image b/image-builder/bin/build-mac-image index 31185600..5b117d1c 100755 --- a/image-builder/bin/build-mac-image +++ b/image-builder/bin/build-mac-image @@ -33,7 +33,11 @@ SUPPORTED_FW=(13.5 14.8.3) ICON_SHA256=cf26ed5d2831db99c00d62ca046040e01a18e08e63363d629340d04ac6ec8c23 KERNEL=linux-aurora # The hardware this image is for. It is written to the image-target manifest -# and is the only machine identity the build chroot sees. +# and is the only machine identity the build chroot sees. Upstream's name is now +# aarch64-apple (omacom/omarchy e1b0e5e9b), which still reads apple-silicon from +# a manifest; an older runtime (5397950a2, in released images and packages) +# refuses aarch64-apple in omarchy-hw-platform and install/helpers/image-target.sh, +# so the manifest keeps apple-silicon until no supported runtime needs it. PLATFORM=apple-silicon PLATFORM_COMPATIBLE=apple,arm-platform @@ -762,6 +766,8 @@ write_image_target() { [[ ${MAC_IMAGE_BUILDER_COMMIT:-} =~ ^[0-9a-f]{40}$ && ${MAC_IMAGE_BUILDER_CLEAN:-} =~ ^(true|false)$ ]] || fail "the build names no builder commit for the image target" install -d -m 0755 "$target/var/lib/omarchy" "$target/var/lib/omarchy/image" + # platform=apple-silicon, not upstream's aarch64-apple: the new runtime reads + # either, an older one only apple-silicon (see PLATFORM above). printf 'format=1\nplatform=%s\ncandidate_set=%s\ncandidate_source_commit=%s\nbuilder_commit=%s\nbuilder_tree_clean=%s\nimage_profile=%s\n' \ "$PLATFORM" "$set_name" "$source_commit" "$MAC_IMAGE_BUILDER_COMMIT" "$MAC_IMAGE_BUILDER_CLEAN" "$image_profile" \ >"$target/var/lib/omarchy/image/target" From 7e9a552590afcf112946a93e51495832340fd950 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 10 Oct 2026 11:15:43 +1000 Subject: [PATCH 5/7] Name the aarch64-apple list and templates in the image docs --- docs/image-producer.md | 4 ++-- image-builder/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/image-producer.md b/docs/image-producer.md index 11c4a048..fd4bd7d4 100644 --- a/docs/image-producer.md +++ b/docs/image-producer.md @@ -57,8 +57,8 @@ Candidate set, as the importer accepts it: Build order inside the container: 1. A base system and `omarchy-settings`, alone, so its pacman platform guard (omacom/omarchy-mac#539) is resident before any platform package. -2. The root-owned image-target manifest `/var/lib/omarchy/image/target` (`format=1`, `platform=apple-silicon`), which deferred hardware setup (omacom/omarchy-mac#528) and the pacman platform guard (#539) both read. It also records `candidate_set`, `candidate_source_commit`, `builder_commit`, `builder_tree_clean` and `image_profile` (`lab` for `--lab-access`, `test` for a candidate-only set, else `release`), which the runtime ignores; after first boot the file is `target.booted`, so a booted Mac names the image it came from. Once the package set is recorded (step 5), the builder appends `package_set_sha256` and `built` (UTC, `YYYY-MM-DDTHH:MM:SSZ`), the values `IMAGE` and `PROVENANCE` record, before `@factory` is sealed, so a hardware report maps to one build's release assets (`image-builder/README.md`, Build identity). The image's own sha256 is not in it (root.img holds the file): `IMAGE`, `PROVENANCE` and the catalog carry it. -3. The runtime with `omarchy-base.packages`, then the Apple set: `omarchy-apple-silicon.packages` (an older runtime's `omarchy-apple.packages`), the kernel, m1n1, U-Boot and the Limine hook, plus `alsa-ucm-conf-asahi` and `asahi-audio` from the pinned asahi-alarm database. Every candidate set package is installed by its `omarchy-candidates/` name, so `[asahi-alarm]`, ordered before `[omarchy]` in the Apple profile, cannot supply `uboot-asahi` or `m1n1`. +2. The root-owned image-target manifest `/var/lib/omarchy/image/target` (`format=1`, `platform=apple-silicon`: upstream's platform is now `aarch64-apple`, but the runtime still reads `apple-silicon` and an older runtime (omacom/omarchy `5397950a2`) refuses `aarch64-apple`), which deferred hardware setup (omacom/omarchy-mac#528) and the pacman platform guard (#539) both read. It also records `candidate_set`, `candidate_source_commit`, `builder_commit`, `builder_tree_clean` and `image_profile` (`lab` for `--lab-access`, `test` for a candidate-only set, else `release`), which the runtime ignores; after first boot the file is `target.booted`, so a booted Mac names the image it came from. Once the package set is recorded (step 5), the builder appends `package_set_sha256` and `built` (UTC, `YYYY-MM-DDTHH:MM:SSZ`), the values `IMAGE` and `PROVENANCE` record, before `@factory` is sealed, so a hardware report maps to one build's release assets (`image-builder/README.md`, Build identity). The image's own sha256 is not in it (root.img holds the file): `IMAGE`, `PROVENANCE` and the catalog carry it. +3. The runtime with `omarchy-base.packages`, then the Apple set: `omarchy-aarch64-apple.packages` (an older runtime's `omarchy-apple-silicon.packages` or `omarchy-apple.packages`), the kernel, m1n1, U-Boot and the Limine hook, plus `alsa-ucm-conf-asahi` and `asahi-audio` from the pinned asahi-alarm database. Every candidate set package is installed by its `omarchy-candidates/` name, so `[asahi-alarm]`, ordered before `[omarchy]` in the Apple profile, cannot supply `uboot-asahi` or `m1n1`. 4. The runtime's `omarchy-apply-system --defer-provisioning --first-install`. The build chroot never sees the build host's hardware: a device tree naming the image's platform, UEFI without EFI variables, no PCI, USB, input or DMI devices. The host kernel must run with a device tree for the image's platform checks to find one. A runtime with #528 queues its hardware steps for first boot from the manifest; the first candidate set's runtime (`073e489b5`) predates it and runs them here, and its first boot runs only the Limine leaf (`mac-first-boot/deferred-steps`). 5. The image's GRUB defaults (the Limine command line's source, with `plymouth.ignore-serial-consoles` as on mx-mac) and Plymouth's `omarchy` theme, presets (`80-omarchy-mac*.preset`, so omarchy-mac's audio preset too), Node.js for owner provisioning, `mkinitcpio -P`, GRUB, `update-m1n1` under `LC_ALL=C`, the runtime's GRUB compatibility and Limine leaves, and first boot armed by the boot package's own `arm` command. 6. Seal (snapper's `/.snapshots` stays a btrfs subvolume through the copy), inspect, package. `PROVENANCE` and `IMAGE` record the builder commit, the set's receipt, manifest, source commit and signer, each installed package's repository and archive sha256, `package_set_sha256`, and `built`, the UTC time the build recorded its identity in the image-target manifest (the same `package_set_sha256` and `built` the manifest carries). diff --git a/image-builder/README.md b/image-builder/README.md index 9e2096be..11f19818 100644 --- a/image-builder/README.md +++ b/image-builder/README.md @@ -21,7 +21,7 @@ A build takes about 15 minutes and 25 GB of disk. On a host with a desktop sessi ## What a build does -1. Installs a base system and `omarchy-settings` first, so its pacman platform guard is resident, then writes the root-owned image-target manifest (`/var/lib/omarchy/image/target`: `format=1`, `platform=apple-silicon`, read by deferred hardware setup and the platform guard, then the image's provenance, which the runtime ignores: `candidate_set`, `candidate_source_commit`, `builder_commit`, `builder_tree_clean` and `image_profile` of `lab`, `test` for a candidate-only set, or `release`), then installs the runtime with `omarchy-base.packages` and `omarchy-aarch64.packages` (as `omarchy-pkg-defaults apple-silicon` composes them) and, last, the Apple set: the runtime's Apple list (`omarchy-apple-silicon.packages`, or an older runtime's `omarchy-apple.packages`; a runtime with neither stops the build), the Aurora kernel, m1n1, U-Boot, the Limine hook, and the speaker stack's model profiles and DSP chain (`alsa-ucm-conf-asahi`, `asahi-audio`), which the runtime's audio step would otherwise fetch on a first boot that may have no network. Set packages are installed by their `omarchy-candidates/` name, so no other repository can supply them. Base names the pinned repositories lack are recorded as `unavailable=` in `PROVENANCE`. +1. Installs a base system and `omarchy-settings` first, so its pacman platform guard is resident, then writes the root-owned image-target manifest (`/var/lib/omarchy/image/target`: `format=1`, `platform=apple-silicon`, read by deferred hardware setup and the platform guard; upstream's platform is now `aarch64-apple`, but omacom/omarchy e1b0e5e9b still reads `apple-silicon` and an older runtime (5397950a2) refuses `aarch64-apple`, then the image's provenance, which the runtime ignores: `candidate_set`, `candidate_source_commit`, `builder_commit`, `builder_tree_clean` and `image_profile` of `lab`, `test` for a candidate-only set, or `release`), then installs the runtime with `omarchy-base.packages` and `omarchy-aarch64.packages` (as `omarchy-pkg-defaults aarch64-apple` composes them; `apple-silicon` on an older runtime) and, last, the Apple set: the runtime's Apple list (`omarchy-aarch64-apple.packages`, or an older runtime's `omarchy-apple-silicon.packages` or `omarchy-apple.packages`; a runtime with none stops the build), the Aurora kernel, m1n1, U-Boot, the Limine hook, and the speaker stack's model profiles and DSP chain (`alsa-ucm-conf-asahi`, `asahi-audio`), which the runtime's audio step would otherwise fetch on a first boot that may have no network. Set packages are installed by their `omarchy-candidates/` name, so no other repository can supply them. Base names the pinned repositories lack are recorded as `unavailable=` in `PROVENANCE`. 2. Runs the runtime's own `omarchy-apply-system --defer-provisioning --first-install` in an isolated chroot. The chroot never sees the build host's hardware: its device tree names the image's platform, it has UEFI (as U-Boot provides) without EFI variables, and no PCI, USB, input or DMI devices, so hardware setup installs the same packages on any aarch64 host whose kernel runs with a device tree (an Apple Silicon Mac, most arm64 boards; on an ACPI-only host the image's platform checks find no device tree and the build stops at the Limine activation). A runtime with deferred hardware setup (omacom/omarchy-mac#528) queues its hardware steps from the manifest; an older one runs them here for the image's platform. 3. Sets the kernel command line in `/etc/default/grub`, which the Limine command line is derived from; the image installs no GRUB (`quiet splash`, and `plymouth.ignore-serial-consoles` as on mx-mac, since the Mac's device tree registers a serial console), and Plymouth's `omarchy` theme, which omarchy-settings leaves to Arch Linux ARM's `bgrt` on Apple Silicon. Applies the Apple presets (`80-omarchy-mac*.preset`, including omarchy-mac's audio preset), stages owner provisioning with the pinned Node.js, rebuilds the initramfs, runs `update-m1n1` with `LC_ALL=C` so the device trees go into m1n1's stage 2 in one order, activates Limine through omarchy-mac-boot's `setup-boot` operation of the runtime's lifecycle dispatcher (once before the Limine gate is set, for the console settings, and once after, for the activation; a runtime whose dispatcher has no `setup-boot`, or a boot package without it, keeps the runtime's own `install/hardware/apple` console and Limine leaves), and arms first boot with the boot package's own `arm` command. Once the installed package set is recorded, it appends the build's identity to the image-target manifest (see [Build identity](#build-identity)). 4. Copies the root into a fresh image, keeping snapper's `/.snapshots` a btrfs subvolume (a file copy would flatten it into a plain directory and snapper would fail on the Mac), seals a snapshot of it into `@factory`, then inspects the images against the set's archives and packages them. @@ -40,7 +40,7 @@ A build takes about 15 minutes and 25 GB of disk. On a host with a desktop sessi - the pacman hooks that rebuild the UKI and redeploy Limine come from their packages, the Apple gate included - the image-target manifest (its provenance matching the set, the build's profile and `@factory`'s copy; `mac-image-check provenance` matches it to `PROVENANCE`), first boot, owner provisioning, the Limine gate and the fresh-image `deferred-steps` contract; the hardware queue never runs an older runtime's `install/hardware/apple/pacman.sh`, which adds the unsigned `[omarchy-aarch64]` - `/.snapshots` is an empty btrfs subvolume under snapper's root configuration, or absent with a deferred hardware step queued to create it; a plain directory fails -- the installed pacman configuration is omarchy-mac's Apple Silicon template for the channel (`/usr/share/omarchy-mac/pacman`; on an older set the runtime's `default/pacman/apple-silicon`, or `aarch64` on an older runtime still) with the runtime's aarch64 mirror list (`aarch64/mirrorlist-`, or a single `mirrorlist-aarch64`), plus the test image's pin below, and the installed-system checks (`builder/verify_installed_system.py`) pass, `alsa-ucm-conf-asahi`, `asahi-audio`, `vulkan-asahi` and `asahi-bless` included, and no `[omarchy-aarch64]` section or `TrustAll` SigLevel; Bluetooth counts as enabled when its hardware step is queued for first boot +- the installed pacman configuration is the runtime's Apple Silicon template for the channel (`default/pacman/aarch64-apple`; on an older set omarchy-mac's `/usr/share/omarchy-mac/pacman`, the runtime's `default/pacman/apple-silicon`, or `aarch64` on an older runtime still) with the mirror list beside the runtime's `aarch64-apple` template, else its aarch64 one (`aarch64/mirrorlist-`, or a single `mirrorlist-aarch64`), plus the test image's pin below, and the installed-system checks (`builder/verify_installed_system.py`) pass, `alsa-ucm-conf-asahi`, `asahi-audio`, `vulkan-asahi` and `asahi-bless` included, and no `[omarchy-aarch64]` section or `TrustAll` SigLevel; Bluetooth counts as enabled when its hardware step is queued for first boot - `@factory` is sealed for the set without fresh-image or owner state `bin/mac-image-check` also holds the payload to the installer engine's contract and checks `PROVENANCE` and `IMAGE` against the bytes beside them. From 9bf5b90280233f32ee1ee16e03f1d0b0ab8b6da8 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 10 Oct 2026 11:38:15 +1000 Subject: [PATCH 6/7] Keep asahi-alarm out of the layouts' plain aarch64 template As in e1b0e5e9b, only the platform template carries [asahi-alarm], so the check that an image does not install the plain aarch64 one tests that. --- image-builder/test/fixtures.py | 5 ++++- image-builder/test/test_inspection.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/image-builder/test/fixtures.py b/image-builder/test/fixtures.py index 1659b155..15f27da5 100755 --- a/image-builder/test/fixtures.py +++ b/image-builder/test/fixtures.py @@ -396,7 +396,10 @@ def member(name: str, path: str) -> bytes: template = "[options]\nArchitecture = auto\n\n[asahi-alarm]\nServer = https://github.com/asahi-alarm/asahi-alarm/releases/download/aarch64\n" template += "".join(f"\n[{r}]\nInclude = /etc/pacman.d/mirrorlist\n" for r in ("core", "extra", "alarm", "aur")) template += "\n[omarchy]\nServer = https://pkgs.omarchy.org/edge/$arch\n" - templates = {"aarch64": (template, "Server = https://mirror.invalid/$arch/$repo\n")} + # A runtime with a platform template keeps asahi-alarm out of its plain aarch64 one. + asahi = "\n[asahi-alarm]\nServer = https://github.com/asahi-alarm/asahi-alarm/releases/download/aarch64\n" + templates = {"aarch64": (template.replace(asahi, "") if layout else template, + "Server = https://mirror.invalid/$arch/$repo\n")} for name in RUNTIME_LAYOUTS[layout]["templates"] if layout else (): templates.setdefault(name, (f"# {name}\n" + template, f"Server = https://{name}.mirror.invalid/$arch/$repo\n")) for name, (conf, mirrors) in templates.items(): diff --git a/image-builder/test/test_inspection.py b/image-builder/test/test_inspection.py index f01a8424..c9695759 100755 --- a/image-builder/test/test_inspection.py +++ b/image-builder/test/test_inspection.py @@ -677,6 +677,7 @@ def test_each_runtime_layout_passes_inspection(self): self.assertIn(pacman, report["checks"]["pacman-config"]["detail"]) # The plain aarch64 template, without asahi-alarm, is not what the image installs. template = root / "usr/share/omarchy/default/pacman/aarch64/pacman-edge.conf" + self.assertNotIn("[asahi-alarm]", template.read_text()) (root / "etc/pacman.conf").write_bytes( fixtures.test_image_pin.render(template.read_bytes(), fixtures.test_image_pin.pinned(summary))) report = inspection.inspect(root, candidates, "edge", factory, trust=signer.trust) From 5d181b1ffd9e2240aa30b2f534d2b8af256e5754 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Sat, 10 Oct 2026 11:38:15 +1000 Subject: [PATCH 7/7] Name omarchy-aarch64.packages in the producer's build order --- docs/image-producer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/image-producer.md b/docs/image-producer.md index fd4bd7d4..743a299b 100644 --- a/docs/image-producer.md +++ b/docs/image-producer.md @@ -58,7 +58,7 @@ Build order inside the container: 1. A base system and `omarchy-settings`, alone, so its pacman platform guard (omacom/omarchy-mac#539) is resident before any platform package. 2. The root-owned image-target manifest `/var/lib/omarchy/image/target` (`format=1`, `platform=apple-silicon`: upstream's platform is now `aarch64-apple`, but the runtime still reads `apple-silicon` and an older runtime (omacom/omarchy `5397950a2`) refuses `aarch64-apple`), which deferred hardware setup (omacom/omarchy-mac#528) and the pacman platform guard (#539) both read. It also records `candidate_set`, `candidate_source_commit`, `builder_commit`, `builder_tree_clean` and `image_profile` (`lab` for `--lab-access`, `test` for a candidate-only set, else `release`), which the runtime ignores; after first boot the file is `target.booted`, so a booted Mac names the image it came from. Once the package set is recorded (step 5), the builder appends `package_set_sha256` and `built` (UTC, `YYYY-MM-DDTHH:MM:SSZ`), the values `IMAGE` and `PROVENANCE` record, before `@factory` is sealed, so a hardware report maps to one build's release assets (`image-builder/README.md`, Build identity). The image's own sha256 is not in it (root.img holds the file): `IMAGE`, `PROVENANCE` and the catalog carry it. -3. The runtime with `omarchy-base.packages`, then the Apple set: `omarchy-aarch64-apple.packages` (an older runtime's `omarchy-apple-silicon.packages` or `omarchy-apple.packages`), the kernel, m1n1, U-Boot and the Limine hook, plus `alsa-ucm-conf-asahi` and `asahi-audio` from the pinned asahi-alarm database. Every candidate set package is installed by its `omarchy-candidates/` name, so `[asahi-alarm]`, ordered before `[omarchy]` in the Apple profile, cannot supply `uboot-asahi` or `m1n1`. +3. The runtime with `omarchy-base.packages` and `omarchy-aarch64.packages` (when the runtime ships it), then the Apple set: `omarchy-aarch64-apple.packages` (an older runtime's `omarchy-apple-silicon.packages` or `omarchy-apple.packages`), the kernel, m1n1, U-Boot and the Limine hook, plus `alsa-ucm-conf-asahi` and `asahi-audio` from the pinned asahi-alarm database. Every candidate set package is installed by its `omarchy-candidates/` name, so `[asahi-alarm]`, ordered before `[omarchy]` in the Apple profile, cannot supply `uboot-asahi` or `m1n1`. 4. The runtime's `omarchy-apply-system --defer-provisioning --first-install`. The build chroot never sees the build host's hardware: a device tree naming the image's platform, UEFI without EFI variables, no PCI, USB, input or DMI devices. The host kernel must run with a device tree for the image's platform checks to find one. A runtime with #528 queues its hardware steps for first boot from the manifest; the first candidate set's runtime (`073e489b5`) predates it and runs them here, and its first boot runs only the Limine leaf (`mac-first-boot/deferred-steps`). 5. The image's GRUB defaults (the Limine command line's source, with `plymouth.ignore-serial-consoles` as on mx-mac) and Plymouth's `omarchy` theme, presets (`80-omarchy-mac*.preset`, so omarchy-mac's audio preset too), Node.js for owner provisioning, `mkinitcpio -P`, GRUB, `update-m1n1` under `LC_ALL=C`, the runtime's GRUB compatibility and Limine leaves, and first boot armed by the boot package's own `arm` command. 6. Seal (snapper's `/.snapshots` stays a btrfs subvolume through the copy), inspect, package. `PROVENANCE` and `IMAGE` record the builder commit, the set's receipt, manifest, source commit and signer, each installed package's repository and archive sha256, `package_set_sha256`, and `built`, the UTC time the build recorded its identity in the image-target manifest (the same `package_set_sha256` and `built` the manifest carries).