Skip to content

omarchy-mac-boot: deterministic m1n1 device-tree order, first boot via #528 - #541

Merged
maralcbr merged 17 commits into
quattro-upstreamfrom
mac/09-mac-boot-source
Sep 25, 2026
Merged

maralcbr merged 17 commits into
quattro-upstreamfrom
mac/09-mac-boot-source

Conversation

@maralcbr

@maralcbr maralcbr commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Ticket 09 of the Apple Silicon convergence plan: the omarchy-mac-boot source changes its notes ask for. The recipe is omacom/omarchy-pkgs#637, which pins this branch's head until a quattro-upstream merge commit exists.

Base is now quattro-upstream, which has #527 and #528. quattro-upstream is merged into this branch.

  • Deterministic boot.bin. update-m1n1 concatenates device trees through an unquoted glob, so boot.bin followed the locale of whatever ran the pacman transaction. On the M2 Max it was in en_US order, and checks that rebuilt it in C order reported a mismatch. The package now ships /etc/default/update-m1n1 exporting LC_ALL=C.
  • The boot check compares the device-tree set. When the C-order rebuild differs, the check splits the ESP image on its flattened device tree headers, requires exactly the expected device trees, each as often, rebuilds in that order and compares again. This replaces the session-locale rebuild, so an image written before the pin no longer fails from a session in another locale.
  • Deferred hardware setup. First boot runs omarchy-provision-hardware once the keyring exists, instead of sourcing the Limine leaf itself, and omarchy-provision-hardware.service waits for first boot. mac-first-boot/deferred-steps stays the initrd's conversion token. A fresh image (token present) now also needs Defer image hardware setup to the Mac's first boot #528's build manifest, /var/lib/omarchy/image/target (or target.booted on a retry); without it first boot stops rather than finishing with no hardware setup and GRUB still in charge. The image builder has to write it (tickets 21/23/24). A reset root has no token and is unaffected.

Testing

  • packages/omarchy-mac/boot/test/all in an Arch Linux ARM container as a non-root user: all 97 checks pass. New cases: update-m1n1's own steps under a real en_US.UTF-8 locale (without the file the caller's collation wins; with it C wins under LANG or LC_ALL; the file sets no update-m1n1 input), the boot check accepting the expected device trees in any order and refusing a duplicate, a missing or foreign device tree or a different tail, the first-boot hand-off and drop-in, and a fresh image without a manifest. Each new check fails against the previous code (mutation runs).
  • Runtime suite (test/all) as a non-root user in the same container: 2 of 314 files fail (hermes-remove-test.sh, and system-sleep-ownership-migration-test.sh, which is flaky), both in Run the shell suites in CI for quattro-upstream #518's list. Land #503 with the review fixes and the gap-audit merge blockers #527's head fails the same two plus desktop-entry-launch-test.sh.
  • Recipe build, ownership and pacman upgrade checks: Add omarchy-mac-boot for aarch64 edge omarchy-pkgs#637.
  • Not done: hardware. Cold boots on the M2 Max and M1 Pro are tickets 25 and 26.

Follow-up for #528: first boot runs omarchy-provision-hardware while this drop-in holds the service's own start job queued, and its disarm deletes the unit file and reloads systemd, so the journal logs a failed start job for a unit that no longer exists. Nothing is degraded. Removing only the wants link (keeping the unit, whose ConditionPathExists= then skips it) would avoid it; that is #528's code, so it is not changed here.

Second review: an independent review found that first boot would finish a fresh image with no hardware setup when the builder wrote no manifest (fixed, with a test), and the #528 follow-up above. The device-tree walk (truncated, garbage and oversized images, duplicate-content trees, a symlinked m1n1), the locale pin under sh and the unit ordering held up. The second-model review could not run today (usage limit) and still has to run before merge.

Update (2026-09-25, Brisbane): merged #527's current head (a279bffdc) into this branch. The only conflict was docs/file-layout.md; both paragraphs are kept, in quattro-upstream's order. Boot package suite: 97 checks pass. Runtime test/all as a non-root user in an Arch Linux ARM container: only hermes-remove-test.sh fails (known). The base fails that plus the flaky system-sleep-ownership-migration-test.sh. The recipe, omacom/omarchy-pkgs#637, now pins 3a58abb5. Second review of the merge, from a fresh-context reviewer on a different model, because the usual second-model reviewer was rate-limited: the conflict resolution is correct and no auto-merged file was silently combined. The usual reviewer's re-check is still owed.

Update 2 (2026-09-25, Brisbane):

update-m1n1 concatenates the device trees in the order its unquoted $DTBS
glob expands, which follows the locale of whoever runs it; pacman's hook
inherits the caller's. On the M2 Max boot.bin was written in en_US order and
checks that rebuilt it in C order reported a mismatch. omarchy-mac-boot now
ships /etc/default/update-m1n1 exporting LC_ALL=C, so every rebuild is the
same image.
An image update-m1n1 wrote before the C locale was pinned carries the same
device trees in its caller's collation, and the check only rebuilt C and
session order. When the C rebuild differs, the check now reads the device
trees from the image's flattened device tree headers, requires exactly the
expected ones, each as often, rebuilds in that order and compares again.
#528 queues every hardware step of an image build for the machine's first
boot. Mac first boot now runs omarchy-provision-hardware after creating the
package keyring instead of sourcing the Limine activation leaf itself, and
omarchy-provision-hardware.service waits for first boot. The fresh-image
deferred-steps token stays only as the initrd's conversion gate.
omarchy-provision-hardware does nothing without #528's build manifest, so an
image from a builder that does not write one would finish first boot with no
hardware setup and Limine never activated. A fresh image (its deferred-steps
token present) now needs /var/lib/omarchy/image/target, or target.booted once
an earlier attempt retired it; a reset root has no token and is unaffected.
A platform first boot that runs omarchy-provision-hardware while the
service's start job waits would otherwise turn that job into a failure at
the next daemon-reload. With the unit kept, the emptied queue skips it.
The conversion gate reads only the first-boot marker and its token, never
the image hardware queue that @factory restores, and a reset now drops the
token from @factory and the next root.
The drop-in now also skips omarchy-provision-hardware.service while first
boot is pending, so nothing runs the queue before this Mac's keyring exists.
A failed step names both logs, and the tests check that the hand-off runs
before owner setup is staged.
The queue holds every hardware leaf, and some need the network, so a failed
step on an offline first boot left the owner at the failure screen with no
way to get online. The step now stays queued for
omarchy-provision-hardware.service, which retries it after first boot and on
later boots. A runtime without the command still stops first boot.
Also match the exact not-a-fresh-image message in the restored-queue case.
@maralcbr

Copy link
Copy Markdown
Collaborator Author

Ticket 64 is folded in here rather than opened as a separate PR. I pushed seven commits on top of 602266c8; the head is now 7237f6d8d, so omacom/omarchy-pkgs#637's pin needs to move to it.

  • Kept unit file (the Defer image hardware setup to the Mac's first boot #528 follow-up above). Once the queue is empty, omarchy-provision-hardware removes only the enable link and keeps the unit file. I checked this in a systemd 261 container. Deleting the file while first boot holds the service's start job made the next daemon-reload fail that job. With the file kept, the job is skipped on its condition.
  • Drop-in gate. The drop-in also has ConditionPathExists=!/var/lib/omarchy/mac-first-boot/pending, so the queue never runs before this Mac's keyring exists.
  • Offline first boot. A failed deferred step no longer stops first boot. The queue holds every hardware leaf, and apple/pacman.sh needs the network, so an offline first boot left the owner at the failure screen with no way to get online. The step now stays queued for the service, and the journal names both logs. A missing omarchy-provision-hardware and a fresh image without a manifest still stop first boot.
  • Encryption gate. The initramfs gate never reads /var/lib/omarchy/image, which @factory keeps and a reset restores. There is a static check and a new disposable loop-device case: a reset-style root with a restored queue stays plaintext, and a gate mutated to accept the queue fails that case. A factory reset now also removes the mac-first-boot/deferred-steps token from @factory and from the next root.
  • Comments and docs are updated (disabled, not disarmed; who retires the manifest; docs/file-layout.md, source-port note). The tests now check that the hand-off runs after the keyring, before owner setup is staged, and leaves no error behind.

Tested at 7237f6d8d in an Arch Linux ARM container as a non-root user. packages/omarchy-mac/boot/test/all passes, 97 checks. The disposable encrypt lane passes, 34 checks. The runtime suite has 315 passing files. The same two fail on the base (hermes-remove, and update-lock in this container). test/cli and packages/omarchy-mac/test/all pass. Mutations of each new behaviour fail the tests.

Second review: two independent rounds. They found the offline dead end, the pipefail false pass in the new static check, a loose grep, a missing check that no error is left behind, and stale wording. All are fixed.

Left open, since they are #528's design: the service has no network ordering, so an Apple step that needs the network may keep failing at early boot. Its non-zero exit also doesn't separate a broken image from a transient failure, so first boot can't tell them apart.

maralcbr added a commit to omacom/omarchy-pkgs that referenced this pull request Sep 25, 2026
groups=('omarchy-platform-apple-silicon') is the platform tag the pacman
platform guard reads (omacom/omarchy-mac#539); the package is not published
yet, so it carries the tag from its first build. The pin moves to 3a58abb5,
omacom/omarchy-mac#541 with #527's current head merged in.
@maralcbr
maralcbr changed the base branch from mac/04-land-503 to quattro-upstream September 25, 2026 13:05
… network up

omarchy-provision-hardware exited 1 both when a queued step failed and when it
refused to run at all (untrusted manifest or queue), so first boot handed off
with no hardware setup in the second case. A failed step now exits 75 and first
boot tolerates only that; anything else keeps it on its retry screen.

What an offline first boot leaves queued is apple/pacman.sh's repository sync,
ahead of the Limine activation, and the retry service ran before the network
was up, so it could keep failing. The Mac drop-in now orders it after
network-online.target.
network-online.target waits for nothing on Omarchy, which masks
NetworkManager-wait-online.service, so ordering the retry after it still ran
apple/pacman.sh's repository sync before Wi-Fi was up, and everything queued
behind it (the Limine activation included) stayed undone. The drop-in now
runs nm-online -s first, as first-run's Wi-Fi step does; with nothing to
connect it returns at once.
@maralcbr
maralcbr merged commit 4198945 into quattro-upstream Sep 25, 2026
5 checks passed
maralcbr added a commit that referenced this pull request Sep 25, 2026
Owner provisioning and its journal test take quattro-upstream's version,
which already carries #540 and ticket 32. The dispatch contract keeps both
sides; boot-rebuild stays optional until ticket 36, since the update path
does not call it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant