Skip to content

Converge omarchy-mac and omarchy-mx-mac into upstream Omarchy - #13362

Open
maralcbr wants to merge 73 commits into
quattrofrom
upstream/convergence
Open

maralcbr wants to merge 73 commits into
quattrofrom
upstream/convergence

Conversation

@maralcbr

@maralcbr maralcbr commented Sep 26, 2026 •

Copy link
Copy Markdown

This is the generic part of the Apple Silicon work in omacom/omarchy-mac, plus fixes Snapdragon and x86 need too, as one PR in place of the eight smaller ones we had lined up. It has 48 commits grouped by topic. Each one adds no test failure over the baseline given under Testing, so it can be reviewed and bisected commit by commit. On x86 every new path is a no-op or behaves exactly as today, except for the changes listed under What changes on x86.

Why

aarch64 now covers Snapdragon laptops (dragon), Apple Silicon Macs (omacom/omarchy-mac), Raspberry Pis and VMs, so uname -m can no longer pick platform setup. Macs also boot through a chain (m1n1 → U-Boot → Limine, with the install key on a separate boot partition) that first boot, factory reset and omarchy update can't drive through the Limine UKI path. Until now omarchy-mac carried Mac branches inside those flows. This PR gives Omarchy one detector and a small, fixed set of lifecycle hooks that a platform's boot package implements. Omarchy keeps no Mac code; the Mac side ships as packages.

Along the way we found and fixed generic bugs in the encrypted first-boot path: the LUKS parent lookup broke on lsblk tree glyphs, an interrupted re-key could lock setup out or leave the disk unlocking unattended, and omarchy-drive-password left the login and root passwords behind.

What changes on x86

Everything else in this PR is a no-op on x86 or read-only there. These change behaviour on purpose:

  • Disk encryption fixes. The LUKS parent lookup (owner setup and factory reset stopped on lsblk tree glyphs), the journaled first-boot re-key, the factory-reset passphrase check ignoring tokens, and omarchy-drive-password: on the system disk it now changes the login and root passwords with the disk's, as setup gave all three one password, and says so before it asks for anything. LUKS1 drives can change their password again, and the drive list comes from lsblk, so it no longer says there are no encrypted drives until root has run blkid.
  • mkinitcpio. The HOOKS line moves to its own early drop-in (same resulting initramfs). thunderbolt is early-loaded only where the kernel builds it as a module, as on every x86 kernel today.
  • Keyboard layout at first-boot setup (deferred installs). A layout that doesn't take (loadkeys failing on a console, a keymap localectl doesn't list, or an /etc/vconsole.conf that doesn't read back with it) is asked for again instead of being ignored, so the disk password is never set in one layout and asked for in another. After three failures in a row, setup's own retry-or-console screen takes over. The first-boot re-key rebuilds the boot files again if the layout changes after it.
  • First run. A failed user finalization at first login keeps first run pending for the next login instead of being ignored.
  • Deferred installs (OEM, set up by the owner on first boot) set the wireless regulatory domain from the owner's timezone; before, it stayed on the world domain because the hardware step ran on the UTC placeholder.
  • Section 6. Brave links the system Widevine CDM on the rare x86 machine with the widevine package; a failed setup step also prints its error on stderr.
  • Section 7. Laptops with an ambient light sensor and a keyboard LED get automatic keyboard backlight (enabled for existing installs by migration 1789135842), unplugging a monitor keeps your workspace in front, a display logind doesn't count (USB-C, DVI-A, Composite) holds the lid switch, the recording's finalize pass writes 48 kHz audio instead of 96 kHz, and omarchy-reinstall-pkgs finishes after skipping a default no repository offers (it prints each).
  • Section 9. Batteries are found by type, not by a BAT* name: a machine whose battery is named otherwise (Framework's CMB0, for one) now gets the battery panel and the laptop-only services, and battery status reads its thresholds and cycles. Battery presence ignores a peripheral's battery. Battery status picks the battery UPower marks as the power supply, shows the magnitude of a negative power_now (kernels that sign it by direction), and rounds the percentage half-up for display instead of truncating it. A machine without an ACPI lid but with an input device reporting a lid switch counts as a laptop, and its lid state comes from logind (ACPI lids read as before). omarchy-hw-touchpad still takes a device named touchpad or trackpad first; only when there is none does it take a mouse udev marks as a touchpad, so a touchscreen's multi-touch mouse interface is never picked.
  • Section 17 (audio panel). An input whose ports are all unavailable (an empty headset jack) is no longer listed unless it's the default, as outputs already were. Choosing an input no longer moves a filter's own capture (EasyEffects, an echo canceller), which fed the filter from the new input or from itself. The shell's own level meters no longer light the microphone widget. A virtual default input, such as EasyEffects' source, shows and sets its real volume and has a working meter instead of reading 0% and muted. pactl is read in the C locale for sinks too.
  • Section 10. Steam installs on a machine with no Intel, AMD or NVIDIA GPU instead of failing at the 32-bit driver step.
  • Section 12. omarchy-refresh-pacman, omarchy-channel-set and install finalization ask omarchy-hw-platform first and stop before changing anything if it can't place the machine; x86_64 then writes the same templates as before.

How this relates to omarchy-mac

omarchy-mac's integration branch, quattro-upstream, carries this generic slice plus the Apple-only content, which stays in omarchy-mac. Once this merges, quattro-upstream takes the slice from upstream quattro and keeps only the Apple packages on top. The branch lives on omacom/omarchy-mac (a fork of this repo) so it can be rebased there alongside that work.

What's in it

1. Platform detector and package lists (4 commits)

  • bin/omarchy-hw-platform prints apple-silicon, qualcomm, generic-aarch64 or generic. It reads the vendor prefix of each token in the device tree's root compatible (apple, / qcom,, from /proc/device-tree, then /sys/firmware/devicetree/base) and the CPU, and fails instead of guessing when the evidence contradicts itself. bin/omarchy-hw-apple-silicon is a predicate on top of it, for the Mac packages to call.
  • An image built away from its hardware names its target in a root-owned manifest, /var/lib/omarchy/image/target (format=1, platform=). While a root is being built rather than booted (no /run/systemd/system, PID 1 rooted elsewhere, or PID 1 not systemd) the detector answers from it. A booted system always answers from its hardware. As root the detector restarts under env -i with PATH=/usr/bin and bash -p.
  • test/shell.d/architecture-gates-test.sh fails on a new uname -m platform gate in shipped code that isn't on its reviewed list (ABI, binary availability, a repository's $arch).
  • bin/omarchy-pkg-defaults [platform] composes the default set: omarchy-base.packages, then install/omarchy-aarch64.packages (zram-generator) on aarch64, then install/omarchy-<platform>.packages when there is one (omarchy-qualcomm.packages: linux-firmware-qcom; Apple Silicon's is in section 11). omarchy-reinstall-pkgs installs that set. x86_64 gets exactly the base list, as before, and reinstall stops before touching packages if the platform can't be told.
  • thunderbolt_module.conf adds thunderbolt only where modinfo finds it as a module for the kernel being built: linux-aarch64 has no such module, and mkinitcpio fails the whole image over a missing one. The optional thunderbolt? form isn't enough, since a systemd initramfs copies it verbatim into modules-load.d. x86_64 kernels build it as a module, so their images are the same. Co-authored with @scottjones.
  • Tested by: hw-platform-test.sh (real M1 Pro, M2 Max, M1 mini, Yoga Slim 7x, XPS 13 9345 and T14s trees, QEMU virt, a Pi, ACPI-only aarch64, x86, every contradiction, and agreement with dragon's omarchy-hw-qualcomm-soc on every aarch64 fixture), image-target-platform-test.sh (chroots, PID namespaces, bound /run, stale and hostile manifests, root ignoring BASH_ENV, exported functions, PATH and fixture roots), platform-packages-test.sh. Also run natively on aarch64, as a normal user and as root.

2. Composable boot and generic encryption (8 commits)

  • HOOKS baseline. The unconditional HOOKS=(...) line moves, unchanged, from omarchy_hooks.conf into a new etc/mkinitcpio.conf.d/00-omarchy-hooks.conf, which sorts first, so a numbered drop-in (90-*.conf) from a platform or hardware package can add hooks without being overwritten. omarchy_hooks.conf keeps the NVIDIA and vconsole handling. Migration 1786605598 now sources the baseline before reading HOOKS. Old against new drop-ins gave identical HOOKS, MODULES and FILES across 588 combinations. Two hand-edited setups behave differently: a user drop-in that sets HOOKS and sorts between 00- and omarchy_hooks.conf now takes effect, and a user who deleted the HOOKS line from their own omarchy_hooks.conf now gets the baseline.
  • Apple Silicon HOOKS. The baseline asks omarchy-hw-platform. Apple Silicon starts from the systemd line omarchy-mac-boot's drop-ins build on: its encryption drop-in reads busybox encrypt as a legacy cryptdevice= Mac and leaves such a line alone, so without this an encrypted Mac built an image with no sd-encrypt and couldn't unlock. A Mac whose own mkinitcpio.conf carries asahi or busybox encrypt keeps its line. x86, Snapdragon and generic aarch64 get exactly the busybox line, with or without the detector. Without the runtime's detector, the baseline uses the copy omarchy-settings ships for the guard; a detector that can't place the machine stops the build.
  • LUKS parent lookup. lsblk -s printed tree glyphs, so owner setup and factory reset stopped with "Could not locate the LUKS device to re-key". It now reads lsblk -r. The lookup test is Scott Jones's (@scottjones), extended to LVM on LUKS.
  • Factory reset passphrase check uses --token-type passphrase-only, so an enrolled TPM2 or FIDO2 token can't approve a wrong passphrase.
  • Journaled first-boot re-key (install/provisioning/luks-rekey.sh). The staged-key → owner-password re-key used to run in one pass with no record of progress. Killed after the staged slot was retired, every retry failed. With luks-key missing, setup could finish with the disk still unlocking unattended. It now runs as a journaled sequence: record the staged slot, add the owner's key, rebuild boot without the auto-unlock, retire the other slots, verify, destroy the staged key. The journal holds phases and slot numbers only. The staged key is destroyed only once it provably opens nothing. LUKS1 slots are retired too, and key checks ignore LUKS2 tokens.
  • omarchy-drive-password on the disk holding / checks the current password, changes the LUKS key, confirms the new key opens the disk and the old one doesn't, and only then sets the login and root passwords (setup gives all three one password). A journal lets the next run finish an interrupted change. Other drives change their LUKS key only. --pbkdf argon2id is now passed only to LUKS2, so LUKS1 drives can change their password again. The manual's security page says so.
  • Tested by: mkinitcpio-hooks-test.sh and nvidia-kms-hook-test.sh (the drop-ins composed the way mkinitcpio does, for plain, NVIDIA, hybrid, T2, SPI MacBook and Surface machines, on every platform and on legacy Mac lines, plus the migration), mkinitcpio-apple-drop-ins-test.sh (the baseline with omarchy-mac-boot's real drop-ins, as fixtures, gives exactly the HOOKS an encrypted M1 Pro boots today), luks-parent-detection-test.sh, luks-rekey-journal-test.sh and drive-password-test.sh. The last two kill the flow after every durable step and rerun it with the old or new password, against a slot-table fake and against real file-backed LUKS2 and LUKS1 volumes.

3. Lifecycle dispatch (4 commits)

  • bin/omarchy-lifecycle-dispatch <operation> calls a fixed set of operations: provision-prepare, provision-commit, provision-verify, reset-prepare, reset-verify, reset-commit, reset-rollback, update-verify, luks-slots (and migrate, below, and the setup and app operations in sections 8 and 10). Registration is code: apple-silicon → /usr/lib/omarchy/mac-boot, shipped by omarchy-mac-boot. generic, generic-aarch64 and qualcomm register nothing, so every call is a no-op there. A required operation with no entrypoint fails and names the package. As root it runs under bash -p with a fixed PATH and trusts only root-owned, non-symlinked entrypoints in root-owned directories, run under env -i.
  • Callers:
    • First-boot owner setup runs provision-prepare, and uses provision-commit/provision-verify where the platform has both (the Limine UKI callbacks otherwise), and luks-slots before the staged key is destroyed. On x86 there's no new screen.
    • Factory reset stages and verifies the platform's boot files before adding the throwaway slot, and rolls back on failure.
    • omarchy update runs omarchy-update-boot (update-verify) after AUR packages.
  • Placement after Ask for the sudo password once per omarchy update #13323. Verify is the last sudo-capable step, after AUR, so it also covers initramfs rebuilds triggered by AUR packages. Because it follows third-party build code, it starts from a revoked timestamp and goes through the no-update wrapper like AUR does. A failed verify finishes the remaining steps and exits 1 without offering a reboot. omarchy-update-boot resolves as the user first, so nothing asks for root where the platform has nothing to run. On a platform that implements update-verify without passwordless sudo, that's one more prompt. If you'd rather verify inside the shared authorization before AUR, it's a one-line move, at the cost of not covering AUR rebuilds.
  • There is no update preflight and no boot-rebuild operation: no platform implements either, so they are left for the platform that needs one. Without a preflight, a machine whose platform can't be told gets its packages and then fails verification, without the reboot prompt. After a factory reset left stale boot entries, owner setup runs limine-update on every platform, as before.
  • docs/lifecycle-dispatch.md is the contract, including how a Qualcomm boot package would plug in.
  • Tested by: lifecycle-dispatch-test.sh, luks-rekey-journal-test.sh (the x86 path, and an Apple fixture with a fake boot package), drive-password-test.sh, factory-reset-dispatch-test.sh and update-boot-verify-test.sh (inside the sudo boundary fixture: no root and no prompt on x86, generic aarch64 and Qualcomm).

4. Migration caller (2 commits)

  • migrations/1790347292.sh resolves the new optional migrate operation and, when the platform's boot package implements it, runs sudo omarchy-lifecycle-dispatch migrate. Macs installed before the official packages move onto them this way, inside omarchy-mac-boot. Everywhere else nothing resolves, so it completes without running anything or asking for root. An undetermined platform stays pending. A root-owned marker spares other accounts a second run. The filename matches the one Mac testers already run from omarchy-mac.
  • A migration that exits 75 (EX_TEMPFAIL) is deferred: it changed nothing and can't finish yet, so omarchy-migrate leaves it pending with its login notification, runs the migrations after it and exits 0, and omarchy update carries on. Any other failure still stops the queue. Migration 1790347292 passes a deferred refusal from the platform's entrypoint through, and defers itself when the platform can't be told, so the update still reaches verification.
  • Tested by: platform-migration-test.sh, with a stub and with the real dispatcher on every platform fixture, and migrate-deferred-test.sh.

5. Pacman platform guard (1 commit)

  • A platform package declares groups=('omarchy-platform-<platform>'). On aarch64, omarchy-settings ships 00-omarchy-platform-guard.hook, the guard script and a copy of the detector (/usr/share/libalpm/{hooks,scripts}/), so the guard is resident before the runtime or any platform package. Before each install or upgrade it reads tags from the synced databases and aborts a transaction that would install a package tagged for another platform. A transaction with no tagged package passes without asking the detector, and an unreadable database is reported and skipped, so it can't wedge pacman. No platform package is built for x86_64, and pacman refuses another architecture's package there anyway, so the x86_64 omarchy-settings ships none of the three files (omarchy-settings: ship the full set on aarch64 for runtime-profile sources omarchy-pkgs#638 and Boot loop after update #691): x86 transactions run no hook and pay nothing. (Measured before that change, one guard run on x86 took about 0.36 s.)
  • Hardware setup's first step (install/hardware/platform-guard.sh) checks the guard is resident and audits what the installer already placed. On generic aarch64 a missing guard is logged, not fatal; on x86 it is expected and says nothing.
  • The omarchy-settings and -dev recipes in omacom/omarchy-pkgs already install the three files when the source has them (omarchy-settings: ship the pacman platform guard omarchy-pkgs#636), on aarch64 only since Make Chaotic-AUR optional during installation #638 and Boot loop after update #691, and config-test.sh checks that. docs/platform-guard.md is the contract.
  • Tested by: platform-guard-test.sh (fixtures for all four platforms, image builds, invalid manifests, a masked hook, and fresh-install ordering), config-test.sh against omarchy-pkgs master, and on the x86 VM below.

6. Generic fixes (3 commits)

7. Generic helpers from omarchy-mac (6 commits)

Helpers omarchy-mac grew for aarch64 that any machine can use, one commit each with its callers and tests. None of them adds an Apple branch.

  • Install menu availability. omarchy-pkg-available checks the sync databases (one pacman -Slq per menu batch, pacman -Sp for a provided name or a constraint). Each Install row gets a when: naming every package its install command adds, derived from the recipes, so a row with nothing to install on this architecture hides instead of failing mid-transaction. AUR-built browsers and NordVPN are guarded on the architectures their vendors ship. omarchy-reinstall-pkgs reports and skips a default no configured repository offers. All 47 package-guarded rows resolve against x86_64 stable and edge today, so the x86 menu is unchanged. While any configured repository has no sync database yet (a fresh ISO install has only its offline one until the first update), availability is unknown, so every row shows as before and reinstall skips nothing. Co-authored with @ryanrhughes.
  • Software GL without a render node. omarchy-hw-render-gpu, omarchy-cmd-electron-gl-args, omarchy-cmd-electron-gl-wrap (a marked /usr/local/bin wrapper that adds --ozone-platform=wayland --disable-gpu only while there is no renderD* node, refuses to replace a launcher it didn't write) and omarchy-cmd-desktop-exec-repair (points the user's desktop entry at the wrapper, keeping arguments and customizations). The 1Password installer uses them only where there is no render node (VMs, headless GPUs, Apple M3 and later). Co-authored with @scottjones.
  • Screen recording fallback. When gpu-screen-recorder is missing or exits before the file appears (it aborts on GPUs it doesn't know, such as Apple Silicon's), recording falls back to wf-recorder if installed. Stop and status use the recorder's pid through omarchy-capture-screenrecording-process, which matches by executable and signals through a pidfd, instead of pgrep -f ^gpu-screen-recorder; the bar indicator and the Stop row use it too. The finalize pass pins 48 kHz (the same line as Pin recording audio sample rate at 48 kHz #11092; whichever lands second drops it). Co-authored with @scottjones and @ijt.
  • Lid inhibitor. logind counts a closed lid as docked only for connector types on its list, which lacks USB (Apple Silicon's USB-C displays) and fuses DVI-A with Composite. omarchy-system-lid-inhibit, run by the monitor watcher, holds a handle-lid-switch inhibitor while such a display is connected and enabled, where HandleLidSwitchDocked is ignore. It changes nothing when Hyprland or logind doesn't answer, and it is bound to the watcher's unit.
  • Workspace on monitor removal. default/hypr/monitor-removal.lua refocuses the workspace you were on when its monitor is unplugged or the lid turns the panel off (Hyprland warps focus first and moves the workspaces after, so yours ended up hidden).
  • Keyboard backlight from ambient light. On laptops with an ambient light sensor and a keyboard LED, a user unit lights the keys in the dark and dims them as the room brightens; the keyboard-brightness keys win until the light changes enough. The unit's ExecCondition makes it a no-op without both. First run enables it, and migration 1789135842 enables it for existing installs. That migration keeps the name it has in omarchy-mac, so Macs that already ran it skip it; it sorts before newer migrations on purpose. Co-authored with @scottjones and @6abe.
  • Tested by: optional-availability-test.sh, optional-packages-test.sh, optional-transactions-{x86_64,aarch64}-test.sh, menu-guards-test.sh, menu-test.sh, platform-packages-test.sh, electron-gl-test.sh, install-1password-software-gl-test.sh, screenrecording-test.sh, screenrecording-process-test.sh, screenrecording-sample-rate-test.sh, lid-inhibit-test.sh, hyprland-monitor-removal-test.sh, brightness-keyboard-auto-test.sh, systemd-test.sh.

8. Image builds and platform setup (3 commits)

Prebuilt images need what the ISO does at install time done on the machine instead, and a platform package needs one place to hook its own setup. Image-build deferral never runs on an ISO install; the setup seam runs on every install.

  • Image-build deferral. An image is built away from its hardware, so hardware setup would probe the builder. While the image manifest the detector already reads (/var/lib/omarchy/image/target) exists, omarchy-apply-hardware queues every leaf of install/hardware/all.sh instead of running it and arms omarchy-provision-hardware.service. On the first boot, before owner setup and the login screen, omarchy-provision-hardware retires the manifest to target.booted and runs the queue on the real hardware. A failed step stays queued for the next boot (exit 75), and the initramfs is rebuilt once after the last step when a step changed the mkinitcpio configuration or the Limine command line, or asked for it. Bluetooth starts its service on that boot, since its target is already up. install/helpers/image-target.sh holds the contract; root ignores any environment override of its paths. Nothing on that boot fetches package databases: an image carries its sync databases and every package its steps install, since a Mac has no network until the owner is on the desktop. Without a manifest, hardware setup runs exactly as before.
  • Locale. A root built from a distribution tarball (Arch Linux ARM ships LANG=C) never went through the ISO's locale step. The config phase selects en_US.UTF-8 only when LANG is unset, C or POSIX, and changes only that line. The ISO writes the locale first, so an ISO install is unchanged. Co-authored with @scottjones.
  • Platform setup seam. Three dispatch operations. setup-boot and then setup-system run as root from the last hardware leaf, so an image build queues it and its first boot runs it (with image-first-boot: possibly offline, and asking for the one rebuild instead of building). setup-user runs as the user being set up, refuses root and gets only a fixed PATH, HOME, USER, XDG_CONFIG_HOME, XDG_STATE_HOME, XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS, WAYLAND_DISPLAY and OMARCHY_PATH (the two XDG directories from section 9); it runs from the last user leaf at finalization (skipped inside an image build, where the detector would read the build host) and again as a first-run step once the session is up. On Apple Silicon setup-boot resolves in /usr/lib/omarchy/mac-boot and is required: the Mac's boot setup (GRUB console, Limine activation) has no other path, so a boot package without it stops the leaf. setup-system and setup-user resolve in /usr/lib/omarchy/mac, shipped by omarchy-mac, and are optional. Every other platform registers none, so all three are no-ops. docs/lifecycle-dispatch.md has the contract. With it comes @scottjones's first-run fix: a failed finalization now keeps first run pending instead of letting the later steps mark it complete.
  • Tested by: image-deferred-hardware-test.sh (queueing, a first boot racing a rerun, refused manifests, replay after a failed step, one rebuild for mkinitcpio or Limine command-line changes or a request, no database fetch, Bluetooth, the keyring an image build defers to the first boot, root ignoring fixture roots), locale-setup-test.sh, lifecycle-dispatch-test.sh, platform-setup-test.sh (both leaves and first run through the real dispatcher, setup-boot before setup-system, and a Mac whose boot package lacks it) and first-run-test.sh.

9. Hardware without PC names, platform desktop defaults, quirks (6 commits)

omarchy-mac carried Apple Silicon lines in files Omarchy already has. Here detection becomes generic, Mac-only desktop defaults get generic places a platform package fills (so they live in omarchy-mac), and the PC and Intel Mac quirks that misfire on Apple Silicon skip it behind omarchy-hw-apple-silicon, like the other omarchy-hw-* predicates in hardware setup.

  • Detection. Battery presence goes by type, not a BAT* name, and ignores a peripheral's battery (scoped Device); battery status takes the first UPower battery that powers the machine, reads its own thresholds and cycles (BAT* stays the fallback), shows the magnitude of a signed power_now, and rounds the percentage half-up like the bar while the charge-hold check keeps the raw value. A laptop is also one with any input device reporting a lid switch (SW_LID), whatever its name; whether the lid is closed comes from ACPI where there is an ACPI lid, and otherwise from logind (a 2 s timeout, since the check also runs inside sudo's PAM stack where fingerprint login is set up). A device named touchpad or trackpad wins; otherwise the touchpad is a mouse udev marks as a touchpad (Apple's multi-touch trackpad), and the backlight and external-monitor brightness follow a platform's displays.conf (backlight nodes to skip or prefer, and whether an external monitor needs its connector's DDC channel), so Omarchy names no Mac devices; without the file they behave exactly as before. A Mac's file skips the Touch Bar nodes, prefers apple-panel-bl and spares the 3-second DDC probe over the SoC's SMBus.
  • User setup keeps XDG_CONFIG_HOME and XDG_STATE_HOME. Section 8's user operations dropped them, so a platform's user setup looked in the default directories for a user who sets either.
  • Platform root. Platform files live in a fixed /usr/share/omarchy-platform/, which the one installed platform package owns and Omarchy ships nothing into, read by literal path (no environment variable). Missing files mean no platform additions.
  • Hyprland defaults from a platform package. Three directories under the platform root. hypr/defaults/*.lua loads before Omarchy's defaults: a chord bound there replaces Omarchy's default for it, and a decorator added to o.bind_decorators runs before every later bind, so it can bind what must run first. A decorator sees the command a bind runs, including the one a shell shortcut such as { menu = "root" } stands for when it binds the shell's global shortcut. hypr/settings/*.lua loads after Omarchy's defaults and before the theme and the user's files. A platform sets there through hl.config anything a user might set globally, since Hyprland lets an hl.device value win over the user's global one, and keeps hl.device for what applies to that device alone. hypr/gestures/*.lua loads after the user's files, only for gestures: the gesture registry, which now records on every platform and knows every direction alias Hyprland accepts, lets a default gesture step aside for the user's own. The user's files can unbind or rebind anything set in the first two. Each platform file loads by path under pcall (never through package.path, so it can't shadow a module), and a failing file or decorator is reported and skipped, so Omarchy's defaults and the user's files still load.
  • Key names in the keybindings menu. key-names in the platform root (<keysym> <name> lines) renames a key where the keyboard prints something else, in the key column only (a MacBook's XF86MonBrightnessUp is F2). It is part of the menu cache's key. The menu's own config scan runs lua -E, so nothing in the user's environment reaches it.
  • The bar and a camera cutout. A platform package describes panels with a notch in display-cutouts.json in the platform root (connector, physical mode, rows covered). On a described panel a top bar is never shorter than the cutout, its center section sits beside the right one, and hiding it parks all of it; [bar] notch-height sets the floor by hand on that panel only.
  • Quirks. The Intel Mac Wi-Fi quirk (firmware supplicant off) skips Apple Silicon, where U-Boot reports DMI vendor Apple and the M1 Pro/Max's BCM4387 is on the list but the firmware supplicant is the one that works; its migration skips it too. fnmode=2 for Apple-like keyboards is left to the Mac's platform package there. The SPI keyboard check survives a missing DMI product name. The Windows VM (an x86_64 KVM guest) and direct boot (U-Boot makes its own boot entries) refuse Apple Silicon and the menu hides both; hibernation setup skips it.
  • Tested by: battery-present-test.sh, battery-status-test.sh, hw-laptop-test.sh (lid capability words, a Mac mini's SMC device without a lid), hw-touchpad-test.sh, hw-display-test.sh, brightness-display-test.sh, lifecycle-dispatch-test.sh, hyprland-platform-defaults-test.sh (fixture platform files: early binds replacing a default in any spelling, a decorator's bind running first and not recursing, a decorator picking a global-shortcut menu bind by its command, the user's rebind and unbind winning, default bindings off, a late gesture stepping aside, the development-checkout path), menu-keybindings-key-names-test.sh (names in the key column only, never in what a row runs), bar-notch-test.sh, bar-test.sh, apple-silicon-quirks-test.sh, brcmfmac-supplicant-test.sh, spi-keyboard-test.sh, menu-test.sh. Every case says which platform it runs on, and tests never read a machine's installed platform files.

10. Platform hooks for app installs and update takeovers (2 commits)

omarchy-mac's inline branches in installers and the update were Apple policy, so they move onto the dispatch seam from section 8 and into omarchy-mac.

  • post-install <app> and pre-remove <app>. Optional user operations, like setup-user (refuse root, same session allowlist). Every browser install runs post-install <browser> after its flags file and before it says it's installed; Steam's install runs post-install steam after the package, and its removal runs pre-remove steam before its packages go. On a Mac they resolve in /usr/lib/omarchy/mac (browser flags for software video decode, Steam's FEX launcher, which depends on Steam and so must go first). Everywhere else they are no-ops. Steam's 32-bit driver step also stops failing the install on a machine with no Intel, AMD or NVIDIA GPU.
  • update-takeover <path>.... When an Omarchy package's upgrade stops only on files no package owns, the update asks the platform's boot package before moving them aside for the retry. It is resolved as the user and run with sudo only where it resolves. It's required on Apple Silicon (omarchy-mac-boot): a Mac's boot chain and image write files no package owns, so a Mac whose boot package can't vouch for the takeover keeps them and the update stops as it did before the retry existed. Nothing resolves elsewhere, so the takeover goes ahead as before.
  • Tested by: platform-app-hooks-test.sh (every browser, Steam install and removal through the real dispatcher on Apple and x86 fixtures), lifecycle-dispatch-test.sh, update-file-conflict-test.sh (a vouched, a refused and an unanswered takeover on Apple; x86 asking no one).

11. Mac package set (1 commit)

  • Apple Silicon package list. install/omarchy-apple-silicon.packages, which omarchy-pkg-defaults already composes after the base and aarch64 lists: omarchy-mac and omarchy-mac-boot (whose entrypoints the dispatcher requires on a Mac), plus defaults an owner may remove: the startup-volume tool, video decode, Widevine, and wf-recorder, since gpu-screen-recorder can't capture on Apple Silicon (its display and GPU are separate DRM devices) and section 7's fallback records with it. What a Mac can't work without (its speaker stack, PipeWire's PulseAudio server and realtime scheduling, the Vulkan driver) comes as omarchy-mac's dependencies, so a new Mac driver needs no Omarchy change.
  • Tested by: platform-packages-test.sh.

12. aarch64 repositories, keys and zram (3 commits)

  • Channel changes keep an ARM machine's repositories. omarchy-refresh-pacman (and so omarchy-channel-set) wrote the x86_64 templates everywhere, so on aarch64 it replaced Arch Linux ARM and a platform's repositories with Arch's x86 ones. It now asks the platform. x86_64 runs exactly the same copies as before. On aarch64 the machine's pacman.conf, mirrors and signature policies stay, and only the [omarchy] server moves to the new channel (dragon's approach); a configuration whose Omarchy server can't be identified is refused unchanged, and one with no Omarchy repository gets it at the end (except on Apple Silicon, whose own repository must not come first, or where an included file already defines it). Only pacman.conf is backed up and rewritten, piped from the command straight to sudo tee, never through a file another process could change.
  • Qualified channels. A channel must be qualified for the platform: a signed upgrade and a reboot tested there, not just a published repository. x86_64 keeps stable, rc and edge; Snapdragon and other aarch64 machines take edge, as dragon does; Apple Silicon takes none until the Mac packages qualify on edge, which is then a one-line change in install/helpers/pacman.sh. Before touching /etc, the refresh checks as the user that the channel's omarchy.db publishes the runtime pair, and on Apple Silicon omarchy-mac and omarchy-mac-boot. omarchy-channel-set refuses an unqualified channel before anything else, the dev confirmation included; the dev checkout is still linked before the refresh, as today, and on aarch64 a checkout from before this PR is refused. An unreadable pacman.conf is refused, not taken for one with no Omarchy section.
  • Install finalization writes a whole template, as before on x86_64. Other aarch64 platforms get dragon's pacman-aarch64.conf and mirrorlist-aarch64 (byte for byte), with Arch Linux ARM's keyring installed and populated first (in an image build, on the machine's first boot, so machines flashed from one image don't share a pacman master key), plus Omarchy on a qualified channel, and a message saying why when the channel isn't qualified there. Such a machine also says so in omarchy update's confirmation, omarchy-refresh-pacman and omarchy-channel-set, pointing to omarchy-channel-set edge. On aarch64, omarchy-reinstall-pkgs refreshes on the channel the machine's configuration names, or keeps its repositories where that channel isn't qualified. Apple Silicon gets omarchy-mac's template (/usr/share/omarchy-mac/pacman) on a qualified channel and keeps its image's configuration otherwise.
  • Arch Linux ARM's keyring. omarchy-update-keyring also reinstalls archlinuxarm-keyring on aarch64 platforms where it's installed, so a rotated ALARM key doesn't fail the upgrade after it. x86_64 is unchanged, even with that package installed.
  • zram on existing aarch64 machines. Migration 1790328426 installs zram-generator where the platform's default packages name it (aarch64 only; x86_64 gets it from its installer) and starts the zram swap when it's down, unless no device is configured or the unit is masked. A failed install defers it (75) to the next update; a swap that won't start only warns, since its unit starts on every boot, so it never stops an update. An undetermined platform defers it too. It keeps the name it has in omarchy-mac, so Macs that ran it skip it, and retires an /etc copy of the drop-in identical to the packaged one.
  • Tested by: pacman-arm-channel-test.sh (the gate per platform; keeping every repository, mirror, signature policy, look-alike server and trailing line; refusing unknown, included, duplicated or missing Omarchy servers; appending, but not on a Mac or through an include; each platform's finalization and that a channel change reads it; the preflight against a fixture omarchy.db; the refresh through the sudo boundary fixture: refusals before anything else, the order check, backup, write, cold hook, upgrade, and a failed check changing nothing), channel-sudo-boundary-test.sh (unqualified channels refused before any change), update-keyring-test.sh, zram-aarch64-migration-test.sh. The Thunderbolt modinfo form is in section 1, tested by mkinitcpio-hooks-test.sh.

13. omarchy-settings on every architecture (1 commit)

  • default/settings-runtime-profile tells the omarchy-settings recipe (omarchy-settings: ship the full set on aarch64 for runtime-profile sources omarchy-pkgs#638) that this source's platform files decide at runtime, so it can ship the same files on aarch64 instead of stripping the mkinitcpio, Limine, memory and USB drop-ins. It lists each file and what it decides from: the HOOKS baseline asks the platform, Thunderbolt the kernel (section 1), the Limine files are read only where Limine is installed, the oomd drop-ins are inert without systemd-oomd, aarch64 installs the zram generator the zram drop-in needs (section 12), and a kernel that builds usbcore in ignores the USB autosuspend option. Older sources keep today's aarch64 package.
  • Tested by: settings-runtime-profile-test.sh (every listed file exists, and each runtime decision is in place), config-test.sh against omarchy-pkgs master.

14. Wireless regulatory domain at first-boot setup (1 commit)

  • A deferred install (an Apple Silicon image, an OEM ISO install) runs its hardware steps on the UTC placeholder, so install/hardware/set-wireless-regdom.sh left the regulatory domain unset. Owner setup now runs that step again once the owner's timezone is set, and applies it to the radios through wireless-regdb's set-wireless-regdom, since no reboot follows setup. An up-front install is unchanged.
  • Tested by: provision-owner-regdom-test.sh.

15. Keyboard layout at first-boot setup (1 commit)

  • Owner setup's keyboard step fails closed. On a console loadkeys must succeed, the keymap must be in localectl's list exactly, and after systemd-firstboot or the localectl fallback, /etc/vconsole.conf is read back in a clean shell and must name the keymap with an XKB layout. A failure is logged, the owner is told, and the layout is asked for again; after three in a row the attempt fails and setup's retry-or-console screen takes over, so a machine that can't set any layout doesn't loop. The journaled re-key (section 2) now records a fingerprint of the layout lines and rebuilds the boot files when it changes, including after a completed re-key.
  • Tested by: provision-owner-keyboard-test.sh, and luks-rekey-journal-test.sh for the fingerprint.

16. Platform keyrings (1 commit)

  • A platform package can list its repository's keyrings in /usr/share/omarchy-platform/keyrings, one package name per line (asahi-alarm-keyring on Apple Silicon). omarchy-update-keyring reinstalls each one that is installed, in the same transaction as Arch's. It adds no repository the machine didn't already trust, and a line that isn't one keyring package name is skipped with a warning. Without the file, as on x86, nothing changes.
  • Tested by: update-keyring-test.sh.

17. Untyped inputs in the audio panel (1 commit)

  • Quickshell types only exact media classes, so a virtual source (Audio/Source/Virtual: EasyEffects' source, or a platform's microphone mapping) had no volume, meter or mute in the shell. Such an input now goes through wpctl (shell/Commons/UntypedInput.qml, one pactl subscribe for the whole shell), is metered by the new omarchy-audio-source-level, and is listed when PulseAudio lists it as a source.
  • A platform can name processing nodes that are neither devices nor apps in /usr/share/omarchy-platform/audio.json (hidden, or replaced while a replacement exists). The panel and microphone widget leave them out, except the current default. Omarchy ships none.
  • The changes on every machine are under What changes on x86.
  • Tested by: audio-test.sh, audio-input-set-default-test.sh, audio-source-availability-test.sh, audio-source-level-test.sh.

Testing

  • ./test/all as a normal user in a native x86_64 Arch container (archlinux:base-devel, cryptsetup, omarchy-pkgs master checked out for the packaging checks), on quattro e1614f2 and on each of the first 20 commits: every commit fails the same 8 shell files and the same test/cli checks as quattro, and nothing else. Those need an installed system, mise, an omarchy-iso checkout or namespaces the container lacks. Each commit's new tests pass at that commit.

  • The last two commits (migration deferral, Apple HOOKS) against commit 20, with ./test/all as a normal user on an installed x86_64 Omarchy machine: all three fail the same 12 shell files and the same test/cli check, and each commit's new tests pass. The HOOKS commit's tests also pass natively on aarch64, as a normal user on an M1 Pro, where its shell suite fails no file that its parent passes.

  • x86 VM (KVM, OVMF, ISO built from this branch with omarchy-iso, installed through the configurator): the ISO was built from an earlier state of this branch whose tree differs from the head only in test/shell.d/mkinitcpio-hooks-test.sh.

    • Encrypted install: omarchy-hw-platform says generic, and every dispatch operation resolves to nothing. HOOKS compose from 00-omarchy-hooks.conf and mkinitcpio -P succeeds. omarchy update -y asks for the sudo password once and finishes, with the boot checks running nothing. omarchy-drive-password moves the disk, the login and root to the new password: the old one is refused and one key slot is left.
    • Platform guard: omarchy-settings owns the hook, the script and the detector copy. The installer put the settings package in its fourth transaction, after a first pacstrap with no platform package and before the runtime and the base list, and the hook ran on every later transaction. Hardware setup's guard step passed. With real pacman, a locally built package tagged omarchy-platform-apple-silicon was refused ("This machine is generic") and an untagged one installed. With core, extra, multilib and the offline repository synced, one guard run takes about 0.36 s.
    • Encrypted install with first-boot owner setup: the disk ends on the owner's password alone, with no staged key, unlock file or cryptkey= left, and no recovery-key screen. Factory reset on that machine staged through the generic path (the passphrase check now ignores tokens), rebooted unattended into owner setup, re-keyed to one slot, and the next boot asked for the new password.
    • The in-guest shell suite fails only the files that also failed in the same VM setup for the lifecycle-dispatch branch alone, plus the HOOKS test, which the last fix to it (checked in the guest) makes pass. Acceptance fails the same three checks (root menu, weather, emoji picker) as that earlier run, a known race with the idle lock.
  • Earlier, on the separate branches: the detector, image-manifest and package-list tests natively on aarch64 (an Arch Linux ARM container), as a normal user and as root.

  • The six helper commits (section 7): ./test/all as a normal user in a fresh x86_64 Arch container (archlinux:latest with the test tools, no ripgrep, cryptsetup or omarchy-pkgs checkout, so more files fail for want of tools than in the runs above), on commit 22 and on each of the six: every commit fails exactly the same 26 shell files and test/cli check as commit 22 (all for missing tools: ripgrep, mise, gum, ssh-keygen, an omarchy-pkgs checkout, namespaces), and its own tests pass. Their Apple-relevant test files also passed natively on aarch64, as a normal user on an M2 Max where the render node, light sensor, keyboard LED and USB connectors the helpers look for are present, on the revision before the last review fixes. With that Mac's real sync databases the availability guards hide the 15 Install rows that have no aarch64 package there (RetroArch, Lutris, Heroic, Spotify and others), and each guarded row also resolved on x86_64 as above.

  • The five commits of section 8: ./test/all as a normal user in a fresh x86_64 Arch container (archlinux:latest with the test tools and cryptsetup, no mise, gum, ssh-keygen, namespaces or omarchy-pkgs checkout), on commit 28 and on each of the five: every commit fails exactly the same 25 shell files and test/cli check as commit 28, all for missing tools, and its own tests pass. Their Apple-relevant test files (16, the LUKS, dispatch, image and first-run ones included) also pass natively on aarch64, as a normal user on an M2 Max, at the last commit. Read-only on that Mac, the branch's dispatcher then resolved both setup operations to nothing (no omarchy-mac entrypoints yet); section 8's required setup-boot now needs an omarchy-mac-boot that ships it (omarchy-mac has it since Run the Mac's boot setup through the platform setup seam omarchy-mac#635).

  • The eight commits of sections 9 and 10: ./test/all as a normal user in the same x86_64 Arch container as section 8, on commit 33 and on each of the eight: every commit fails exactly the same 25 shell files and test/cli check as commit 33, all for missing tools, and its own tests pass. Their Apple-relevant test files (38) also pass natively on aarch64, as a normal user on an M2 Max that has omarchy-mac's current files installed. Read-only on that Mac, the branch finds the laptop, lid, battery, apple-panel-bl and apple-mtp-multi-touch, gives its 16" panel a 32 px floor with the cutout file omarchy-mac is to ship (none without it), and resolves post-install and pre-remove to nothing (no omarchy-mac entrypoints yet) and update-takeover to an error naming the omarchy-mac-boot update it needs.

  • The seven commits sections 11 to 13 had then (setup-boot and Thunderbolt have since moved into sections 8 and 1): ./test/all as a normal user in the same x86_64 Arch container, on commit 41 and on each of the seven: every commit fails exactly the same 25 shell files and test/cli check as commit 41, all for missing tools, and its own tests pass. Their Apple-relevant test files (19, the dispatch, image, LUKS, HOOKS, channel and sudo-boundary ones included, plus config-test.sh against omarchy-pkgs master) also pass natively on aarch64, as a normal user on an M2 Max. Read-only on that Mac, the branch places it on apple-silicon, refuses every channel for it, would leave its pacman.conf byte for byte on edge and change only the Omarchy server on rc, finds edge's omarchy.db publishing the runtime pair and both Mac packages, lists thunderbolt for its kernel, and resolves setup-boot to an error naming the omarchy-mac-boot update it needs.

  • This revision (after review: timezone toast dropped, update-preflight and boot-rebuild removed, setup-boot and the Thunderbolt modinfo form folded into sections 8 and 1, guard silent on x86): the runs above predate it, and the section 8 counts there are from when it had five commits. At the new head, every shell test file and test/cli in an Arch container as a normal user: every file that fails at the head also fails at the previous head (7fabb9b) there, all for the container (no omarchy-pkgs checkout, namespaces, an installed system); system-sleep-ownership-migration-test.sh and one test/cli theme check fail intermittently at both. Each of the commits then in the PR passes the test files it adds or changes, apart from those same container failures.

  • Round 2 (the disk recovery key dropped to match Omarchy's single password, and the second review's fixes folded into their commits): at the new head, every shell test file in an Arch container as a normal user fails only where the previous head (39a9909) fails there, and passes two fewer files, the two recovery-key tests that went with the feature. Each of the 44 commits passes the test files it adds or changes, apart from those container failures, with the root probes run under unshare. On an M2 Max, the touchpad and lid fixes were checked live, and so was what the notch fix reads (Quickshell's whole-number ratio and Hyprland's 3456x2234 mode); a fractional scale itself couldn't be set there.

  • Round 3 (the third review's fixes folded into their commits, plus the wireless regulatory domain at first-boot setup as a new commit): at the new head every shell test file in an Arch container fails only where the previous head fails there, and each of the 45 commits passes the test files it adds or changes. The fresh-install menu case runs real pacman on a root holding only offline.db.

  • Round 4 (the keyboard layout at first-boot setup, platform keyrings and untyped audio inputs, sections 15 to 17): at the new head every shell test file in an Arch container fails only where the round-3 head fails there, and each of the 48 commits passes the test files it adds or changes, apart from those container failures.

  • End to end at f22c43f (this head), x86: a fresh encrypted ISO install in a VM with a UK layout. The UK keymap is in the boot image, and a password with @ unlocks on the first try. One key slot, platform generic, every dispatch operation resolves to nothing, omarchy update gives no keyring warnings, omarchy-drive-password and the reboot unlock work, and all 47 Install rows show. In the audio panel an empty-jack input is hidden unless it's the default, a filter's capture stays put when choosing an input, the shell's meters don't light the microphone widget while a real pw-record does, and a virtual default input shows and sets its real volume with a moving meter. The in-guest suite fails only its known files.

  • End to end at f22c43f, Apple Silicon: a lab image with omarchy-mac 0.1.0-10 and omarchy-mac-boot 20260928-3 passes all eight VM scenarios on an M1 Pro. audio.json is in the image and omarchy update refreshes asahi-alarm-keyring. The M2 Max hardware install below is from the round-3 head.

  • End to end at df41281 (the round-3 head), x86: a fresh encrypted ISO install and a provisioned one in a VM. At first login, before any update or pacman -Sy, /var/lib/pacman/sync holds only offline.db, and all 47 guarded Install rows show, none hidden. The platform is generic and every dispatch operation resolves to nothing. Owner setup leaves one key slot. omarchy-drive-password finds the disk right after boot with the root blkid cache empty, the regulatory domain follows the timezone, omarchy update exits 0 with no failed units, and the in-guest suite fails only its 17 known files.

  • End to end at df41281, Apple Silicon: a lab image from this runtime with omarchy-mac 0.1.0-9 and omarchy-mac-boot 20260928-2 passes all seven VM acceptance scenarios on an M1 Pro (first boot, conversion, second boot, password change, update, snapshot restore, factory reset) and an offline first boot, with no failures. The two skips are the audio sink, since a VM has no Apple audio, and the restore itself, which the generic kernel refuses; the snapshot entry boots. The owner-only disk (one key slot) holds through conversion and factory reset, the password change works on the first try, and the regulatory domain follows the timezone.

  • End to end at df41281, Apple Silicon hardware: a full install on an M2 Max with omarchy-mac 0.1.0-9, omarchy-mac-boot 20260928-2 and the Aurora boot chain, through the installer app: remove the previous Omarchy (macOS gets all its space back and is the startup disk again), install on edge with encryption, Recovery handoff, first boot and first login. Lab acceptance passes 42 of 42 with no warnings: Limine in U-Boot's slot, a LUKS2 root with one key slot (the owner's) and no recovery key or boot-time key left, the regulatory domain set, and the expected services active.

  • End to end at b0d870c (the round-2 head): an x86 ISO install in a VM passes. An Apple Silicon lab image built from this runtime passes its VM scenarios (first boot, conversion to an owner-only key slot, second boot) apart from the drive listing and the regulatory domain, both fixed in round 3. The Mac hardware install is still to run on this head.

Risk and rollback

  • x86 behaviour changes only as listed under What changes on x86. Everything else is a no-op on x86 (dispatch, including the app hooks under a manual sudo install; the boot, system and user setup operations; the update's boot check; migration 1790347292; the 1Password wrapper with a render node; the recording fallback where gpu-screen-recorder works; the lid inhibitor without USB, DVI-A or Composite displays; image-build deferral without an image manifest; the locale step after the ISO wrote one; in sections 9 to 12 every Apple Silicon and aarch64 guard, the platform directories and files (empty or absent without a platform package), the two app hooks and the takeover check) or read-only (the menu guards). The platform guard isn't installed on x86.
  • On Snapdragon, compared with dragon: a configuration without an [omarchy] section gets one appended instead of being refused, and a refresh fetches the channel's omarchy.db as the user first. Compared with this PR's base, Snapdragon refreshes stop writing x86 repositories, and finalization, the keyring and zram follow section 12.
  • Rollback is by topic, newest first. In section 8 the setup seam builds on image-build deferral, and the locale step stands alone. In section 9 each commit stands alone; in section 10 both build on dispatch. Each of the six helper commits stands alone, and so do the generic fixes and the guard (without the guard's source files, omarchy-settings' conditional install lines are inert). The migration caller depends on dispatch, and dispatch and the guard depend on the detector. The encryption fixes don't depend on anything else in this PR. Sections 14 to 17 each stand alone. Section 13's marker goes first, then the pacman commit (which needs the detector); every other commit in sections 11 and 12 stands alone.
  • Release order: the omarchy-mac-boot 20260928-2 or later, with setup-boot, update-takeover and luks-slots --owner (exit 4 when no owner slot is recorded), and omarchy-mac 0.1.0-9 or later (the hardware stack as dependencies, vulkan-asahi included, and its files under /usr/share/omarchy-platform), publish with, or before, this runtime. Otherwise a Mac stops its hardware leaf and refuses every takeover, a new Mac image has no Vulkan driver, and a Mac loses its platform files. omarchy-mac 0.1.0-10 adds the keyrings and audio.json files; before it, updates don't refresh asahi-alarm-keyring and the audio panel has no hints for the Mac's processing nodes.
  • Packaging follow-up for omarchy-pkgs: omarchy-settings: ship the full set on aarch64 for runtime-profile sources omarchy-pkgs#638 reads section 13's marker. Add etc/mkinitcpio.conf.d/00-omarchy-hooks.conf to omarchy-settings' backup list, next to omarchy_hooks.conf, and install the platform guard's three files only when CARCH is aarch64 (follow-up PR linked in the comments). For Fix: restrict Steam centering to only the main window #380: omarchy_hooks.conf no longer has a top-level HOOKS= line here, so its asahi guard should apply only to the old layout, and this layout ships unchanged.

What stays in omarchy-mac

The Apple entrypoints (omarchy-mac-boot: boot chain, boot-partition key, key-slot records, the migration from older Mac setups, update-takeover, setup-boot; omarchy-mac: setup-system, setup-user, post-install and pre-remove, and its Hyprland defaults, key names, cutout file and display hints under /usr/share/omarchy-platform), the omarchy-mac hardware package, the Apple package tags, the Asahi repositories and the pacman templates finalization reads from omarchy-mac, the Apple initramfs drop-ins, the Mac installer and image builder, and Mac-only migrations. None of it is needed on other hardware. The Apple-aware lines here are the detector's apple-silicon answer, omarchy-hw-apple-silicon, the dispatcher's two registrations (boot operations to omarchy-mac-boot, setup and app hooks to omarchy-mac), the HOOKS baseline's Apple line, the Apple package list, the Apple row of the channel gate (none yet) with the path of omarchy-mac's pacman templates, and section 9's hardware facts: comments naming the Mac devices generic detection now covers, and the quirks and two menu rows omarchy-hw-apple-silicon gates. The Mac's own binds, gestures, key names, notch sizes and backlight choices are omarchy-mac's files under the platform root. Repairs for Macs set up before this (their locale) stay in omarchy-mac too.

For the Dragon maintainers

The detector's qualcomm answer follows dragon: @JimmayVV's qcom, device-tree check and @birkskyum's Snapdragon detection. Both co-author the detector and guard commits. You decide what counts as a Qualcomm machine, so push back on anything here.

  • qualcomm uses the same evidence as omarchy-hw-qualcomm-soc (a qcom, token in the boot device tree), and the fixtures check that the two agree. They differ only on contradictory evidence, where the detector fails. Once both land, omarchy-hw-qualcomm-soc can become [[ $(omarchy-hw-platform) == "qualcomm" ]].
  • qualcomm registers no dispatch entrypoints, so Snapdragon machines take today's paths.
  • Section 12 carries dragon's keep-and-swap refresh and ARM finalization, behind the detector instead of uname -m. Merging dragon: keep this PR's bin/omarchy-refresh-pacman, install/helpers/pacman.sh and install/post-install/pacman.sh (they stay inside the hardened sudo boundary, and the lint has nothing to review there); default/pacman/pacman-aarch64.conf and mirrorlist-aarch64 are identical; refresh-pacman-arm-test.sh and pacman-aarch64-test.sh test the old interface, and pacman-arm-channel-test.sh replaces them. install/user/mise-work.sh still needs an entry in the lint's reviewed list. thunderbolt_module.conf conflicts: keep this PR's modinfo form.
  • Two differences from dragon's refresh, for you to accept or push back on: a configuration with no [omarchy] section gets one appended instead of being refused (a stable install finalized by dragon has none, so it could never move to edge), and the refresh first checks, as the user, that the channel's omarchy.db publishes the runtime pair.
  • omarchy-qualcomm.packages is a first cut (only linux-firmware-qcom, which dragon's hardware setup already installs), and Qualcomm packages can adopt the guard tag whenever you like.

Review of round 4: no blocker. It asked for the keyring change to be its own commit, for the keyboard step to hand over to setup's retry-or-console screen instead of asking forever (both done, with a test), and for the docs to say a hidden audio node still shows while it's the default (done). Second review of round 2: no blocker; two doc wordings tightened. Earlier rounds: no blocker. It confirmed that verify after AUR never reuses a timestamp, and asked for a revocation right after AUR (added) and for the guard's per-transaction cost to be stated (above). For section 8: no blocker. It asked for the recovery-key reset (since removed) to be documented in docs/lifecycle-dispatch.md (added), and for root to ignore the locale step's and the reset's path overrides (done); a design review before that had the deferred rebuild also watch the Limine command line, the reset (since removed) refuse snapshot roots when started by hand, and the locale step keep LC_* lines (all done, with tests). For sections 9 and 10: no blocker, one test cleanup fixed. Design reviews before that had lid detection go by the lid switch capability (a Mac mini has the SMC device without a lid), the platform binds load before Omarchy's defaults so they can replace a default and run before a menu bind, key names apply to the key column only, decorators not recurse, the cutout calibration stay off other screens, a hidden bar park by its full height, Steam's removal get its own hook, and the takeover check added instead of dropping the Mac's protection (all done, with tests). For sections 11 to 13: no blocker; it asked for a clearer refusal when a Mac has no Omarchy repository, keyring setup kept off a Mac's finalization, and a doc row (all done). Design reviews before that kept Apple Silicon off every channel until its packages qualify, replaced a root pacman sync into a user-writable directory with a user-level package-list check and a configuration written straight through sudo, made the zram migration retry a swap that doesn't start, and kept Arch Linux ARM's keyring off x86 (all done, with tests).

Credits

This brings upstream work that started in omacom/omarchy-mac:

Their commits carry Co-authored-by trailers where their code or fix is in this PR.

@birkskyum

Copy link
Copy Markdown

I'd like to co-author the detector and guard with @birkskyum and @JimmayVV, since they decide what counts as a Qualcomm machine.

@maralcbr feel free to, thanks!

@maralcbr maralcbr changed the title Add platform detection, boot lifecycle dispatch and LUKS fixes Converge omarchy-mac and omarchy-mx-mac into upstream Omarchy Sep 26, 2026
@maralcbr
maralcbr force-pushed the upstream/convergence branch from eebfbc0 to 8c7c65b Compare September 27, 2026 06:39
@maralcbr

Copy link
Copy Markdown
Author

Two updates, force-pushed with the same trees plus one new commit:

  • Credits. New Co-authored-by trailers for @birkskyum on the detector and the guard, and for @scottjones on the first-boot key-slot commit that carries his LUKS recovery-key operations. The existing trailers for Scott, @cajomar and @oliverlukschander now use their GitHub noreply addresses so the commits link to their profiles. @JimmayVV, happy to add you on the detector and guard too if you'd like. The rewritten commits have exactly the same trees as before, and the Credits section lists who did what.
  • HOOKS fix (new last commit). Testing against an encrypted M1 Pro, I found I'd dropped the Apple case when I moved the HOOKS line into 00-omarchy-hooks.conf. With the busybox line everywhere, omarchy-mac-boot's encryption drop-in treats the Mac as a legacy cryptdevice= install and leaves it alone, so the image has no sd-encrypt and the Mac can't unlock. The baseline now asks omarchy-hw-platform: Apple Silicon gets the systemd line, a Mac that set up its own line keeps it, and x86, Snapdragon and generic aarch64 get exactly today's line. The new tests fail on the old baseline, and the shell suite fails the same files as before on x86 and aarch64.

For omarchy-pkgs#380: omarchy_hooks.conf no longer has a top-level HOOKS= line here, so the asahi guard should apply only to the old layout, and 00-omarchy-hooks.conf should join backup.

kevinwyckoff added a commit to kevinwyckoff/omarchy that referenced this pull request Sep 27, 2026
Nine keymaps the keyboard picker offers have no row in systemd's
kbd-model-map: colemak, azerty, bg-cp1251, cz, de_CH-latin1, kyrgyz,
no-latin1, pl and ua. systemd-firstboot and localectl persist those as KEYMAP
alone, and default/hypr/input.lua falls back to us without XKBLAYOUT, so a
Polish, Czech, Ukrainian, Bulgarian, Norwegian, Swiss German, Kyrgyz or
Colemak user got the chosen console keymap and a US desktop. For cz, de_CH,
azerty and colemak the ASCII keys move between the two, so a password set on
the console types differently at the desktop.

Those rows now carry the XKB layout[:variant] as a third field, and
omarchy_keyboard_xkb_settings turns it into the lines systemd-firstboot writes
for a mapped keymap, with its pc105 model and terminate:ctrl_alt_bksp option.
First-boot owner setup inserts them after KEYMAP once the keymap is persisted
and only when systemd wrote no XKBLAYOUT, so every keymap systemd maps comes
out exactly as before. The ISO's configure_keyboard reads the same helper from
the list it vendors.

Each layout is the one for the same language whose letters sit where the
console keymap puts them, checked key by key against xkeyboard-config: kbd's
cz is its QWERTY Czech map (cz:qwerty) and bg-cp1251 its phonetic Bulgarian
one (bg:phonetic). ua, bg and kg are non-Latin, so input.lua puts us in front
of them with the Alt toggle, as it does for the mapped ones.

The pinned layout is for the desktop only. Plymouth reads a vconsole.conf with
KEYMAP and no XKBLAYOUT through the console keymap the keymap hook loads, the
one the LUKS passphrase was typed on in the installer and in first-boot setup,
but switches to XKB as soon as XKBLAYOUT is set. Some pinned layouts put other
characters on the same keys (Shift+4 is $ on the no-latin1 console and ¤ in
XKB's no; azerty's = key is ! in XKB's fr), so the passphrase typed at install
would stop unlocking, and the Latin-layout check would start leaving ua, bg
and kg out of the initramfs. When the XKB lines in vconsole.conf are exactly
the ones omarchy_keyboard_xkb_settings gives for its KEYMAP,
omarchy_hooks.conf now bundles the file through a new omarchy-vconsole hook
that leaves them out, so the initramfs gets the file it got before the pin and
the LUKS prompt reads keys as before. The hook hands mkinitcpio a regular file
rather than a pipe, because add_file reads its source twice when an earlier
hook such as sd-vconsole has already added vconsole.conf.

Every other vconsole.conf goes through the Latin-layout check and FILES
exactly as before: one for a keymap systemd maps, one for a picker keymap
written before the pin (so a later drop-in that sets HOOKS outright still
keeps it), one with XKB lines set by hand, such as a QWERTZ cz layout on the
cz console keymap, and any file on a system whose setup-form.sh is missing
or predates the helper. The check reads the packaged setup-form.sh under
/usr/share/omarchy rather than $OMARCHY_PATH, so rebuilding from a shell
pointed at a checkout can't change what the LUKS prompt reads, and the list
test pins all nine layouts, since changing one sends existing installs'
desktop layout to the LUKS prompt.

The Azerbaijani row is relabelled French (AZERTY). kbd's azerty is the French
AZERTY map and kbd ships no Azerbaijani one; pairing it with XKB az would have
the console and the desktop type different letters (az puts ü on W), so the
row now says what it gives and gets fr.

The mkinitcpio-hooks-test in omacom#13362 composes the drop-ins against the host's
own /etc/vconsole.conf and sets its FILES entry aside. On a machine whose
vconsole.conf carries a pinned layout it would also need to set
omarchy-vconsole aside from HOOKS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019juCxGWnRefP2JnpA5tmdt
@maralcbr

Copy link
Copy Markdown
Author

Pushed six more commits (section 7 in the description): generic helpers omarchy-mac grew for aarch64 that any machine can use, one commit each with its callers and tests.

  • Install rows hide when nothing they install exists on this architecture (omarchy-pkg-available). Every guarded row resolves on x86_64 stable and edge, so the x86 menu is unchanged.
  • 1Password gets software GL where there is no render node.
  • Screen recording falls back to wf-recorder when gpu-screen-recorder can't start, and stop/status follow the recorder's pid instead of pgrep -f. The finalize pass pins 48 kHz, the same line as Pin recording audio sample rate at 48 kHz #11092.
  • A logind lid inhibitor for displays on connectors logind doesn't count (USB-C on Apple Silicon).
  • The workspace you were on stays in front when its monitor goes away.
  • Ambient-light keyboard backlight on laptops with a light sensor and a keyboard LED. Its migration keeps the name it has in omarchy-mac, so it sorts before a few newer ones on purpose.

None of them adds an Apple branch. In an x86_64 container each of the six fails exactly the same ./test/all files as the previous head (all missing tools), and their Apple-relevant tests passed natively on an M2 Max on the revision before the last review fixes (rerun to follow). @scottjones, @6abe, @ijt and @ryanrhughes are co-authors where their code is carried.

@maralcbr

Copy link
Copy Markdown
Author

Rerun done: all 15 Apple-relevant test files from the six helper commits pass natively on the M2 Max at the pushed head (087bd8d).

@maralcbr

Copy link
Copy Markdown
Author

Pushed five more commits (section 8 in the description): what prebuilt images and a platform's own setup need, one commit each with its tests. None of it runs on an ISO install.

  • Image-build deferral: while the image manifest the detector already reads exists, hardware setup queues its leaves, and omarchy-provision-hardware runs them on the machine's first boot, resuming after a failed step and rebuilding the boot image once at the end.
  • A UTF-8 locale for image roots still on LANG=C (only LANG changes, and only from unset, C or POSIX).
  • A first-run timezone toast, only on an image-built machine still on UTC, so someone who picked UTC on the ISO is never asked.
  • Password reset with the disk's recovery key before the login screen. Owner setup arms it only where it creates a recovery key, which no x86 install does today.
  • Two optional dispatch operations, setup-system and setup-user, so a platform package runs its own setup from Omarchy's instead of inline platform branches. They resolve to nothing on x86, Snapdragon and generic aarch64. On a Mac they resolve to omarchy-mac, which doesn't ship them yet, so nothing changes there either until it does. It also brings @scottjones's fix that keeps first run pending when finalization fails; that is the one behaviour change on x86.

Testing, on the pushed revisions: in an x86_64 Arch container each of the five fails exactly the same ./test/all files as the previous head (25 shell files and one test/cli check, all for missing tools), and its own tests pass. The 16 Apple-relevant test files (dispatch, LUKS, image, first run) pass natively on an M2 Max at the last commit. @scottjones and @dl-alexandre are co-authors where their code is carried.

@maralcbr

Copy link
Copy Markdown
Author

Pushed eight more commits (sections 9 and 10 in the description). They cover the Apple Silicon lines omarchy-mac still carried in files Omarchy already has, one commit per area with its tests. Mac-only desktop defaults don't come into Omarchy. Omarchy gets generic places a platform package fills, and omarchy-mac ships the Mac's own files there.

  • Detection that works without PC names: batteries by type (and not a wireless mouse's), lids by the lid switch capability, logind for the lid state, Apple's trackpad and panel backlight.
  • Platform user setup keeps XDG_CONFIG_HOME and XDG_STATE_HOME.
  • Hyprland defaults from a platform package, in the packaged tree:
    • default/hypr/platform/defaults loads before Omarchy's defaults. A chord bound there replaces Omarchy's default for it, and a bind decorator can run first.
    • default/hypr/platform loads after the user's files. A default gesture there steps aside for the user's own.
    • The user's files still override everything the platform sets.
  • Key names for the keybindings menu (default/omarchy/platform/key-names).
  • A camera-cutout file for the bar (default/shell/platform/display-cutouts.json), so a top bar clears a notch on a described panel.
  • PC and Intel Mac quirks that skip Apple Silicon: the Broadcom supplicant quirk and its migration, fnmode=2, the Windows VM, direct boot and hibernation.
  • post-install <app> and pre-remove <app> for browsers and Steam, and update-takeover so the Mac's boot package can refuse moving files it needs. Steam's 32-bit driver step no longer fails without an Intel, AMD or NVIDIA GPU.

Without a platform package, the new directories and files are empty or absent and the dispatch operations resolve to nothing, so x86 and Snapdragon run as before. The exceptions are the generic detection fixes, listed under Risk.

Testing:

  • x86_64 Arch container: each of the eight fails exactly the same ./test/all files as the previous head (25 shell files and one test/cli check, all for missing tools), and its own tests pass.
  • M2 Max: the 38 Apple-relevant test files pass natively at the last commit.

@scottjones, @ryanrhughes, @skkumarsparsh, @dl-alexandre and @kx0101 are co-authors where their code is carried.

@omarchybot omarchybot added the enhancement New feature or request label Sep 27, 2026
maralcbr added a commit to jdvmi00/omarchy-pkgs that referenced this pull request Sep 27, 2026
omacom/omarchy#13362 sets HOOKS per platform inside 00-omarchy-hooks.conf,
with no column-0 HOOKS= line, so the aarch64 build failed on it; settings-dev
tracks quattro automatically and would break as soon as it lands. Wrap only a
single-line HOOKS=(...), refuse any other column-0 HOOKS=, then source the
shipped files onto a Mac line and a stock line: the Mac line must come through
unchanged and the stock one must not. Back up 00-omarchy-hooks.conf when it
ships, and refuse it without the omarchy-hw-platform copy it asks.

The test now uses the real v4.0.4 hooks file, #13362's two files and
omarchy-mac-boot's 90-94 fragments, and covers upgrades over a hand-restored
file.
@maralcbr

Copy link
Copy Markdown
Author

Pushed seven more commits (sections 11 to 13 in the description):

  • setup-boot, a dispatch operation for the platform's boot package, run by the last hardware leaf before setup-system. It's required on Apple Silicon, where omarchy-mac-boot sets up the console and the switch to Limine.
  • install/omarchy-apple-silicon.packages, the Mac's package set, including wf-recorder (gpu-screen-recorder can't capture on Apple Silicon).
  • Channel changes on aarch64 keep the machine's own repositories and move only the [omarchy] server, dragon's approach, inside the hardened sudo boundary. A channel must be qualified for the platform (x86_64 all three, Snapdragon and other aarch64 edge, Apple Silicon none until its packages qualify), and the refresh first checks that the channel publishes the runtime pair. Finalization uses dragon's ARM template and keyring steps, and omarchy-mac's template on a Mac.
  • Arch Linux ARM's keyring stays current on aarch64, existing aarch64 machines get zram-generator (migration 1790328426, the name Macs already ran), and Thunderbolt is early-loaded only where the kernel has it as a module.
  • default/settings-runtime-profile, which lets omarchy-settings ship the same drop-ins on aarch64 (omarchy-settings: ship the full set on aarch64 for runtime-profile sources omarchy-pkgs#638).

x86_64 runs as before: the refresh copies the same templates, the keyring step is unchanged, and the Thunderbolt module is still listed. Snapdragon differences from dragon are listed under Risk.

Testing:

  • x86_64 Arch container: each of the seven fails exactly the same ./test/all files as the previous head (25 shell files and one test/cli check, all for missing tools), and its own tests pass.
  • M2 Max: the 19 Apple-relevant test files pass natively at the last commit.

@birkskyum and @jdvmi00 are co-authors of the pacman commit, and @scottjones of the Apple package list.

@maralcbr

Copy link
Copy Markdown
Author

Thanks @scottjones, this was a great pass. I've taken nearly all of it, rewriting the commits rather than adding removals on top, so each change lands in the commit it belongs to. The branch is now 45 commits: the head you read, plus the step 6 commits and sections 11 to 13 pushed since, with this round folded in. Point by point:

Dropped

  • Timezone toast: gone (leaf, first-run step, test, docs, and the Credits line). You're right that image-built Macs are already asked in owner setup. The real gap is omarchy-mac's installer, so that moves to omarchy-mac: the installer asks, and a one-time toast for installed Macs still on UTC. That toast should only fire when the zone was never chosen, since someone can pick UTC on purpose. @dl-alexandre's credit moves with it.
  • update-preflight and boot-rebuild: removed from the operation list, docs, tests and both call sites. omarchy-update-boot is now verify-only and refuses any argument, so a stray preflight caller fails instead of silently verifying. Owner setup's stale-entry refresh is back to plain reset_limine_config; limine-update. One thing we lose, and the docs say so: an update on a machine whose platform can't be told now installs its packages and then fails verification, instead of stopping before the transaction. I'll track your ticket 36 question (an unencrypted Mac factory reset through limine-update) in omarchy-mac. If it needs the Mac's rebuild, boot-rebuild comes back with its first entrypoint.

Synced with quattro-upstream

  • setup-boot: in, folded into the setup seam commit in section 8. It's required on Apple, resolves in omarchy-mac-boot, and runs before setup-system from platform-setup.sh (with image-first-boot on an image's first boot). update-takeover, post-install and pre-remove were already in the step 6 commits.
  • Thunderbolt: the original commit in section 1 now uses the modinfo form. It's retitled "Early-load thunderbolt only where the kernel builds the module" and carries your trailer, and the Dragon note says to keep that form.

Keep, but easier to see

  • The omarchy-drive-password summary now reads "Set a new password for an encrypted drive; for the system disk, also the login and root passwords".
  • The description has a "What changes on x86" section right after "Why", and Risk points to it.

Your open questions

  • Platform guard on x86: it'll be aarch64-only. omarchy-settings is built per architecture and already branches on CARCH, so with omarchy-settings-dev: ship the platform guard on aarch64 only omarchy-pkgs#691 the x86_64 package ships no hook, guard or detector copy. x86 doesn't need the copy: its HOOKS baseline keeps the busybox line without a detector. This PR's side is done: hardware setup says nothing about a missing guard on x86 and still warns on generic aarch64. I kept the guard out of omarchy-mac on purpose. The exposure you named is a Snapdragon or generic aarch64 machine installing Apple packages, and those machines never have omarchy-mac, so a guard living there couldn't protect them. A pre_install refusal in the Apple packages doesn't work either: pacman 7 ignores that scriptlet's exit status.
  • Sync databases on image first boot: agreed the fix belongs to the image, and a bare -Sy is wrong. I left omarchy-provision-hardware's fallback alone for now, because removing it can leave a deferred step queued forever (and the initramfs rebuild behind it) for an owner who never updates. I don't want menu guards to fail open either, since an Install row with no database can't install anything. My proposal is an image-side first-online step: a full upgrade through omarchy update's safeguards (keyring, verification) that then resumes provisioning. Once that exists, the -Sy fallback goes. Until then, a fresh image hides guarded Install rows until its sync databases are populated (the first update, or the fallback if a deferred step needed a package), and I'd like first boot to say so. Does that work for you?
  • Locale: I'd keep it here. Hand installs on ALARM aside, the Macs already on LANG=C still need omarchy-mac's repairs, and those run this leaf. It's a no-op after the ISO. If we later decide hand installs are over, the leaf and the repairs move to omarchy-mac together, as you say.
  • Recovery key: agreed it's Ryan's call and should match across platforms. No change until he decides. Your x86 plan (created in the ISO configurator, a "Reset password" boot entry for busybox encrypt) is the one I'd follow if the answer is yes.

Credits

@JimmayVV

Copy link
Copy Markdown

Thanks @maralcbr, and sorry for the slow reply. Yes please, I'd be glad to be added on the detector and the guard.

I went through the detector, the guard and the channel refresh from the Snapdragon side.

Detector. A qcom, token in the root compatible is the right test. Every Qualcomm hardware step on dragon is already gated on the same test through omarchy-hw-qualcomm-soc, so the two stay consistent. A few notes:

  • The comment says ARM laptops have no DMI. Snapdragon laptops do publish SMBIOS, and systemd-stub uses it to pick the board's tree from the UKI's .dtbauto sections. Only the comment is off.
  • A board whose tree the UKI doesn't carry yet boots ACPI-only and reads generic-aarch64. That's right for that boot, but once we start tagging, nothing that helps a machine get its tree back should carry omarchy-platform-qualcomm.
  • qualcomm means a Qualcomm SoC, not a Windows-on-ARM laptop: Snapdragon Chromebooks match too ("google,lazor-rev9", "qcom,sc7180"). That's fine, since board packages check their own board, but a line in platform-guard.md could say so.

Guard. It reads well from our side. Starting with no Qualcomm tags and adopting them package by package suits us.

Refresh. Both differences are fine by me. A stable dragon install has no [omarchy] section, so it can't reach edge today, and adding one last matches install finalization and the x86 templates. The preflight passes today: edge's aarch64 omarchy.db publishes omarchy-dev and omarchy-settings-dev.

Thanks for carrying dragon's refresh over so carefully.

maralcbr and others added 29 commits October 6, 2026 12:12
…-boot setup

A deferred install (an Apple Silicon image, an OEM ISO install) runs its
hardware steps before the owner picks a timezone, on the UTC placeholder,
which names no country, so install/hardware/set-wireless-regdom.sh leaves the
regulatory domain unset and the radios stay on the world domain. Owner setup
now runs that step again once the owner's timezone is set, and applies it to
the radios through wireless-regdb's helper, since no reboot follows setup. An
up-front install, which picks the timezone before its hardware steps, is
unchanged.
First-boot setup kept going when the chosen layout did not take: loadkeys
errors were ignored, a keymap localectl did not list kept the default, and a
failed or partial write of /etc/vconsole.conf was only logged. The owner then
typed the disk password in one layout while the boot files asked for it in
another, and could only unlock the disk by typing US-style.

The keyboard step now fails closed. On a console loadkeys must succeed, the
keymap must be in localectl's list exactly (a list localectl cannot produce
counts as a failure too, logged apart), and after systemd-firstboot or the
localectl fallback, /etc/vconsole.conf is read back in a clean shell and must
name the chosen keymap with an XKB layout. Any failure is logged, the owner is
told the layout could not be set, and the layout is asked for again, so the
password form is never reached with a layout that did not take.
omarchy-update-keyring reinstalls archlinux-keyring (and Arch Linux ARM's on
aarch64) before the system upgrade, so a rotated key never fails that
upgrade's signature checks. A platform's own repository has a keyring of its
own that nothing refreshed: on Apple Silicon, asahi-alarm-keyring for
[asahi-alarm].

A platform package now lists those keyrings in
/usr/share/omarchy-platform/keyrings, one package name per line, and
omarchy-update-keyring reinstalls each one that is installed in the same
transaction. It adds no repository the machine didn't already trust. A line
that isn't one keyring package name is skipped with a warning, so a mistake in
the list can't stop every update. Without the file, as on x86 today, the step
runs as before.
Quickshell types only exact media classes, so a virtual source (an
Audio/Source/Virtual: EasyEffects' source, or the microphone mapping a
platform package adds) has no PwNode.audio. With one as the default input the
audio panel showed the input at 0% and its slider changed nothing, the peak
meter stayed flat, the microphone widget read it as muted, and the panel could
not list it unless it was already the default.

Such an input now goes through wpctl for its volume and mute (UntypedInput in
shell/Commons, one reader and one pactl subscribe for the whole shell), the
panel meters it with the new omarchy-audio-source-level, and the input list
takes an untyped node PulseAudio lists as a source.

A platform whose audio runs through its own processing can also name nodes
that are neither devices nor apps in /usr/share/omarchy-platform/audio.json,
which Omarchy ships none of: the panel and the microphone widget leave them
out, and a "replaced" node only while its replacement exists. Without the
file nothing is hidden.

On every machine: the input list leaves out a source whose ports are all
unavailable (an empty headset jack) unless it is the default, as the output
list does for sinks; choosing an input no longer moves a filter's own capture
(a source output with node.link-group), which would feed the filter from the
new input or from itself; the shell's own level meters no longer light the
microphone widget; and omarchy-audio-sink-availability reads pactl in the C
locale for sinks too, since "not available" is translated.
systemd-firstboot writes an XKB layout only for keymaps listed in
kbd-model-map. Nine the setup form offers are not (Colemak, Azerbaijani,
Bulgarian, Czech, Swiss German, Kyrgyz, Norwegian, Polish, Ukrainian), so it
wrote KEYMAP= alone, the readback refused it, and owner setup could never get
past the keyboard step for them.

Setup now adds the XKB settings itself when firstboot left none, matching the
console map so the shared password types the same on the desktop: kbd's cz is
QWERTY, bg-cp1251 phonetic and azerty French AZERTY, and non-Latin layouts keep
a US layout to switch to, as kbd-model-map's own rows do. A test checks every
layout the form offers ends with an XKB layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
The ambient keyboard backlight treated any 0 the keyboard-brightness keys had
not recorded as an accident and relit it within five seconds. Firmware Fn keys
(Fn+Space on ThinkPads and Framework laptops, Fn+F10 on Dells) write the LED
directly, so on those laptops turning the keys off never stuck.

The lock screen's off now records a blank, and only that blank or a 1%
leftover is taken back; any other off is a choice that pauses auto like the
keys do. A wake clears the blank only once the restore succeeded, the command
runs one at a time so a blank racing a wake can't leave the LED and record
disagreeing, and the loop drops a blank once it has taken the keys back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
Each menu open checked every Install row against the sync databases, which
costs most of a second of pacman work on every machine to hide rows only an
ARM machine is missing. The fix for those is publishing the ARM packages, not
hiding the rows, so this removes omarchy-pkg-available, the Install rows'
availability and architecture guards, and reinstall's skipping of defaults no
repository offers.

The Windows VM row keeps its x86_64 guard, since no package fixes a guest that
needs an x86_64 host, and reinstall keeps its aarch64 channel handling.

This reverts commit aea001c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
Factory reset moved the running root aside, marked the reset committed, then
renamed the staged factory root to @. If that second rename failed, cleanup
saw a committed reset: it skipped the boot package's rollback and kept the
throwaway LUKS slot, and since the swap had not happened it deleted the staged
root as well, leaving nothing at @.

The reset now commits only once the factory root is at @. A failed activation
renames the old root back first, then cleanup rolls the platform's boot state
back and revokes the throwaway slot. If that rename fails too, both roots are
kept, cleanup still rolls back and revokes, and the error names the rename
that recovers the machine. The generic Limine path has already rebuilt its
boot files from the factory root by then, as it had before this change.
A saved pid that no longer named a recorder made status and stop select every
gpu-screen-recorder and wf-recorder the user ran, so stopping could interrupt,
and after five seconds kill, a recording started somewhere else.

A stale pid now means no recording of ours is running: the toggle starts a new
one, and --stop-recording finds nothing to stop. Stop acts on the recorder the
status check selected rather than selecting again. Without a saved pid, as for
a recording started before the pid was saved, every recorder the user runs
still counts.
With desktop and microphone audio both requested, wf-recorder records a null
sink fed by two loopbacks, and a loopback that failed to load was ignored: the
recording went ahead without that source, or silent when both failed. A
missing null sink or default device dropped audio just as quietly.

A source that cannot be captured is now left out with a notification naming
it, the loopbacks that did load are unloaded, and the recording carries the
source that remains, or no audio track at all.
These vendors currently publish Linux builds for x86_64 alone (the AUR's
microsoft-edge-stable-bin and spotify are arch=('x86_64'), and Dropbox lists
no Linux ARM client), so no native aarch64 build is available and choosing
one on an ARM machine only fails. Like the Windows VM row they keep a uname
check, which the menu answers once per open without asking pacman. The other
Install rows are unchanged.
The generic Limine path added its throwaway LUKS slot without recording it,
so a reset that failed before activation never revoked it. Once the factory
root's UKI was on the ESP, a failed activation that put the old root back left
a machine that booted the factory kernel and unlocked unattended with a key
readable on the ESP, while the screen said the current root was back.

The generic slot is now recorded like the platform's and revoked by cleanup
whenever the reset fails before the factory root is active. A failed
activation on that path says the boot files were already rebuilt from the
factory system and to run limine-update before rebooting.
The recorder's pid is saved only once its file exists. Until then a pid left
by a recording that ended without a stop still counted as stale, so pressing
the toggle again while the new recorder started found nothing of ours and
could start a second recording. Starting a recording now clears that pid,
and during startup the toggle falls back to the user's recorders as before.
The notice said the dropped source "could not be captured", which was wrong
when the null sink itself failed to load: the microphone was never tried, and
desktop audio recorded alone as it always has. It now says the source could
not be added to the recording, true for a missing device, a failed loopback
and a failed null sink alike.
Heroic publishes Linux builds for x86_64 alone (its arm64 builds are for
macOS and Windows), and Mojang's Linux launcher is a single x86_64 tarball.
They join Edge, Spotify and Dropbox under the same uname check, so every row
whose vendor ships no native Linux aarch64 build is treated alike.
The bar indicator and the menu's Stop Screenrecording row asked the process
helper whether any recorder the user runs was alive, while the toggle's stop
acts only on the recording it saved. With a stale saved pid alongside an
unrelated recorder, both offered a stop that did nothing.

omarchy-capture-screenrecording --status answers with the selection the stop
uses, and both now ask it. Status needs no recordings directory and never
notifies, so the recordings-directory check moved below argument parsing and
skips it.
On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.

Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.

Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
Fresh installs mark every migration done, and every Apple Silicon machine is a
fresh install, so the Mac handoff to omarchy-mac-boot (and the migrate
operation it called), zram for aarch64 installs that predate the package list,
the Brave Widevine link and the Broadcom migration's Apple Silicon guard never
run anywhere. The exit-75 deferral in omarchy-migrate existed only for two of
them. Keyboard backlight auto stays on new installs through first run rather
than switching on for existing ones, and migration 1786605598 goes back to
quattro: the mkinitcpio drop-ins arrive by package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pacman platform guard (and the detector copy omarchy-settings shipped for
it, which the HOOKS baseline fell back to), the lint against uname gates, first
run staying pending after a failed finalization, run_logged echoing failures to
stderr, half-up battery rounding, hiding empty-jack inputs and leaving a
filter's capture alone when choosing an input all rode along with the Mac work
without serving it. Each goes back to quattro's behaviour and can come back on
its own merits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A platform package could load its own Hyprland binds, settings and gestures:
binds that replaced Omarchy's chords and decorated every later bind, settings
ahead of the theme, and gestures after the user's files, which needed every
hl.gesture call wrapped so a default could step aside. On a Mac those carried
macOS habits (a three-finger workspace swipe, tap-to-click off, menus opening
on the built-in screen, other screenshot and keyboard-backlight chords), not
anything the hardware needs, and Omarchy should behave the same on every
machine. The one bind a Mac does need, its lid switch under the SMC's name,
joins Omarchy's own lid binds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts them

Every aarch64 channel's template pointed at the edge repository, but a switch
to stable or rc installs the release-named omarchy and omarchy-settings. On
edge/aarch64 those are the 4.0.4 release line, which has no omarchy-hw-platform,
lifecycle dispatch or omarchy-pkg-defaults, so `omarchy-channel-set stable` on
a Mac replaced the quattro runtime it runs with one that cannot run it, and
succeeded.

aarch64 platforms now have edge templates alone, so a switch to stable or rc is
refused before anything changes; adding those templates once a release
supports aarch64 opens them. Refresh with no channel named, as reinstall runs
it, and install finalization for a channel the platform has no templates for
take the platform's default channel: stable on x86_64, edge on aarch64.
d0b94d9 returned omarchy-audio-input-set-default to moving every source
output, as not serving Mac support. It does: asahi-audio's microphone DSP on
Apple Silicon records the raw array through a filter-chain capture stream
(audio_effect.jNNN-mic, node.link-group "filter-chain-..."). On an M1 Pro,
choosing the Mac's microphone in the audio panel moved that stream onto the
DSP's own output, and the microphone recorded digital silence (-91 dB, from
-55 dB of room noise) until PipeWire restarted. With the skip it keeps
recording (-54 dB).

This restores the skip of source outputs carrying node.link-group, and its
test, as they were before that commit.
The aarch64 edge templates, the template helper and the update-process docs
still said every platform has a template per channel and every ARM channel
installs from edge, which the previous commit made untrue.
omarchy-mac and omarchy-mac-boot now live in omacom/omarchy-mac-pkgs, with
each package at the top level. The dispatcher, its documentation and the
Apple Silicon package list name that repository, and the drop-in test's
fixtures, byte for byte the same as omarchy-mac-boot/files/etc/mkinitcpio.conf.d
at 4399105, cite it there.
The migrate operation went with the Mac migrations, and no caller hands a
machine to a platform migration any more, so the header lists only the
flows that still dispatch. The optional-operations comment is rewrapped.
A fresh install marks every migration done only for the user it creates, so
an account added later runs them all on its first update. On an Apple
Silicon Mac this one matches: an M1 Pro reports sys_vendor "Apple Inc." and
a BCM4387 (14e4:4433), so it would append feature_disable=0x82000, which
stops that chip scanning once brcmfmac reloads. Restore the guard and its
test case.
Steam's installer now adds the 32-bit drivers before Steam itself, so the hook runs after both and before the first launch.
@maralcbr
maralcbr force-pushed the upstream/convergence branch from 606e33d to 2827c5e Compare October 6, 2026 03:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants