From ccecdbde56a0174a7962ccffaabe412f96d0c4cd Mon Sep 17 00:00:00 2001 From: Jim Martin Date: Wed, 9 Sep 2026 22:36:53 -0500 Subject: [PATCH 01/32] omarchy-settings: keep the Limine and mkinitcpio drop-ins on aarch64 The aarch64 packages removed etc/mkinitcpio.conf.d and etc/limine-entry-tool.d wholesale. On an encrypted aarch64 install that boots with Limine (DGX Spark, Snapdragon X), installing the published package therefore drops omarchy_hooks.conf, HOOKS falls back to /etc/mkinitcpio.conf, and the next kernel update builds an initramfs with no encrypt hook: the machine hangs waiting for /dev/mapper/root with no prompt. Ship both drop-in directories on aarch64 and remove only thunderbolt_module.conf, whose module ARM kernels do not have. Keep the Limine template and the snapper notifier autostart for the same reason. The memory-stack removals (zram, oomd, zswap, USB autosuspend) are unchanged. --- pkgbuilds/omarchy-settings-dev/PKGBUILD | 42 ++++++++++------------- pkgbuilds/omarchy-settings/PKGBUILD | 44 ++++++++++--------------- 2 files changed, 35 insertions(+), 51 deletions(-) diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 6c7f8d9..61dd516 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -6,10 +6,11 @@ _pkgver_base=4.0.0 _pkgver_base_tag=v3.8.2 pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers (quattro branch tip)' # Arch-specific because the shipped /etc tree is not the same on every -# architecture: the Limine, mkinitcpio, zram and oomd drop-ins belong to the -# x86_64 boot and memory stack and are left out of the aarch64 package (see -# package()). makepkg only honours the arch-suffixed arrays below on -# arch-specific packages. +# architecture: the zram and oomd drop-ins belong to the x86_64 memory stack +# and are left out of the aarch64 package (see package()). The Limine and +# mkinitcpio drop-ins ship on both: UEFI aarch64 installs (DGX Spark, +# Snapdragon X) boot with Limine and an encrypted root exactly like x86_64. +# makepkg only honours the arch-suffixed arrays below on arch-specific packages. arch=('x86_64' 'aarch64') url='https://github.com/basecamp/omarchy' license=('MIT') @@ -68,14 +69,17 @@ backup=( 'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-wifi-powersave.rules' ) +# Boot configuration is backed up on both architectures. +backup+=( + 'etc/mkinitcpio.conf.d/omarchy_hooks.conf' + 'etc/limine-entry-tool.d/omarchy-defaults.conf' + 'etc/limine-entry-tool.d/omarchy-uki.conf' +) # backup has no arch-suffixed form, so the x86_64-only drop-ins join it here. # Each of these paths is removed from the aarch64 package in package(). if [[ $CARCH == x86_64 ]]; then backup+=( - 'etc/mkinitcpio.conf.d/omarchy_hooks.conf' 'etc/mkinitcpio.conf.d/thunderbolt_module.conf' - 'etc/limine-entry-tool.d/omarchy-defaults.conf' - 'etc/limine-entry-tool.d/omarchy-uki.conf' 'etc/modprobe.d/omarchy-usb-autosuspend.conf' 'etc/systemd/oomd.conf.d/10-omarchy.conf' 'etc/systemd/zram-generator.conf' @@ -160,14 +164,6 @@ package() { install -d "$pkgdir/usr/share/omarchy/config" cp -a config/. "$pkgdir/usr/share/omarchy/config/" - # The Limine/Snapper notifier has nothing to notify about without the x86_64 - # boot stack; drop it from both seeds so aarch64 users don't autostart a - # helper whose backing tool is not installed. - if [[ $CARCH == aarch64 ]]; then - rm -f "$pkgdir/etc/skel/.config/autostart/limine-snapper-notify.desktop" \ - "$pkgdir/usr/share/omarchy/config/autostart/limine-snapper-notify.desktop" - fi - # Package-owned defaults with real system/XDG locations. User config remains # higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d, # ~/.config/environment.d, ~/.config/fontconfig, ~/.config/xdg-terminals.list, @@ -217,11 +213,12 @@ package() { install -d "$pkgdir/etc" cp -a etc/. "$pkgdir/etc/" if [[ $CARCH == aarch64 ]]; then - # The x86_64 boot stack's drop-ins must not ship on aarch64: mkinitcpio - # reads every file under /etc/mkinitcpio.conf.d/, so omarchy_hooks.conf - # would inject the Limine hooks into the Asahi kernel's initramfs, and the - # Limine entry-tool config has no consumer without Limine. - rm -rf "$pkgdir/etc/limine-entry-tool.d" "$pkgdir/etc/mkinitcpio.conf.d" + # Keep omarchy_hooks.conf and the Limine entry-tool config: without them a + # kernel update on an encrypted aarch64 install rebuilds an initramfs with + # no encrypt hook and the machine cannot unlock its root. Only the + # Thunderbolt module request is x86-specific; ARM kernels lack the module + # and mkinitcpio treats a missing explicit module as an error. + rm -f "$pkgdir/etc/mkinitcpio.conf.d/thunderbolt_module.conf" # Memory stack: no zram device or zswap on the aarch64 install, and # systemd-oomd is not enabled there, so the vm.* reclaim tuning written # for zram would be wrong for it. Keep only the network tuning. @@ -325,11 +322,6 @@ EOF # live root config. install -d "$pkgdir/usr/share/omarchy/default" cp -a default/. "$pkgdir/usr/share/omarchy/default/" - # The Limine template is read by the x86_64 ISO orchestrator only. - if [[ $CARCH == aarch64 ]]; then - rm -rf "$pkgdir/usr/share/omarchy/default/limine" - fi - # Snapper config template used by the install-time `snapper create-config`. install -Dm644 default/snapper/root \ "$pkgdir/etc/snapper/config-templates/omarchy" diff --git a/pkgbuilds/omarchy-settings/PKGBUILD b/pkgbuilds/omarchy-settings/PKGBUILD index 0d063f3..346dbe2 100644 --- a/pkgbuilds/omarchy-settings/PKGBUILD +++ b/pkgbuilds/omarchy-settings/PKGBUILD @@ -13,13 +13,14 @@ pkgname='omarchy-settings' _tag='v4.0.3' _commit='0534987009061cbe2dacdde4ad564092ab698d12' pkgver=4.0.3 -pkgrel=1 +pkgrel=2 pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers' # Arch-specific because the shipped /etc tree is not the same on every -# architecture: the Limine, mkinitcpio, zram and oomd drop-ins belong to the -# x86_64 boot and memory stack and are left out of the aarch64 package (see -# package()). makepkg only honours the arch-suffixed arrays below on -# arch-specific packages. +# architecture: the zram and oomd drop-ins belong to the x86_64 memory stack +# and are left out of the aarch64 package (see package()). The Limine and +# mkinitcpio drop-ins ship on both: UEFI aarch64 installs (DGX Spark, +# Snapdragon X) boot with Limine and an encrypted root exactly like x86_64. +# makepkg only honours the arch-suffixed arrays below on arch-specific packages. arch=('x86_64' 'aarch64') url='https://github.com/basecamp/omarchy' license=('MIT') @@ -77,14 +78,17 @@ backup=( 'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-wifi-powersave.rules' ) +# Boot configuration is backed up on both architectures. +backup+=( + 'etc/mkinitcpio.conf.d/omarchy_hooks.conf' + 'etc/limine-entry-tool.d/omarchy-defaults.conf' + 'etc/limine-entry-tool.d/omarchy-uki.conf' +) # backup has no arch-suffixed form, so the x86_64-only drop-ins join it here. # Each of these paths is removed from the aarch64 package in package(). if [[ $CARCH == x86_64 ]]; then backup+=( - 'etc/mkinitcpio.conf.d/omarchy_hooks.conf' 'etc/mkinitcpio.conf.d/thunderbolt_module.conf' - 'etc/limine-entry-tool.d/omarchy-defaults.conf' - 'etc/limine-entry-tool.d/omarchy-uki.conf' 'etc/modprobe.d/omarchy-usb-autosuspend.conf' 'etc/systemd/oomd.conf.d/10-omarchy.conf' 'etc/systemd/zram-generator.conf' @@ -155,14 +159,6 @@ package() { install -d "$pkgdir/usr/share/omarchy/config" cp -a config/. "$pkgdir/usr/share/omarchy/config/" - # The Limine/Snapper notifier has nothing to notify about without the x86_64 - # boot stack; drop it from both seeds so aarch64 users don't autostart a - # helper whose backing tool is not installed. - if [[ $CARCH == aarch64 ]]; then - rm -f "$pkgdir/etc/skel/.config/autostart/limine-snapper-notify.desktop" \ - "$pkgdir/usr/share/omarchy/config/autostart/limine-snapper-notify.desktop" - fi - # Package-owned defaults with real system/XDG locations. User config remains # higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d, # ~/.config/environment.d, ~/.config/fontconfig, ~/.config/xdg-terminals.list, @@ -215,11 +211,12 @@ package() { install -d "$pkgdir/etc" cp -a etc/. "$pkgdir/etc/" if [[ $CARCH == aarch64 ]]; then - # The x86_64 boot stack's drop-ins must not ship on aarch64: mkinitcpio - # reads every file under /etc/mkinitcpio.conf.d/, so omarchy_hooks.conf - # would inject the Limine hooks into the Asahi kernel's initramfs, and the - # Limine entry-tool config has no consumer without Limine. - rm -rf "$pkgdir/etc/limine-entry-tool.d" "$pkgdir/etc/mkinitcpio.conf.d" + # Keep omarchy_hooks.conf and the Limine entry-tool config: without them a + # kernel update on an encrypted aarch64 install rebuilds an initramfs with + # no encrypt hook and the machine cannot unlock its root. Only the + # Thunderbolt module request is x86-specific; ARM kernels lack the module + # and mkinitcpio treats a missing explicit module as an error. + rm -f "$pkgdir/etc/mkinitcpio.conf.d/thunderbolt_module.conf" # Memory stack: no zram device or zswap on the aarch64 install, and # systemd-oomd is not enabled there, so the vm.* reclaim tuning written # for zram would be wrong for it. Keep only the network tuning. @@ -323,11 +320,6 @@ EOF # live root config. install -d "$pkgdir/usr/share/omarchy/default" cp -a default/. "$pkgdir/usr/share/omarchy/default/" - # The Limine template is read by the x86_64 ISO orchestrator only. - if [[ $CARCH == aarch64 ]]; then - rm -rf "$pkgdir/usr/share/omarchy/default/limine" - fi - # Snapper config template used by the install-time `snapper create-config`. install -Dm644 default/snapper/root \ "$pkgdir/etc/snapper/config-templates/omarchy" From 39722554db638febabe1cceb6f787e8a25619c19 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Wed, 16 Sep 2026 20:37:43 +0200 Subject: [PATCH 02/32] Refresh dev settings version and package revision Record the current quattro-derived version before bumping pkgrel, so makepkg does not discard the revision bump when updating a stale pkgver. --- pkgbuilds/omarchy-settings-dev/PKGBUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 46ee0bf..273e387 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Ryan Hughes pkgname='omarchy-settings-dev' -pkgver=4.0.0.r847.g4de185b -pkgrel=1 +pkgver=4.0.0.r2153.g9c5482c +pkgrel=2 _pkgver_base=4.0.0 _pkgver_base_tag=v3.8.2 pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers (quattro branch tip)' From 203b7340ab227407da1f4f3cb51b9dce87a16b5b Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Wed, 16 Sep 2026 20:37:43 +0200 Subject: [PATCH 03/32] Test packaged boot settings on ARM and x86_64 Exercise both settings recipes with synthetic runtime files. Check the exact Limine templates, boot drop-ins and backup metadata, installer-owned configuration, notifier copies and architecture-specific Thunderbolt handling. Run package regressions in the existing self-test job. --- .github/workflows/test.yml | 4 ++ pkgbuilds/omarchy-settings/test.sh | 110 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 pkgbuilds/omarchy-settings/test.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e48346..4910361 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,4 +47,8 @@ jobs: ./bin/omarchy-release self-test ./tests/partial-release.sh ./tests/published-build-plan.sh + shopt -s nullglob + for test in pkgbuilds/*/test.sh; do + bash "$test" + done ' diff --git a/pkgbuilds/omarchy-settings/test.sh b/pkgbuilds/omarchy-settings/test.sh new file mode 100644 index 0000000..5cb8c62 --- /dev/null +++ b/pkgbuilds/omarchy-settings/test.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# Exercise the real package functions with a minimal, synthetic runtime tree. +set -euo pipefail + +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/../..") +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT +fixture=$scratch/src/omarchy + +files=( + config/autostart/limine-snapper-notify.desktop + etc/fastfetch/config.jsonc + etc/mkinitcpio.conf.d/omarchy_hooks.conf + etc/mkinitcpio.conf.d/thunderbolt_module.conf + etc/limine-entry-tool.d/omarchy-defaults.conf + etc/limine-entry-tool.d/omarchy-uki.conf + etc/security/faillock.conf + etc/nsswitch.conf + etc/cups/cups-browsed.conf + etc/cups/cups-files.conf + etc/plymouth/plymouthd.conf + etc/sysctl.d/99-omarchy-sysctl.conf + default/uwsm/env.d/10-omarchy + default/environment.d/10-omarchy-fcitx.conf + default/fontconfig/conf.avail/50-omarchy.conf + default/xdg-terminal-exec/hyprland-xdg-terminals.list + default/applications/mimeapps.list + default/systemd/user/bt-agent.service + default/systemd/user/omarchy-sleep-lock.service + default/systemd/user/omarchy-recover-internal-monitor.service + default/systemd/user/omarchy-migrate-notify.service + default/systemd/user/omarchy-tailscale-receive.service + default/systemd/user/omarchy-fcitx5.service + default/systemd/user/omarchy-crash-watch.service + default/systemd/user/app.slice.d/10-oomd.conf + default/systemd/zram-generator.conf.d/90-omarchy.conf + default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf + default/systemd/system-sleep/unmount-fuse + default/bashrc + default/limine/default.conf + default/limine/limine.conf + default/snapper/root + default/sddm/omarchy/Main.qml + default/sddm/hyprland.lua + default/wayland-sessions/omarchy.desktop + default/plymouth/omarchy.plymouth + default/fonts/omarchy/omarchy.ttf + default/hypr/toggles/flags.lua + default/nautilus-python/extensions/localsend.py + default/nautilus-python/extensions/transcode.py + default/tensaku/state.toml + applications/example.desktop + bin/omarchy-upload-log + bin/omarchy-debug + bin/omarchy-debug-idle + logo.txt + logo.svg + icon.txt + icon.png +) +for path in "${files[@]}"; do + mkdir -p "$(dirname "$fixture/$path")" + printf 'fixture for %s\n' "$path" > "$fixture/$path" +done + +for recipe in omarchy-settings omarchy-settings-dev; do + for target_arch in aarch64 x86_64; do + ( + export CARCH=$target_arch OMARCHY_SRC=$fixture + export srcdir=$scratch/src pkgdir=$scratch/$recipe-$target_arch + backup=() + # shellcheck disable=SC1090 # Exercise each recipe's actual package function. + source "$BUILD_ROOT/pkgbuilds/$recipe/PKGBUILD" + package + + for path in etc/mkinitcpio.conf.d/omarchy_hooks.conf \ + etc/limine-entry-tool.d/omarchy-defaults.conf \ + etc/limine-entry-tool.d/omarchy-uki.conf; do + cmp "$fixture/$path" "$pkgdir/$path" + printf '%s\n' "${backup[@]}" | grep -Fxq "$path" + done + for template in default.conf limine.conf; do + cmp "$fixture/default/limine/$template" "$pkgdir/usr/share/omarchy/default/limine/$template" + done + # The installer owns the machine-specific live configuration. + [[ ! -e $pkgdir/etc/default/limine ]] + if printf '%s\n' "${backup[@]}" | grep -Fxq 'etc/default/limine'; then + echo 'FAIL: installer-owned Limine configuration is in backup metadata' >&2 + exit 1 + fi + cmp "$fixture/config/autostart/limine-snapper-notify.desktop" \ + "$pkgdir/etc/skel/.config/autostart/limine-snapper-notify.desktop" + cmp "$fixture/config/autostart/limine-snapper-notify.desktop" \ + "$pkgdir/usr/share/omarchy/config/autostart/limine-snapper-notify.desktop" + + thunderbolt=etc/mkinitcpio.conf.d/thunderbolt_module.conf + if [[ $CARCH == aarch64 ]]; then + [[ ! -e $pkgdir/$thunderbolt ]] + if printf '%s\n' "${backup[@]}" | grep -Fxq "$thunderbolt"; then + echo 'FAIL: removed ARM Thunderbolt config remains in backup metadata' >&2 + exit 1 + fi + else + cmp "$fixture/$thunderbolt" "$pkgdir/$thunderbolt" + printf '%s\n' "${backup[@]}" | grep -Fxq "$thunderbolt" + fi + echo "PASS: $recipe $CARCH retains boot configuration and matching backup metadata" + ) + done +done From 16b1a730f6627786f442ebccb7fbeaeeb68df5e5 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Wed, 16 Sep 2026 20:54:59 +0200 Subject: [PATCH 04/32] Run boot settings regression explicitly from tests --- .github/workflows/test.yml | 5 +---- .../test.sh => tests/settings-boot-config.sh | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) rename pkgbuilds/omarchy-settings/test.sh => tests/settings-boot-config.sh (98%) mode change 100644 => 100755 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4910361..f50f8f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,8 +47,5 @@ jobs: ./bin/omarchy-release self-test ./tests/partial-release.sh ./tests/published-build-plan.sh - shopt -s nullglob - for test in pkgbuilds/*/test.sh; do - bash "$test" - done + ./tests/settings-boot-config.sh ' diff --git a/pkgbuilds/omarchy-settings/test.sh b/tests/settings-boot-config.sh old mode 100644 new mode 100755 similarity index 98% rename from pkgbuilds/omarchy-settings/test.sh rename to tests/settings-boot-config.sh index 5cb8c62..b9dcff7 --- a/pkgbuilds/omarchy-settings/test.sh +++ b/tests/settings-boot-config.sh @@ -2,7 +2,7 @@ # Exercise the real package functions with a minimal, synthetic runtime tree. set -euo pipefail -BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/../..") +BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") scratch=$(mktemp -d) trap 'rm -rf "$scratch"' EXIT fixture=$scratch/src/omarchy From 3efb5c8e2ad8667413218722496dff842316fe65 Mon Sep 17 00:00:00 2001 From: Jeremy Dixon Date: Fri, 4 Sep 2026 13:28:13 -0400 Subject: [PATCH 05/32] Update tobi-try to 1.10.1 --- pkgbuilds/tobi-try/.omarchy/package.json | 13 ++++++++++++- pkgbuilds/tobi-try/PKGBUILD | 15 +++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pkgbuilds/tobi-try/.omarchy/package.json b/pkgbuilds/tobi-try/.omarchy/package.json index 2a9719d..504b63a 100644 --- a/pkgbuilds/tobi-try/.omarchy/package.json +++ b/pkgbuilds/tobi-try/.omarchy/package.json @@ -1,3 +1,14 @@ { - "source": "local" + "source": "local", + "upstream": { + "git_tags": "https://github.com/tobi/try.git", + "tag_pattern": "v{pkgver}", + "sources": { + "any": [ + "https://raw.githubusercontent.com/tobi/try/{tag}/try.rb", + "https://raw.githubusercontent.com/tobi/try/{tag}/lib/fuzzy.rb", + "https://raw.githubusercontent.com/tobi/try/{tag}/lib/tui.rb" + ] + } + } } diff --git a/pkgbuilds/tobi-try/PKGBUILD b/pkgbuilds/tobi-try/PKGBUILD index 0afb7d2..17c0fcc 100644 --- a/pkgbuilds/tobi-try/PKGBUILD +++ b/pkgbuilds/tobi-try/PKGBUILD @@ -1,9 +1,8 @@ # Maintainer: dhh pkgname='tobi-try' -pkgver=1.8.1 -_subver=d1bc484cc31a34db3d287550f4800e9a6e56bacd -pkgrel=3 +pkgver=1.10.1 +pkgrel=1 pkgdesc='Fresh directories for every vibe.' url='https://github.com/tobi/try' arch=('any') @@ -13,12 +12,12 @@ provides=('try') conflicts=('try') options=('!debug') -source=("try-${pkgver}.rb::https://raw.githubusercontent.com/tobi/try/${_subver}/try.rb" - "fuzzy.rb::https://raw.githubusercontent.com/tobi/try/${_subver}/lib/fuzzy.rb" - "tui.rb::https://raw.githubusercontent.com/tobi/try/${_subver}/lib/tui.rb") -sha256sums=('55a968dc5b1536b338d8f96693576c8cb19ca6bcabbca9138591cd0518486b02' +source=("try-${pkgver}.rb::https://raw.githubusercontent.com/tobi/try/v${pkgver}/try.rb" + "fuzzy.rb::https://raw.githubusercontent.com/tobi/try/v${pkgver}/lib/fuzzy.rb" + "tui.rb::https://raw.githubusercontent.com/tobi/try/v${pkgver}/lib/tui.rb") +sha256sums=('2e52bcd81244ff59e4a51dda5d7ba446dabaa0d1c5e23f02902431ca8d091be9' 'cf815ed12c8147bbc7f67008cfc1f3fd05df1638ff290e2079188f1b9bf8f190' - 'b62d2b61445d8266f064e2809e06ebc0a25da401ee5a13dc3a73d215c0a1ea71') + '0ea1b79975f2bcf36dbb29c5b76738e7cac81cedde24bf3bde85d8e2a42fd225') build() { cd "${srcdir}" From d2d79ce8fd7427cc272ff1c54ff9ed5c80ad9f9e Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Mon, 21 Sep 2026 14:16:14 -0500 Subject: [PATCH 06/32] Stop the Hermes Desktop launcher reconciling a mise install The launcher called omarchy-install-hermes-cli with no arguments on every start to remove a mise-built Hermes left beside the app. Omarchy no longer builds Hermes through mise: it sets the app's runtime up before the app is opened, and the installer now only answers a named --check or --now, so the call had nothing left to reconcile and only printed usage. The launch check keeps a forbidden stand-in for that command on its PATH, so a launcher that reaches for it again fails the build. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Codex XHigh --- pkgbuilds/hermes-desktop/PKGBUILD | 6 +++--- pkgbuilds/hermes-desktop/hermes-desktop.sh | 5 ----- pkgbuilds/hermes-desktop/runtime-test.py | 6 ++++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index bd41ea2..b37a12a 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -5,7 +5,7 @@ pkgname=hermes-desktop pkgver=2026.9.7 -pkgrel=1 +pkgrel=2 pkgdesc='Native desktop shell for Hermes Agent' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' @@ -75,11 +75,11 @@ source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz 'runtime.patch' 'runtime-test.py') sha256sums=('907c2a72db1c5dd637ea8eeae97f4cb5b32cef615c17258f6b190924ec5bf688' - '094d5f3191109a80eea9f23053b78a2e00dbecf90d62d1ca04c8e48866251469' + 'c68233f93387251f08537559c072c9ec36ba9a304b1669decc56df17c464b252' '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52' '9d5015d1be762a901f8f64319981ae862e9852fa5cb9a22a2ba1e691f90430a2' - '7337a12c71e8091ad5fc2e879e922c9cb1706c65f81b59d6dd70b12123dc7c00') + '514a5e7ab2b7262141a2588c5b5036832cb4ba789a9578b8b50b6d79a9d63deb') build() { cd "${srcdir}/${_srcdir}" diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 40822b8..db23758 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -4,11 +4,6 @@ set -euo pipefail unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME export HERMES_DESKTOP_IGNORE_EXISTING=1 -# Reconcile direct package installs and interrupted Omarchy setup as well. -if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then - omarchy-install-hermes-cli >/dev/null 2>&1 || true -fi - hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}") parent=${hermes_home%/*} if [[ ${parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then diff --git a/pkgbuilds/hermes-desktop/runtime-test.py b/pkgbuilds/hermes-desktop/runtime-test.py index cc08e07..1f0fe91 100644 --- a/pkgbuilds/hermes-desktop/runtime-test.py +++ b/pkgbuilds/hermes-desktop/runtime-test.py @@ -75,9 +75,11 @@ def with_hermes_node_path(env=None): cli.write_text(forbidden) cli.chmod(0o755) (cli.parent / "python").symlink_to(sys.executable) + # The launcher used to call Omarchy's installer on every start; a launcher + # that reaches for it, or for sudo, fails here. for command in ("sudo", "omarchy-install-hermes-cli"): target = mock_bin / command - target.write_text(forbidden if command == "sudo" else '#!/bin/bash\nexit 0\n') + target.write_text(forbidden) target.chmod(0o755) output = root / "launch.json" @@ -92,7 +94,7 @@ def with_hermes_node_path(env=None): assert result == {"args": ["--disable-setuid-sandbox", *expected_args], "home": str(home / ".hermes"), "store": store, "gpu": gpu, "ozone": ozone, "cwd": str(home), "inherited": []}, result - assert not forbidden_output.exists(), "launcher invoked CLI or sudo" + assert not forbidden_output.exists(), "launcher invoked the Omarchy installer or sudo" output.unlink() wayland = {"WAYLAND_DISPLAY": "wayland-1"} From 1e1d52430586461e2ca02260cf8d82aaa0c8c938 Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Tue, 22 Sep 2026 20:22:55 -0400 Subject: [PATCH 07/32] Allow normal Steam update and repair checks --- pkgbuilds/omarchy-steam-fex/PKGBUILD | 6 +++--- pkgbuilds/omarchy-steam-fex/README.md | 2 +- pkgbuilds/omarchy-steam-fex/omarchy-launch-steam | 6 ------ pkgbuilds/omarchy-steam-fex/test-launcher.py | 5 ++--- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/pkgbuilds/omarchy-steam-fex/PKGBUILD b/pkgbuilds/omarchy-steam-fex/PKGBUILD index 02d2e9d..2921ddf 100644 --- a/pkgbuilds/omarchy-steam-fex/PKGBUILD +++ b/pkgbuilds/omarchy-steam-fex/PKGBUILD @@ -1,15 +1,15 @@ pkgname=omarchy-steam-fex pkgver=1.0.0 -pkgrel=3 +pkgrel=4 pkgdesc='Steam launcher with login workarounds for Apple Silicon using muvm and FEX' arch=('aarch64') url='https://github.com/omacom/omarchy-pkgs/tree/master/pkgbuilds/omarchy-steam-fex' license=('MIT') checkdepends=('python') source=('omarchy-launch-steam' 'LICENSE' 'test-launcher.py') -sha256sums=('37ad2e8a863e90c2b3248f22d93b548c6070f396f631ad39cefc4745478515b4' +sha256sums=('5e115fd7ec457f7e05a006c95760c122dee7e4bb76edc53af4419a3474ac7901' '717ba1949502290f8e47688ae2e323acd06c8ca47aec9f7596b15f678c1af4a2' - 'fbab0f88ecdf3238bfb95a1523eef7de2ded2928c91c90e4e06435696dd86cdb') + 'efcc624b0a914aec51007cf743197822058b317748c4d125f7ab6a1d00632252') check() { python test-launcher.py diff --git a/pkgbuilds/omarchy-steam-fex/README.md b/pkgbuilds/omarchy-steam-fex/README.md index 9760b17..6f7bbde 100644 --- a/pkgbuilds/omarchy-steam-fex/README.md +++ b/pkgbuilds/omarchy-steam-fex/README.md @@ -2,7 +2,7 @@ `omarchy-steam-fex` provides `omarchy-launch-steam` for the Asahi Linux `steam`, `muvm`, and `FEX-Emu` stack. Those runtime packages come from `asahi-alarm`; `FEX-Emu` provides `FEXBash`. The package is restricted to aarch64 and assumes that stack's `~/.local/share/fex-steam/steam-launcher/bin_steam.sh` layout. -The launcher runs Steam through `muvm` and `FEXBash` with the CEF occlusion workaround. Once Steam's client files are present, it also disables bootstrap verification and repair and patches the Steam UI network initialization block that can leave login waiting indefinitely. Initial bootstrap keeps the normal bootstrap flags. If the FEX launcher is unavailable, it falls back to `steam`. +The launcher runs Steam through `muvm` and `FEXBash` with the CEF occlusion workaround. Once Steam's client files are present, it patches the Steam UI network initialization block that can leave login waiting indefinitely. Both initial and subsequent launches leave Steam's normal bootstrap, update, verification, and repair behavior enabled. If the FEX launcher is unavailable, it falls back to `steam`. `omarchy-launch-steam --prepare` creates the current user's desktop override with `Exec=omarchy-launch-steam %U` if it is missing, and applies the same UI patch. Existing overrides and symlinks are preserved on preparation and launch. If an existing override uses a different command, edit its `Exec` entry to use `omarchy-launch-steam %U` when you want this launcher. Each matching chunk is backed up as `.omarchy-bak` before its first patch; existing backups are preserved. The regex matches the original network initialization block, so a patched block is not changed again. An unrelated connected-state assignment elsewhere in the chunk does not suppress the fix. The regex depends on Valve's client code and may need updating when that code changes. diff --git a/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam b/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam index c46fdf7..0baa257 100755 --- a/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam +++ b/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam @@ -96,12 +96,6 @@ if [[ $(uname -m) == aarch64 ]] && command -v muvm >/dev/null && command -v FEXB steam_args=(-cef-force-occlusion) if steam_client_ready; then prepare_asahi - steam_args+=( - -noverifyfiles - -nobootstrapupdate - -skipinitialbootstrap - -norepairfiles - ) else write_steam_desktop fi diff --git a/pkgbuilds/omarchy-steam-fex/test-launcher.py b/pkgbuilds/omarchy-steam-fex/test-launcher.py index 02e7c65..743562c 100644 --- a/pkgbuilds/omarchy-steam-fex/test-launcher.py +++ b/pkgbuilds/omarchy-steam-fex/test-launcher.py @@ -25,7 +25,6 @@ ORIGINAL = ( '(0,Ab.cd)("System.Network.RegisterForConnectivityTestChanges")&&SteamClient.System.Network.RegisterForConnectivityTestChanges(this.OnConnectivityTestStateChanged),' 't||(this.m_bIsAwaitingInitialNetworkState=!1);after();' ) -SKIP_BOOTSTRAP = ['-noverifyfiles', '-nobootstrapupdate', '-skipinitialbootstrap', '-norepairfiles'] MOCK = ''' import json, os @@ -200,11 +199,11 @@ class SteamLauncherTests(unittest.TestCase): self.assert_fex(self.run_launcher(), ['-cef-force-occlusion'], []) self.assertEqual(path.read_text(), ORIGINAL) - def test_ready_launch_patches_and_disables_bootstrap(self): + def test_ready_launch_patches_and_keeps_updates_enabled(self): path = self.chunk() self.client_ready() args = ['steam://open/main'] - self.assert_fex(self.run_launcher(*args), ['-cef-force-occlusion', *SKIP_BOOTSTRAP], args) + self.assert_fex(self.run_launcher(*args), ['-cef-force-occlusion'], args) self.assertIn('m_bIsConnectedToANetwork=!0', path.read_text()) self.assert_desktop() From 26f47a021f0468d46b65561a6ca808d2c120e3f6 Mon Sep 17 00:00:00 2001 From: Jim Martin Date: Tue, 22 Sep 2026 20:13:52 -0500 Subject: [PATCH 08/32] Carry NVIDIA DisplayPort detach fix for aarch64 --- .../nvidia-open-dkms/.omarchy/package.json | 6 ++ .../0001-allow-unplugged-dp-detach.patch | 37 ++++++++++ .../0002-clarify-unplugged-dp-detach.patch | 23 ++++++ pkgbuilds/nvidia-open-dkms/LICENSE | 12 +++ pkgbuilds/nvidia-open-dkms/PKGBUILD | 74 +++++++++++++++++++ pkgbuilds/nvidia-open-dkms/README.package.md | 13 ++++ 6 files changed, 165 insertions(+) create mode 100644 pkgbuilds/nvidia-open-dkms/.omarchy/package.json create mode 100644 pkgbuilds/nvidia-open-dkms/0001-allow-unplugged-dp-detach.patch create mode 100644 pkgbuilds/nvidia-open-dkms/0002-clarify-unplugged-dp-detach.patch create mode 100644 pkgbuilds/nvidia-open-dkms/LICENSE create mode 100644 pkgbuilds/nvidia-open-dkms/PKGBUILD create mode 100644 pkgbuilds/nvidia-open-dkms/README.package.md diff --git a/pkgbuilds/nvidia-open-dkms/.omarchy/package.json b/pkgbuilds/nvidia-open-dkms/.omarchy/package.json new file mode 100644 index 0000000..7ddd330 --- /dev/null +++ b/pkgbuilds/nvidia-open-dkms/.omarchy/package.json @@ -0,0 +1,6 @@ +{ + "source": "local", + "channels": [ + "edge" + ] +} diff --git a/pkgbuilds/nvidia-open-dkms/0001-allow-unplugged-dp-detach.patch b/pkgbuilds/nvidia-open-dkms/0001-allow-unplugged-dp-detach.patch new file mode 100644 index 0000000..1e6e336 --- /dev/null +++ b/pkgbuilds/nvidia-open-dkms/0001-allow-unplugged-dp-detach.patch @@ -0,0 +1,37 @@ +From bd5d6119ca1ed030ad26d06d1b3e980873ff0336 Mon Sep 17 00:00:00 2001 +From: Martin Stark <901824+martinstark@users.noreply.github.com> +Date: Sun, 13 Sep 2026 22:32:38 +0200 +Subject: [PATCH 1/2] fix(displayport): allow detach when sink is unplugged + +The HPD check in dpPreModeset() rejects detach-only requests after unplug. This skips DP library cleanup while NVKMS advances its head bookkeeping, leaving stale attached groups that can block subsequent link training. + +Allow detach-only requests when HPD is low. Reject requests with an attachment target on any selected head, preserving the connector/discovery guards and forced-connected and dynamic-mux exceptions. +--- + src/common/displayport/src/dp_connectorimpl.cpp | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/src/common/displayport/src/dp_connectorimpl.cpp b/src/common/displayport/src/dp_connectorimpl.cpp +index 99e0e97..7451e6c 100644 +--- a/src/common/displayport/src/dp_connectorimpl.cpp ++++ b/src/common/displayport/src/dp_connectorimpl.cpp +@@ -3861,11 +3861,17 @@ void ConnectorImpl::dpPreModeset(const DpPreModesetParams ¶ms) + return; + } + +- // Skip gating modeset on HPD for DDS panels ++ // Allow detach bookkeeping even when HPD is low. + if(!previousPlugged && !bClientForcedConnected && !main->isInternalPanelDynamicMuxCapable()) + { +- DP_ASSERT(0 && "DPCONN> dpPreModeset called when Plugged State is false!"); +- return; ++ for (NvU32 i = 0; i < NV_MAX_HEADS; i++) ++ { ++ if ((params.headMask & NVBIT(i)) && params.head[i].pTarget != NULL) ++ { ++ DP_ASSERT(0 && "DPCONN> dpPreModeset attach called when Plugged State is false!"); ++ return; ++ } ++ } + } + + this->bFECEnable |= this->needToEnableFEC(params); diff --git a/pkgbuilds/nvidia-open-dkms/0002-clarify-unplugged-dp-detach.patch b/pkgbuilds/nvidia-open-dkms/0002-clarify-unplugged-dp-detach.patch new file mode 100644 index 0000000..cbcf5b0 --- /dev/null +++ b/pkgbuilds/nvidia-open-dkms/0002-clarify-unplugged-dp-detach.patch @@ -0,0 +1,23 @@ +From a2e8b26b20dfb6f2fa3cb8985e3f1126cba38aae Mon Sep 17 00:00:00 2001 +From: Martin Stark <901824+martinstark@users.noreply.github.com> +Date: Mon, 14 Sep 2026 10:43:16 +0200 +Subject: [PATCH 2/2] Clarify DDS exception and unplugged detach bookkeeping + +--- + src/common/displayport/src/dp_connectorimpl.cpp | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/common/displayport/src/dp_connectorimpl.cpp b/src/common/displayport/src/dp_connectorimpl.cpp +index 7451e6c..0e81070 100644 +--- a/src/common/displayport/src/dp_connectorimpl.cpp ++++ b/src/common/displayport/src/dp_connectorimpl.cpp +@@ -3861,7 +3861,8 @@ void ConnectorImpl::dpPreModeset(const DpPreModesetParams ¶ms) + return; + } + +- // Allow detach bookkeeping even when HPD is low. ++ // Skip gating modeset on HPD for DDS panels. ++ // Allow HPD-low detach bookkeeping; notifyLongPulse() permits detach. + if(!previousPlugged && !bClientForcedConnected && !main->isInternalPanelDynamicMuxCapable()) + { + for (NvU32 i = 0; i < NV_MAX_HEADS; i++) diff --git a/pkgbuilds/nvidia-open-dkms/LICENSE b/pkgbuilds/nvidia-open-dkms/LICENSE new file mode 100644 index 0000000..b87c5e4 --- /dev/null +++ b/pkgbuilds/nvidia-open-dkms/LICENSE @@ -0,0 +1,12 @@ +Copyright Arch Linux Contributors + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/pkgbuilds/nvidia-open-dkms/PKGBUILD b/pkgbuilds/nvidia-open-dkms/PKGBUILD new file mode 100644 index 0000000..e4c1617 --- /dev/null +++ b/pkgbuilds/nvidia-open-dkms/PKGBUILD @@ -0,0 +1,74 @@ +# Maintainer: Sven-Hendrik Haase +# Maintainer: Peter Jung +# Contributor: James Rayner +# Contributor: Vasiliy Stelmachenok +# Contributor: Thomas Baechler + +# ARM-only carry of NVIDIA/open-gpu-kernel-modules#1359 by Martin Stark. +# Remove this overlay after a fixed Arch Linux ARM driver is validated. +pkgname=nvidia-open-dkms +pkgver=615.71.09 +pkgrel=1.2 +pkgdesc="NVIDIA open kernel modules - module sources" +arch=('aarch64') +url="https://www.nvidia.com/" +license=('MIT AND GPL-2.0-only') +depends=('dkms' "nvidia-utils=$pkgver") +conflicts=('nvidia-open' 'NVIDIA-MODULE') +provides=('nvidia-open' 'NVIDIA-MODULE' 'nvidia-dkms') +replaces=('nvidia-dkms') +options=('!strip') +_pkg_open="NVIDIA-kernel-module-source-${pkgver}" +source=("https://download.nvidia.com/XFree86/NVIDIA-kernel-module-source/${_pkg_open}.tar.xz" + '0001-allow-unplugged-dp-detach.patch' + '0002-clarify-unplugged-dp-detach.patch') +sha512sums=('0b32c1aaa5ed261bdee7232d5e5d293e53c42ac9896f49c4be4e6b9b6bce1370cb69a7f7fde5531a7537d5e925d651c36bcb512a2f827c2d2cb26ede33f7c057' + '48f802423399ce84a430b7accf10ea50847ea3fafcfe251e3bad2f741af9ca2162408a3109eb3c9fcdbe35ec35154a3dbcc8fe457ed617487c009abf2c9fc89a' + 'a3ff65cb58d72815b272726c38d45148dfba73ea2a0d88672960d8f706c74e7c2b40ec599783acec46ba18433af45f38547a09b2caa985bd71895a64780c00da') + +prepare() { + # Attempt to make builds reproducible + sed -i "s/^ HOSTNAME.*/ HOSTNAME = echo archlinux/" "${srcdir}/${_pkg_open}/utils.mk" + sed -i "s/^WHOAMI.*/WHOAMI = echo archlinux-builder/" "${srcdir}/${_pkg_open}/utils.mk" + sed -i "s/^DATE.*/DATE = date -r version.mk/" "${srcdir}/${_pkg_open}/utils.mk" + + for conf in "${srcdir}/${_pkg_open}/kernel-open/dkms.conf"; do + sed -i "s/__VERSION_STRING/${pkgver}/" "$conf" + sed -i 's/__JOBS/`nproc`/' "$conf" + sed -i 's/__EXCLUDE_MODULES//' "$conf" + sed -i 's/__DKMS_MODULES//' "$conf" + sed -i 's/NV_EXCLUDE_BUILD_MODULES/IGNORE_PREEMPT_RT_PRESENCE=1 NV_EXCLUDE_BUILD_MODULES/' "$conf" + sed -i '$i\ +BUILT_MODULE_NAME[0]="nvidia"\ +DEST_MODULE_LOCATION[0]="/kernel/drivers/video"\ +BUILT_MODULE_NAME[1]="nvidia-uvm"\ +DEST_MODULE_LOCATION[1]="/kernel/drivers/video"\ +BUILT_MODULE_NAME[2]="nvidia-modeset"\ +DEST_MODULE_LOCATION[2]="/kernel/drivers/video"\ +BUILT_MODULE_NAME[3]="nvidia-drm"\ +DEST_MODULE_LOCATION[3]="/kernel/drivers/video"\ +BUILT_MODULE_NAME[4]="nvidia-peermem"\ +DEST_MODULE_LOCATION[4]="/kernel/drivers/video"' "$conf" + done + + # Additional parameters for open kernel modules + cat <>"${srcdir}/${_pkg_open}/kernel-open/dkms.conf" +BUILT_MODULE_LOCATION[0]="kernel-open" +BUILT_MODULE_LOCATION[1]="kernel-open" +BUILT_MODULE_LOCATION[2]="kernel-open" +BUILT_MODULE_LOCATION[3]="kernel-open" +BUILT_MODULE_LOCATION[4]="kernel-open" +EOF + + # Exact PR head a2e8b26b20dfb6f2fa3cb8985e3f1126cba38aae. + cd "${srcdir}/${_pkg_open}" + patch --batch --fuzz=0 -p1 < "$srcdir/0001-allow-unplugged-dp-detach.patch" + patch --batch --fuzz=0 -p1 < "$srcdir/0002-clarify-unplugged-dp-detach.patch" +} + +package() { + install -dm755 "$pkgdir/usr/src" + cp -dr --no-preserve=ownership "$srcdir/$_pkg_open" "$pkgdir/usr/src/nvidia-$pkgver" + mv "$pkgdir/usr/src/nvidia-$pkgver/kernel-open/dkms.conf" "$pkgdir/usr/src/nvidia-$pkgver/dkms.conf" + install -Dm644 "$srcdir/$_pkg_open/COPYING" "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} diff --git a/pkgbuilds/nvidia-open-dkms/README.package.md b/pkgbuilds/nvidia-open-dkms/README.package.md new file mode 100644 index 0000000..79386c7 --- /dev/null +++ b/pkgbuilds/nvidia-open-dkms/README.package.md @@ -0,0 +1,13 @@ +# NVIDIA ARM DisplayPort detach fix + +ARM-only edge package carrying Martin Stark's pending +[NVIDIA PR #1359](https://github.com/NVIDIA/open-gpu-kernel-modules/pull/1359) +to fix DisplayPort disconnect cleanup in 615.71.09. Based on +[Arch's DKMS recipe](https://gitlab.archlinux.org/archlinux/packaging/packages/nvidia-utils/-/commit/f9ae10b379f8b1d0832ec92bca1c12072aa123e9). + +Requires `[omarchy]` before `[extra]` and matching `nvidia-utils=615.71.09`. +Update both NVIDIA packages together; automatic version tracking is disabled. + +Remove this recipe and the published package/database entry once a fixed +Arch Linux ARM driver is validated. A stale package in the earlier repository +can block driver updates. From 4c3f31e972519f3a8cc93d43442b514fa0fe7580 Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Thu, 24 Sep 2026 07:13:06 -0400 Subject: [PATCH 09/32] Preserve Steam UI patch during update checks --- pkgbuilds/omarchy-steam-fex/PKGBUILD | 4 ++-- pkgbuilds/omarchy-steam-fex/README.md | 2 +- pkgbuilds/omarchy-steam-fex/omarchy-launch-steam | 3 +++ pkgbuilds/omarchy-steam-fex/test-launcher.py | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pkgbuilds/omarchy-steam-fex/PKGBUILD b/pkgbuilds/omarchy-steam-fex/PKGBUILD index 2921ddf..a0c77f8 100644 --- a/pkgbuilds/omarchy-steam-fex/PKGBUILD +++ b/pkgbuilds/omarchy-steam-fex/PKGBUILD @@ -7,9 +7,9 @@ url='https://github.com/omacom/omarchy-pkgs/tree/master/pkgbuilds/omarchy-steam- license=('MIT') checkdepends=('python') source=('omarchy-launch-steam' 'LICENSE' 'test-launcher.py') -sha256sums=('5e115fd7ec457f7e05a006c95760c122dee7e4bb76edc53af4419a3474ac7901' +sha256sums=('d00742b36ba3d630b43cfe5ab7ac665625e113651d487630e2e440535c8dab64' '717ba1949502290f8e47688ae2e323acd06c8ca47aec9f7596b15f678c1af4a2' - 'efcc624b0a914aec51007cf743197822058b317748c4d125f7ab6a1d00632252') + '4584557d52d2a56d1edf082c0d0c0deaa3eed2959b02e6eb681c9dd36bc94f3f') check() { python test-launcher.py diff --git a/pkgbuilds/omarchy-steam-fex/README.md b/pkgbuilds/omarchy-steam-fex/README.md index 6f7bbde..df55174 100644 --- a/pkgbuilds/omarchy-steam-fex/README.md +++ b/pkgbuilds/omarchy-steam-fex/README.md @@ -2,7 +2,7 @@ `omarchy-steam-fex` provides `omarchy-launch-steam` for the Asahi Linux `steam`, `muvm`, and `FEX-Emu` stack. Those runtime packages come from `asahi-alarm`; `FEX-Emu` provides `FEXBash`. The package is restricted to aarch64 and assumes that stack's `~/.local/share/fex-steam/steam-launcher/bin_steam.sh` layout. -The launcher runs Steam through `muvm` and `FEXBash` with the CEF occlusion workaround. Once Steam's client files are present, it patches the Steam UI network initialization block that can leave login waiting indefinitely. Both initial and subsequent launches leave Steam's normal bootstrap, update, verification, and repair behavior enabled. If the FEX launcher is unavailable, it falls back to `steam`. +The launcher runs Steam through `muvm` and `FEXBash` with the CEF occlusion workaround. Once Steam's client files are present, it patches the Steam UI network initialization block that can leave login waiting indefinitely. Initial bootstrap uses Steam's normal checks. Subsequent launches keep update and bootstrap checks enabled but pass `-noverifyfiles`: Steam otherwise detects the modified UI file, replaces it, and loses the login fix. After a client update replaces the UI file, launch again to reapply the patch. If the FEX launcher is unavailable, it falls back to `steam`. `omarchy-launch-steam --prepare` creates the current user's desktop override with `Exec=omarchy-launch-steam %U` if it is missing, and applies the same UI patch. Existing overrides and symlinks are preserved on preparation and launch. If an existing override uses a different command, edit its `Exec` entry to use `omarchy-launch-steam %U` when you want this launcher. Each matching chunk is backed up as `.omarchy-bak` before its first patch; existing backups are preserved. The regex matches the original network initialization block, so a patched block is not changed again. An unrelated connected-state assignment elsewhere in the chunk does not suppress the fix. The regex depends on Valve's client code and may need updating when that code changes. diff --git a/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam b/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam index 0baa257..40e5249 100755 --- a/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam +++ b/pkgbuilds/omarchy-steam-fex/omarchy-launch-steam @@ -95,6 +95,9 @@ if [[ $(uname -m) == aarch64 ]] && command -v muvm >/dev/null && command -v FEXB if [[ -f $launcher ]]; then steam_args=(-cef-force-occlusion) if steam_client_ready; then + # Steam's verifier replaces our patched UI chunk on every launch. + # Keep update/bootstrap checks enabled while preserving that patch. + steam_args+=(-noverifyfiles) prepare_asahi else write_steam_desktop diff --git a/pkgbuilds/omarchy-steam-fex/test-launcher.py b/pkgbuilds/omarchy-steam-fex/test-launcher.py index 743562c..5b511c6 100644 --- a/pkgbuilds/omarchy-steam-fex/test-launcher.py +++ b/pkgbuilds/omarchy-steam-fex/test-launcher.py @@ -199,11 +199,11 @@ class SteamLauncherTests(unittest.TestCase): self.assert_fex(self.run_launcher(), ['-cef-force-occlusion'], []) self.assertEqual(path.read_text(), ORIGINAL) - def test_ready_launch_patches_and_keeps_updates_enabled(self): + def test_ready_launch_patches_and_keeps_update_checks_enabled(self): path = self.chunk() self.client_ready() args = ['steam://open/main'] - self.assert_fex(self.run_launcher(*args), ['-cef-force-occlusion'], args) + self.assert_fex(self.run_launcher(*args), ['-cef-force-occlusion', '-noverifyfiles'], args) self.assertIn('m_bIsConnectedToANetwork=!0', path.read_text()) self.assert_desktop() From 9719f061e6d8ec891cec1e4ad5bf20e16a29ac0d Mon Sep 17 00:00:00 2001 From: Jacob Mink Date: Thu, 24 Sep 2026 14:05:50 -0500 Subject: [PATCH 10/32] Bump OpenVINO GenAI to 2026.4.0.0 --- pkgbuilds/openvino-genai/PKGBUILD | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pkgbuilds/openvino-genai/PKGBUILD b/pkgbuilds/openvino-genai/PKGBUILD index 41f52cd..c763d2a 100644 --- a/pkgbuilds/openvino-genai/PKGBUILD +++ b/pkgbuilds/openvino-genai/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: Spencer Bull pkgname=openvino-genai -pkgver=2026.3.1.0 -pkgrel=3 +pkgver=2026.4.0.0 +pkgrel=1 pkgdesc="OpenVINO GenAI C and C++ runtime libraries" arch=('x86_64') url="https://github.com/openvinotoolkit/openvino.genai" @@ -12,7 +12,7 @@ depends=( 'gcc-libs' 'glibc' 'onetbb' - 'openvino=2026.3.1' # Includes libopenvino_c.so. + 'openvino=2026.4.0' # Includes libopenvino_c.so. ) makedepends=( 'cmake' @@ -25,24 +25,19 @@ optdepends=( 'openvino-intel-npu-plugin: inference on Intel NPUs' ) -_commit=56d9685302da2fc5cc7c9689cfab500fd0660a02 +_commit=7ea2546852a382cd16bd22dea0cfad2db70ed744 source=( "openvino.genai::git+$url.git#commit=$_commit" 'gcc-16-char8_t.patch' - 'format-template-linkage.patch::https://github.com/openvinotoolkit/openvino.genai/commit/398fbc1450f7485368edf52dd82f45eba215d6c9.patch' ) sha256sums=( 'SKIP' '6e685c1e45d4b2314fd55e2f791846cc9086413ba62a6bb5e31be5a5878a243c' - '8c2a3e4bf1d33e00b780da7bec2d5e40b6fd26a4e518359d6ff7c59ca7f649e3' ) prepare() { cd openvino.genai patch -Np1 -i "$srcdir/gcc-16-char8_t.patch" - # Backport upstream 398fbc14: GCC -O3 can otherwise leave format - # unresolved in libopenvino_genai.so. - patch -Np1 -i "$srcdir/format-template-linkage.patch" git submodule update --init --recursive } From c330ee34a1521d68f8062a99ace16032dae2b4f1 Mon Sep 17 00:00:00 2001 From: Jacob Mink Date: Thu, 24 Sep 2026 14:26:29 -0500 Subject: [PATCH 11/32] Fix OpenVINO GenAI build with GCC 16 --- pkgbuilds/openvino-genai/PKGBUILD | 3 +++ .../linear-attention-guard-constructor.patch | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 pkgbuilds/openvino-genai/linear-attention-guard-constructor.patch diff --git a/pkgbuilds/openvino-genai/PKGBUILD b/pkgbuilds/openvino-genai/PKGBUILD index c763d2a..1b59e77 100644 --- a/pkgbuilds/openvino-genai/PKGBUILD +++ b/pkgbuilds/openvino-genai/PKGBUILD @@ -29,15 +29,18 @@ _commit=7ea2546852a382cd16bd22dea0cfad2db70ed744 source=( "openvino.genai::git+$url.git#commit=$_commit" 'gcc-16-char8_t.patch' + 'linear-attention-guard-constructor.patch' ) sha256sums=( 'SKIP' '6e685c1e45d4b2314fd55e2f791846cc9086413ba62a6bb5e31be5a5878a243c' + '61958d64dd7510ff0cb6da010f26fddba3e5dbc38691828163a63d54951d1b2a' ) prepare() { cd openvino.genai patch -Np1 -i "$srcdir/gcc-16-char8_t.patch" + patch -Np1 -i "$srcdir/linear-attention-guard-constructor.patch" git submodule update --init --recursive } diff --git a/pkgbuilds/openvino-genai/linear-attention-guard-constructor.patch b/pkgbuilds/openvino-genai/linear-attention-guard-constructor.patch new file mode 100644 index 0000000..5c97188 --- /dev/null +++ b/pkgbuilds/openvino-genai/linear-attention-guard-constructor.patch @@ -0,0 +1,14 @@ +diff --git a/src/cpp/src/continuous_batching/pipeline_impl.cpp b/src/cpp/src/continuous_batching/pipeline_impl.cpp +--- a/src/cpp/src/continuous_batching/pipeline_impl.cpp ++++ b/src/cpp/src/continuous_batching/pipeline_impl.cpp +@@ -529,6 +529,10 @@ void ContinuousBatchingPipeline::ContinuousBatchingImpl::step() { + const Scheduler::Output& m_scheduler_output; + bool m_armed = true; + ++ BorrowedLinearAttentionRowsGuard(ContinuousBatchingImpl& impl, const Scheduler::Output& scheduler_output) ++ : m_impl(impl), m_scheduler_output(scheduler_output) { ++ } ++ + ~BorrowedLinearAttentionRowsGuard() { + if (m_armed) { + m_impl._release_linear_attention_borrowed_rows(m_scheduler_output); From fd78ec0e14e5b835ebeda8b444be8bb129ccbd00 Mon Sep 17 00:00:00 2001 From: Jacob Mink Date: Thu, 24 Sep 2026 14:47:58 -0500 Subject: [PATCH 12/32] Add OpenVINO GenAI optional dependency to Omawake --- pkgbuilds/omawake-bin/PKGBUILD | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/omawake-bin/PKGBUILD b/pkgbuilds/omawake-bin/PKGBUILD index 2ee1d54..4571de1 100644 --- a/pkgbuilds/omawake-bin/PKGBUILD +++ b/pkgbuilds/omawake-bin/PKGBUILD @@ -4,7 +4,7 @@ pkgname=omawake-bin _pkgname=${pkgname%-bin} pkgver=0.0.3 _upstream_ver=0.0.3 -pkgrel=4 +pkgrel=5 pkgdesc='Configurable local wake-word daemon (pre-built binary)' arch=('x86_64' 'aarch64') url='https://github.com/jacob-vincent-mink/omawake' @@ -16,7 +16,8 @@ depends=( ) optdepends=( 'pipewire-audio: PipeWire audio support' - 'openvino: Intel runtime for an externally supplied OpenVINO provider bundle' + 'openvino: Intel runtime for the OpenVINO provider' + 'openvino-genai: OpenVINO GenAI C provider for Whisper' 'openvino-intel-gpu-plugin: Intel GPU device support for OpenVINO' 'openvino-intel-npu-plugin: Intel NPU device support for OpenVINO' 'cuda: NVIDIA CUDA libraries for an external CUDA runtime bundle' From a1549d8724db3639c44021c19e98ef59b68491ae Mon Sep 17 00:00:00 2001 From: Jacob Mink Date: Thu, 24 Sep 2026 14:53:32 -0500 Subject: [PATCH 13/32] Allow later Oma package revisions in removal test --- tests/oma-service-removal.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/oma-service-removal.py b/tests/oma-service-removal.py index 5686144..fd31de3 100644 --- a/tests/oma-service-removal.py +++ b/tests/oma-service-removal.py @@ -338,7 +338,11 @@ esac self.assertIn("When = PreTransaction", hook) self.assertIn("AbortOnFail", hook) self.assertIn(f"Exec = /usr/lib/{app}/package-remove {app}", hook) - self.assertIn("pkgrel=4", (directory / "PKGBUILD").read_text()) + release = next( + line for line in (directory / "PKGBUILD").read_text().splitlines() + if line.startswith("pkgrel=") + ) + self.assertGreaterEqual(int(release.removeprefix("pkgrel=")), 4) scripts.append((directory / "package-remove").read_bytes()) self.assertEqual(*scripts) From b44166a9bcfa8b1300faeb34625140a9bbb7072c Mon Sep 17 00:00:00 2001 From: Jacob Mink Date: Thu, 24 Sep 2026 16:22:18 -0500 Subject: [PATCH 14/32] Package Omaspeak 0.1.0 and Omawake 0.1.1 --- pkgbuilds/omaspeak-bin/PKGBUILD | 17 +++++++++-------- pkgbuilds/omawake-bin/PKGBUILD | 18 +++++++++--------- tests/oma-service-removal.py | 11 ++++++++--- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/pkgbuilds/omaspeak-bin/PKGBUILD b/pkgbuilds/omaspeak-bin/PKGBUILD index c60ac9e..a38fdae 100644 --- a/pkgbuilds/omaspeak-bin/PKGBUILD +++ b/pkgbuilds/omaspeak-bin/PKGBUILD @@ -2,9 +2,9 @@ pkgname=omaspeak-bin _pkgname=${pkgname%-bin} -pkgver=0.0.3 -_upstream_ver=0.0.3 -pkgrel=4 +pkgver=0.1.0 +_upstream_ver=0.1.0 +pkgrel=1 pkgdesc='Local-first text-to-speech application and daemon (pre-built binary)' arch=('x86_64' 'aarch64') url='https://github.com/jacob-vincent-mink/omaspeak' @@ -16,9 +16,10 @@ depends=( ) optdepends=( 'pipewire-audio: audio playback through pw-play' - 'openvino: Intel CPU acceleration runtime' - 'openvino-intel-gpu-plugin: Intel GPU device support for OpenVINO' - 'openvino-intel-npu-plugin: Intel NPU device support for OpenVINO' + 'openvino>=2026.4.0: Intel acceleration runtime' + 'openvino-genai>=2026.4.0.0: Kokoro speech synthesis on OpenVINO' + 'openvino-intel-gpu-plugin>=2026.4.0: Intel GPU device support for OpenVINO' + 'openvino-intel-npu-plugin>=2026.4.0: Intel NPU device support for OpenVINO' 'cuda: NVIDIA CUDA libraries for an external CUDA runtime bundle' 'cudnn: NVIDIA neural-network libraries for an external CUDA runtime bundle' ) @@ -34,8 +35,8 @@ sha256sums=('f1b527448529b45fee2f96b4c0a19b11087377c0ac9f43842bab0d04fcfd3b9f' source_x86_64=("${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz") source_aarch64=("${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz") -sha256sums_x86_64=('c72428bf6989582b5aa802f39e9390e4cacf26f7fbfacf76645118286c7d1ab2') -sha256sums_aarch64=('88fc4ea8c275b9d5b9d41602d32dbca77ee30de0fc7dcbc06ad0e819fd41545a') +sha256sums_x86_64=('56936bd17490a3fcf6c004413f1ba8b723d1e08256e215cae6434ef345c951ba') +sha256sums_aarch64=('10e6d07de03c8243cbb4172d0517653b82a4d1e785fdd1b6aaf5ae9618ea6171') package() { install -Dm755 "${srcdir}/package-remove" "${pkgdir}/usr/lib/${_pkgname}/package-remove" diff --git a/pkgbuilds/omawake-bin/PKGBUILD b/pkgbuilds/omawake-bin/PKGBUILD index 4571de1..4d6b017 100644 --- a/pkgbuilds/omawake-bin/PKGBUILD +++ b/pkgbuilds/omawake-bin/PKGBUILD @@ -2,9 +2,9 @@ pkgname=omawake-bin _pkgname=${pkgname%-bin} -pkgver=0.0.3 -_upstream_ver=0.0.3 -pkgrel=5 +pkgver=0.1.1 +_upstream_ver=0.1.1 +pkgrel=1 pkgdesc='Configurable local wake-word daemon (pre-built binary)' arch=('x86_64' 'aarch64') url='https://github.com/jacob-vincent-mink/omawake' @@ -16,10 +16,10 @@ depends=( ) optdepends=( 'pipewire-audio: PipeWire audio support' - 'openvino: Intel runtime for the OpenVINO provider' - 'openvino-genai: OpenVINO GenAI C provider for Whisper' - 'openvino-intel-gpu-plugin: Intel GPU device support for OpenVINO' - 'openvino-intel-npu-plugin: Intel NPU device support for OpenVINO' + 'openvino>=2026.4.0: Intel runtime for the OpenVINO provider' + 'openvino-genai>=2026.4.0.0: OpenVINO GenAI C provider for Whisper' + 'openvino-intel-gpu-plugin>=2026.4.0: Intel GPU device support for OpenVINO' + 'openvino-intel-npu-plugin>=2026.4.0: Intel NPU device support for OpenVINO' 'cuda: NVIDIA CUDA libraries for an external CUDA runtime bundle' 'cudnn: NVIDIA neural-network libraries for an external CUDA runtime bundle' ) @@ -35,8 +35,8 @@ sha256sums=('f1b527448529b45fee2f96b4c0a19b11087377c0ac9f43842bab0d04fcfd3b9f' source_x86_64=("${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-x86_64.tar.xz") source_aarch64=("${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz::${url}/releases/download/v${_upstream_ver}/${_pkgname}-${_upstream_ver}-linux-aarch64.tar.xz") -sha256sums_x86_64=('fa374341f60760b04c9a97d76f7ad2679463f5b2b673ee6d9c1ceee97d3f0669') -sha256sums_aarch64=('384eb11872c873a33332acf51f567dc4d4e327e57563b61056e4d0aa7fe470eb') +sha256sums_x86_64=('adf8d93dd892fa77089384944a720de502d331582cc0e6eed66c9eaa2d31b568') +sha256sums_aarch64=('9929208a3166f0f1939a66d306018a7a861f92fca1d0e186ea8f9af93a947ecb') package() { install -Dm755 "${srcdir}/package-remove" "${pkgdir}/usr/lib/${_pkgname}/package-remove" diff --git a/tests/oma-service-removal.py b/tests/oma-service-removal.py index fd31de3..bdc08e2 100644 --- a/tests/oma-service-removal.py +++ b/tests/oma-service-removal.py @@ -305,7 +305,13 @@ esac def test_packaging_installs_hooks_and_helpers(self): import shutil - for app, version in (("omawake", "0.0.3"), ("omaspeak", "0.0.3")): + for app in ("omawake", "omaspeak"): + directory = ROOT / f"pkgbuilds/{app}-bin" + version = next( + line.removeprefix("pkgver=") + for line in (directory / "PKGBUILD").read_text().splitlines() + if line.startswith("pkgver=") + ) source = self.root / app / "src" package = self.root / app / "pkg" release = source / f"{app}-{version}-linux-x86_64" @@ -317,7 +323,6 @@ esac target = release / path target.parent.mkdir(parents=True, exist_ok=True) target.write_text("fixture") - directory = ROOT / f"pkgbuilds/{app}-bin" for name in ("package-remove", "remove-user-services.hook"): shutil.copyfile(directory / name, source / name) env = dict(self.env, srcdir=str(source), pkgdir=str(package), CARCH="x86_64") @@ -342,7 +347,7 @@ esac line for line in (directory / "PKGBUILD").read_text().splitlines() if line.startswith("pkgrel=") ) - self.assertGreaterEqual(int(release.removeprefix("pkgrel=")), 4) + self.assertGreaterEqual(int(release.removeprefix("pkgrel=")), 1) scripts.append((directory / "package-remove").read_bytes()) self.assertEqual(*scripts) From ef6bf03935b79140e1f52dcaa95db8a0fcf506d6 Mon Sep 17 00:00:00 2001 From: Jeremy Dixon Date: Fri, 25 Sep 2026 04:55:40 -0400 Subject: [PATCH 15/32] Update Omatrack to 1.8.6 --- pkgbuilds/omatrack/PKGBUILD | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgbuilds/omatrack/PKGBUILD b/pkgbuilds/omatrack/PKGBUILD index eca0ba1..6221ab2 100644 --- a/pkgbuilds/omatrack/PKGBUILD +++ b/pkgbuilds/omatrack/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: David Heinemeier Hansson pkgname=omatrack -pkgver=1.2.0 +pkgver=1.8.6 pkgrel=1 pkgdesc="Cross-format motorsport telemetry analysis workstation" arch=('x86_64' 'aarch64') @@ -11,9 +11,9 @@ depends=('qt6-base' 'qt6-declarative' 'mpv' 'libyaml' 'gcc-libs' 'glibc' 'hicolo makedepends=('cmake' 'ninja' 'rust') options=('!debug' '!lto') -_commit=ebe7f4f6b05845a85a2b713f889e676442fd0e82 +_commit=cd3fcdda3f8e15fda73288f6cf3803164138a7e4 source=("omatrack-$_commit.tar.gz::$url/archive/$_commit.tar.gz") -sha256sums=('8e6c62de5f8c98239a84abe9696c83f1155bef12004ee2c028bd99d64b8263a4') +sha256sums=('6beb5703fe0b08a3bb99409581246498300f5cd91195239da00b91123681b8a3') prepare() { cd "omatrack-$_commit/third_party/motorsport-telemetry" From 6a2561fa7a4fed6f65a42914b288e8f6d255d2be Mon Sep 17 00:00:00 2001 From: Jeremy Dixon Date: Fri, 25 Sep 2026 04:59:36 -0400 Subject: [PATCH 16/32] Declare Omatrack Lua and sol2 build sources --- pkgbuilds/omatrack/PKGBUILD | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pkgbuilds/omatrack/PKGBUILD b/pkgbuilds/omatrack/PKGBUILD index 6221ab2..59fbbaa 100644 --- a/pkgbuilds/omatrack/PKGBUILD +++ b/pkgbuilds/omatrack/PKGBUILD @@ -12,8 +12,16 @@ makedepends=('cmake' 'ninja' 'rust') options=('!debug' '!lto') _commit=cd3fcdda3f8e15fda73288f6cf3803164138a7e4 -source=("omatrack-$_commit.tar.gz::$url/archive/$_commit.tar.gz") -sha256sums=('6beb5703fe0b08a3bb99409581246498300f5cd91195239da00b91123681b8a3') +source=( + "omatrack-$_commit.tar.gz::$url/archive/$_commit.tar.gz" + 'lua-5.4.7.tar.gz::https://www.lua.org/ftp/lua-5.4.7.tar.gz' + 'sol2-d805d027.tar.gz::https://github.com/ThePhD/sol2/archive/d805d027e0a0a7222e936926139f06e23828ce9f.tar.gz' +) +sha256sums=( + '6beb5703fe0b08a3bb99409581246498300f5cd91195239da00b91123681b8a3' + '9fbf5e28ef86c69858f6d3d34eccc32e911c1a28b4120ff3e84aaa70cfbf1e30' + 'e7877a14e90d44e1b2d00ec77b85090b3b441f4634a63f23bfe44cedbac8ac9c' +) prepare() { cd "omatrack-$_commit/third_party/motorsport-telemetry" @@ -26,7 +34,10 @@ build() { cmake -B build -S . -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr + -DCMAKE_INSTALL_PREFIX=/usr \ + -DFETCHCONTENT_SOURCE_DIR_LUA_SRC="$srcdir/lua-5.4.7" \ + -DFETCHCONTENT_SOURCE_DIR_SOL2="$srcdir/sol2-d805d027e0a0a7222e936926139f06e23828ce9f" \ + -DFETCHCONTENT_FULLY_DISCONNECTED=ON cmake --build build } From 71abb9f0aa934bc9657496d2ac53cc33bc456bcc Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Fri, 25 Sep 2026 21:08:13 +0800 Subject: [PATCH 17/32] supergfxctl: drop nonexistent sudo group from D-Bus policy Arch has no sudo group, so dbus-broker rejects that policy block by name on every bus reload: "Invalid group-name in org.supergfxctl.Daemon.conf +9: group=\"sudo\"". The block grants nothing here; the wheel policy in the same file is what authorises an unprivileged user. Remove the dead block with a checked-in patch and bump pkgrel. --- pkgbuilds/supergfxctl/PKGBUILD | 16 +++++++++++++--- .../drop-nonexistent-sudo-group.patch | 13 +++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 pkgbuilds/supergfxctl/drop-nonexistent-sudo-group.patch diff --git a/pkgbuilds/supergfxctl/PKGBUILD b/pkgbuilds/supergfxctl/PKGBUILD index 7767218..46e46f2 100644 --- a/pkgbuilds/supergfxctl/PKGBUILD +++ b/pkgbuilds/supergfxctl/PKGBUILD @@ -3,7 +3,7 @@ pkgname=supergfxctl pkgver=5.2.7 -pkgrel=2 +pkgrel=3 pkgdesc="A utility for Linux graphics switching on Intel/AMD iGPU + nVidia dGPU laptops" arch=('x86_64') url="https://gitlab.com/asus-linux/supergfxctl" @@ -13,11 +13,21 @@ makedepends=('rust') provides=('supergfxctl') conflicts=('supergfxctl-git' 'optimus-manager') -source=("https://gitlab.com/asus-linux/supergfxctl/-/archive/$pkgver/supergfxctl-$pkgver.tar.gz") -sha512sums=('bd94646d289c9f3398e1bf2a189554ac60d4db2d4d2cefddc0b342e8a128d4682e20268e0b1f9b168441136ada0b6fadb097a5a35d1023a77528dbf0540de3af') +source=("https://gitlab.com/asus-linux/supergfxctl/-/archive/$pkgver/supergfxctl-$pkgver.tar.gz" + "drop-nonexistent-sudo-group.patch") +sha512sums=('bd94646d289c9f3398e1bf2a189554ac60d4db2d4d2cefddc0b342e8a128d4682e20268e0b1f9b168441136ada0b6fadb097a5a35d1023a77528dbf0540de3af' + 'c24de0e6a632fd98052eb3b265000cf15bf00ff46f7e52120e462715937df8135297d9f12725d1c303691c0db55b23d2b29671f020bd64e98a993b1ad9b8551e') options=(!debug) _gitdir=${pkgname%"-git"} +prepare() { + cd "$pkgname-$pkgver" + # Arch has no "sudo" group, so dbus-broker rejects that policy block by name on + # every bus reload ("Invalid group-name ... group=\"sudo\""). The block grants + # nothing here; the wheel policy in the same file is the one that applies. + patch -p1 -i "$srcdir/drop-nonexistent-sudo-group.patch" +} + build() { cd "$pkgname-$pkgver" make build diff --git a/pkgbuilds/supergfxctl/drop-nonexistent-sudo-group.patch b/pkgbuilds/supergfxctl/drop-nonexistent-sudo-group.patch new file mode 100644 index 0000000..7c91edd --- /dev/null +++ b/pkgbuilds/supergfxctl/drop-nonexistent-sudo-group.patch @@ -0,0 +1,13 @@ +--- a/data/org.supergfxctl.Daemon.conf ++++ b/data/org.supergfxctl.Daemon.conf +@@ -6,10 +6,6 @@ + + + +- +- +- +- + + + From 4aca3bdbc773f918b0e73d922a45492fa0e36ac9 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 26 Sep 2026 20:01:17 -0400 Subject: [PATCH 18/32] Keep sync PRs building across bot pushes Three things kept the upstream sync PR (#589) from ever finishing a build: Scoped dispatches wiped the shared PR. A workflow_dispatch with `packages` regenerates only those packages from master, and pushing that to auto/sync-upstream replaced 38 pending updates with one. Scoped runs now push to their own auto/sync-{upstream,rebuilds}- branch and PR; scheduled runs keep the shared branch. build-approved stopped working after the first bot push. A GITHUB_TOKEN push creates pull_request runs held for approval but no pull_request_target run, so approve-pr.yml never saw it: its last run on the branch was the label itself (2026-09-25T19:26), and each of the next four syncs sat at action_required. The sync workflows now release the held runs for the commit they just pushed, from a separate job holding actions: write, and only for their own bot-authored, same-repo PR while build-approved is on it. Each approved push cancelled the in-flight build. Approving the 21:43 sync's build cancelled the label-triggered one still queued on strata and schist-bin. On auto/sync-* branches a new build now waits for the running one instead, then reuses its artifacts. The approval script no longer waits for a lone approved build to start before releasing tests, which a queued build would have turned into a timeout. --- .github/scripts/approve-pr-workflows.cjs | 18 ++- .github/scripts/approve-sync-push.cjs | 33 ++++++ .github/scripts/sync-pr-branch.sh | 40 +++++++ .github/workflows/build-pr.yml | 9 +- .github/workflows/sync-rebuilds.yml | 61 ++++++++++- .github/workflows/sync-upstream.yml | 61 ++++++++++- README.md | 16 +++ tests/pr-workflow-approval.cjs | 134 +++++++++++++++++++++++ 8 files changed, 363 insertions(+), 9 deletions(-) create mode 100644 .github/scripts/approve-sync-push.cjs create mode 100755 .github/scripts/sync-pr-branch.sh diff --git a/.github/scripts/approve-pr-workflows.cjs b/.github/scripts/approve-pr-workflows.cjs index 0ee32b3..df5c83b 100644 --- a/.github/scripts/approve-pr-workflows.cjs +++ b/.github/scripts/approve-pr-workflows.cjs @@ -1,7 +1,11 @@ const BUILD = '.github/workflows/build-pr.yml'; const TESTS = '.github/workflows/test.yml'; +// pullRequest/action/since default to the pull_request_target event. The +// sync workflows pass them explicitly: GitHub creates no pull_request_target +// run for a GITHUB_TOKEN push, so they release their own pushes' held runs. module.exports = async function approve({ github, context, core, vouchStatus, + pullRequest = context.payload.pull_request, action = context.payload.action, since, sleep = ms => new Promise(resolve => setTimeout(resolve, ms)), attempts = 36 }) { // Missing/failed vouch lookups must not become approval. Denouncements // remain absolute, just as they are in the package build gate. @@ -9,8 +13,8 @@ module.exports = async function approve({ github, context, core, vouchStatus, throw new Error(`Cannot approve workflows: vouch status is ${vouchStatus || 'missing'}.`); } - const expected = context.payload.pull_request; - const eventTime = Date.parse(expected.updated_at); + const expected = pullRequest; + const eventTime = Date.parse(since ?? expected.updated_at); if (!Number.isFinite(eventTime)) throw new Error('Missing PR event timestamp.'); const approved = new Set(); let precedingBuild; @@ -47,7 +51,7 @@ module.exports = async function approve({ github, context, core, vouchStatus, const newestBuild = runs.findLast(run => run.path === BUILD); if (!newestBuild || !(Date.parse(newestBuild.created_at) >= eventTime) || !runs.some(run => run.path === TESTS && - (context.payload.action === 'labeled' || Date.parse(run.created_at) >= eventTime))) continue; + (action === 'labeled' || Date.parse(run.created_at) >= eventTime))) continue; if (precedingBuild) { const { data: run } = await github.rest.actions.getWorkflowRun({ @@ -71,7 +75,13 @@ module.exports = async function approve({ github, context, core, vouchStatus, await github.rest.actions.approveWorkflowRun({ ...context.repo, run_id: run.id }); approved.add(run.id); core.info(`Approved ${run.path} run ${run.id} for PR #${expected.number}.`); - if (run.path === BUILD) precedingBuild = run.id; + // Only a newer held build needs this one to take the concurrency slot + // first. A lone build may sit pending behind an in-flight build of an + // older commit (sync branches queue rather than cancel); waiting for it + // to start would time out before the tests run was released. + if (run.path === BUILD && pending.some(other => other.path === BUILD && other.id > run.id)) { + precedingBuild = run.id; + } if (pending.length === 1) return; } throw new Error('Timed out waiting for PR workflows. Remove and reapply build-approved to retry.'); diff --git a/.github/scripts/approve-sync-push.cjs b/.github/scripts/approve-sync-push.cjs new file mode 100644 index 0000000..7f4ea85 --- /dev/null +++ b/.github/scripts/approve-sync-push.cjs @@ -0,0 +1,33 @@ +const approvePrWorkflows = require('./approve-pr-workflows.cjs'); + +const BOT = 'github-actions[bot]'; + +// A sync workflow pushes its branch with GITHUB_TOKEN. GitHub holds the +// resulting pull_request runs for approval and, unlike a person's push, +// creates no pull_request_target run, so approve-pr.yml never sees it. The +// sync workflow therefore releases the runs for the commit it just pushed, +// under the same rule approve-pr.yml applies: only while a maintainer's +// build-approved label is on the PR. It acts only on its own bot-authored, +// same-repository PR for the branch and commit it pushed. +module.exports = async function approveSyncPush({ github, context, core, + number, branch, headSha, since, approve = approvePrWorkflows, ...options }) { + if (!Number.isInteger(number) || !branch || !headSha || !since) { + throw new Error('Missing sync PR number, branch, head SHA or push time.'); + } + const { data: pr } = await github.rest.pulls.get({ ...context.repo, pull_number: number }); + const repository = `${context.repo.owner}/${context.repo.repo}`; + if (pr.user?.login !== BOT || pr.head.repo?.full_name !== repository || + pr.base.repo?.full_name !== repository || pr.head.ref !== branch) { + throw new Error(`PR #${number} is not ${BOT}'s ${branch} PR in ${repository}; refusing to approve.`); + } + if (pr.state !== 'open' || pr.head.sha !== headSha) { + core.info(`PR #${number} is closed or has moved past ${headSha}; nothing to approve.`); + return; + } + if (!pr.labels.some(label => label.name === 'build-approved')) { + core.info(`PR #${number} has no build-approved label; its runs wait for a maintainer.`); + return; + } + await approve({ github, context, core, vouchStatus: 'bot', pullRequest: pr, + action: 'synchronize', since, ...options }); +}; diff --git a/.github/scripts/sync-pr-branch.sh b/.github/scripts/sync-pr-branch.sh new file mode 100755 index 0000000..6dbe7ab --- /dev/null +++ b/.github/scripts/sync-pr-branch.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Usage: sync-pr-branch.sh BASE_BRANCH [PACKAGE...] +# +# Prints the branch a sync workflow run pushes to, as branch=/scope= lines for +# $GITHUB_OUTPUT. An unscoped (scheduled) run owns BASE_BRANCH and regenerates +# it from master every time. A run scoped to named packages regenerates only +# those, so it gets its own branch and PR: pushing it to BASE_BRANCH would +# replace every other pending update there with just the named packages. +set -euo pipefail + +base=${1:?base branch required} +shift +if (( $# == 0 )); then + printf 'branch=%s\nscope=\n' "$base" + exit 0 +fi + +names=() +for name in "$@"; do + # Package directory names, as pacman allows them. Anything else is a typo + # or an attempt to smuggle something into a ref name or PR title. + if [[ ! $name =~ ^[a-z0-9@_+][a-z0-9@._+-]*$ ]]; then + echo "invalid package name: $name" >&2 + exit 1 + fi + names+=("$name") +done +mapfile -t names < <(printf '%s\n' "${names[@]}" | sort -u) + +scope="${names[*]}" +slug=$(printf '%s\n' "${names[@]}" | sed 's/[^a-z0-9]\{1,\}/-/g; s/^-//; s/-$//' | paste -sd- -) +# Keep long package lists to a readable ref; the hash keeps distinct lists apart. +hash=$(printf '%s' "$scope" | sha256sum | cut -c1-10) +if [[ -z $slug ]]; then + slug=$hash +elif (( ${#slug} > 60 )); then + slug="${slug:0:48}" + slug="${slug%-}-$hash" +fi +printf 'branch=%s-%s\nscope=%s\n' "$base" "$slug" "$scope" diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index ab0bec4..a49723a 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -19,9 +19,16 @@ on: description: "Space-separated package directories to build" required: true +# A new push normally cancels the PR's in-flight build. The sync bots' +# branches (auto/sync-*) are the exception: they are force-pushed with fresh +# upstream releases several times a day, which kept cancelling multi-hour +# aarch64 builds before they could finish. There the newest run waits +# instead (GitHub keeps at most one pending run per group, replacing older +# pending ones), and when it starts it reuses every artifact the finished +# build uploaded, so only packages whose tree changed are built again. concurrency: group: build-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true + cancel-in-progress: ${{ !(github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'auto/sync-')) }} jobs: # Builds cost real machines, so they run only for trusted authors: diff --git a/.github/workflows/sync-rebuilds.yml b/.github/workflows/sync-rebuilds.yml index 4d8af04..8b849dd 100644 --- a/.github/workflows/sync-rebuilds.yml +++ b/.github/workflows/sync-rebuilds.yml @@ -17,6 +17,12 @@ jobs: permissions: contents: write pull-requests: write + outputs: + branch: ${{ steps.branch.outputs.branch }} + pushed_at: ${{ steps.pushed.outputs.at }} + number: ${{ steps.cpr.outputs.pull-request-number }} + operation: ${{ steps.cpr.outputs.pull-request-operation }} + head_sha: ${{ steps.cpr.outputs.pull-request-head-sha }} steps: - name: Checkout repository @@ -24,6 +30,17 @@ jobs: with: persist-credentials: false + # A scoped dispatch regenerates only the named packages. Pushed to the + # shared branch, that would replace every other pending update in its + # PR, so it gets a branch and PR of its own. + - name: Choose the PR branch + id: branch + env: + PACKAGES: ${{ github.event.inputs.packages }} + run: | + read -r -a package_args <<< "${PACKAGES:-}" + .github/scripts/sync-pr-branch.sh auto/sync-rebuilds "${package_args[@]}" | tee -a "$GITHUB_OUTPUT" + # Runs in an Arch container against the mirror the x86_64 builder itself # uses, because the question being asked is what that builder will link # against and a different mirror can be hours ahead of it. Recording a @@ -68,13 +85,21 @@ jobs: echo "has_changes=true" >> "$GITHUB_OUTPUT" fi + # Runs created by this push are newer than this; the approve job + # waits for them. A minute's slack absorbs runner clock skew. + - name: Record push time + if: steps.changes.outputs.has_changes == 'true' + id: pushed + run: echo "at=$(date -u -d '-1 minute' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + - name: Create Pull Request if: steps.changes.outputs.has_changes == 'true' + id: cpr uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore: rebuild against updated dependencies' - title: 'chore: rebuild against updated dependencies' + title: "chore: rebuild against updated dependencies${{ steps.branch.outputs.scope && format(' ({0})', steps.branch.outputs.scope) || '' }}" body: | Automated pkgrel bump for packages that link against a dependency which has moved in the official repositories. @@ -84,7 +109,7 @@ jobs: bump is what makes the rebuilt package an upgrade pacman will offer; without it the build produces the version already published and no one receives it. - branch: auto/sync-rebuilds + branch: ${{ steps.branch.outputs.branch }} delete-branch: true labels: automated reviewers: ryanrhughes @@ -100,3 +125,35 @@ jobs: "🔴 Rebuild trigger sync failed
View run" \ '{content: $content}')" \ "$BASECAMP_CHATBOT_URL" + + # GitHub holds pull_request runs from a GITHUB_TOKEN push for approval and + # creates no pull_request_target run for it, so approve-pr.yml never sees + # the sync's own pushes. Once a maintainer has labelled the PR + # build-approved, release the held runs for the commit just pushed. A + # separate job, so the sync container's token never holds actions: write. + approve: + needs: sync + if: ${{ !cancelled() && needs.sync.outputs.operation == 'updated' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + pull-requests: read + actions: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Release held build and test runs if build-approved + uses: actions/github-script@v7 + env: + NUMBER: ${{ needs.sync.outputs.number }} + BRANCH: ${{ needs.sync.outputs.branch }} + HEAD_SHA: ${{ needs.sync.outputs.head_sha }} + SINCE: ${{ needs.sync.outputs.pushed_at }} + with: + script: | + const approve = require('./.github/scripts/approve-sync-push.cjs'); + const { NUMBER, BRANCH, HEAD_SHA, SINCE } = process.env; + await approve({ github, context, core, number: Number(NUMBER), + branch: BRANCH, headSha: HEAD_SHA, since: SINCE }); diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 2e69136..67ce46c 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -17,6 +17,12 @@ jobs: permissions: contents: write pull-requests: write + outputs: + branch: ${{ steps.branch.outputs.branch }} + pushed_at: ${{ steps.pushed.outputs.at }} + number: ${{ steps.cpr.outputs.pull-request-number }} + operation: ${{ steps.cpr.outputs.pull-request-operation }} + head_sha: ${{ steps.cpr.outputs.pull-request-head-sha }} steps: - name: Checkout repository @@ -24,6 +30,17 @@ jobs: with: persist-credentials: false + # A scoped dispatch regenerates only the named packages. Pushed to the + # shared branch, that would replace every other pending update in its + # PR, so it gets a branch and PR of its own. + - name: Choose the PR branch + id: branch + env: + PACKAGES: ${{ github.event.inputs.packages }} + run: | + read -r -a package_args <<< "${PACKAGES:-}" + .github/scripts/sync-pr-branch.sh auto/sync-upstream "${package_args[@]}" | tee -a "$GITHUB_OUTPUT" + # Runs in an Arch container for vercmp: whether a release is an upgrade has # to be decided by the same comparator pacman will use on users' machines. - name: Update packages from upstream release feeds @@ -70,13 +87,21 @@ jobs: echo "has_changes=true" >> "$GITHUB_OUTPUT" fi + # Runs created by this push are newer than this; the approve job + # waits for them. A minute's slack absorbs runner clock skew. + - name: Record push time + if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }} + id: pushed + run: echo "at=$(date -u -d '-1 minute' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" + - name: Create Pull Request if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }} + id: cpr uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'chore: sync upstream releases' - title: 'chore: sync upstream releases' + title: "chore: sync upstream releases${{ steps.branch.outputs.scope && format(' ({0})', steps.branch.outputs.scope) || '' }}" body: | Automated update of packages that track an upstream vendor release feed rather than the AUR. @@ -84,7 +109,7 @@ jobs: Release watches and providers are declared in `.omarchy/package.json`; exceptional feeds use `.omarchy/upstream.sh`. Failed package updates are left untouched; check the workflow result for outstanding failures. - branch: auto/sync-upstream + branch: ${{ steps.branch.outputs.branch }} delete-branch: true labels: automated reviewers: ryanrhughes @@ -100,3 +125,35 @@ jobs: "🔴 Upstream sync failed
View run" \ '{content: $content}')" \ "$BASECAMP_CHATBOT_URL" + + # GitHub holds pull_request runs from a GITHUB_TOKEN push for approval and + # creates no pull_request_target run for it, so approve-pr.yml never sees + # the sync's own pushes. Once a maintainer has labelled the PR + # build-approved, release the held runs for the commit just pushed. A + # separate job, so the sync container's token never holds actions: write. + approve: + needs: sync + if: ${{ !cancelled() && needs.sync.outputs.operation == 'updated' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + pull-requests: read + actions: write + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Release held build and test runs if build-approved + uses: actions/github-script@v7 + env: + NUMBER: ${{ needs.sync.outputs.number }} + BRANCH: ${{ needs.sync.outputs.branch }} + HEAD_SHA: ${{ needs.sync.outputs.head_sha }} + SINCE: ${{ needs.sync.outputs.pushed_at }} + with: + script: | + const approve = require('./.github/scripts/approve-sync-push.cjs'); + const { NUMBER, BRANCH, HEAD_SHA, SINCE } = process.env; + await approve({ github, context, core, number: Number(NUMBER), + branch: BRANCH, headSha: HEAD_SHA, since: SINCE }); diff --git a/README.md b/README.md index 8823595..94cad21 100644 --- a/README.md +++ b/README.md @@ -883,6 +883,22 @@ The repository includes GitHub workflows and systemd services for automated rele 1. **sync-upstream.yml** (Every 6 hours): Watches direct upstream feeds and updates owned recipes. Successful package updates reach a PR even if another feed fails; failed recipes stay untouched and the workflow remains red. 2. **sync-rebuilds.yml** (Every 6 hours): Bumps pkgrel for packages whose `rebuild_on` dependencies have moved in the official repositories and opens a PR. +Scheduled runs regenerate one shared PR (`auto/sync-upstream`, `auto/sync-rebuilds`) +from master. A manual run with the `packages` input only regenerates those +packages, so it opens its own PR on `auto/sync-upstream-` (or +`auto/sync-rebuilds-`) rather than replacing the shared PR's other +pending updates. The next scheduled run still picks the same update up in the +shared PR if it has not merged by then; identical package trees reuse the same +build artifacts. + +Sync PRs are pushed with `GITHUB_TOKEN`, so GitHub holds their build and test +runs for approval on every push and starts no `pull_request_target` workflow +for them. Once **`build-approved`** is on a sync PR, the sync workflow's own +`approve` job releases the held runs for each commit it pushes. A push to an +`auto/sync-*` branch does not cancel the PR's in-flight build: the new build +waits for it and then reuses its artifacts, so a long aarch64 build is not +restarted by every sync. + To approve builds for an unvouched contributor's PR, apply **`build-approved`**. Until approval, the PR shows **Awaiting build approval** and its required `result` check stays pending, keeping the PR blocked from merging without diff --git a/tests/pr-workflow-approval.cjs b/tests/pr-workflow-approval.cjs index e4fc661..d704936 100644 --- a/tests/pr-workflow-approval.cjs +++ b/tests/pr-workflow-approval.cjs @@ -186,6 +186,15 @@ test('a delayed tests workflow is also awaited', async () => { assert.deepEqual(state.approved, [1, 2]); }); +test('a lone build left pending behind an older in-flight build does not hold back the tests', async () => { + // Sync branches queue rather than cancel, so an approved build can stay + // queued for hours. Only a newer held build needs to wait for it to start. + const { state, invoke } = fixture([run(1, BUILD), run(2, TESTS)], { queueUntil: Infinity }); + await invoke(); + assert.deepEqual(state.approved, [1, 2]); + assert.deepEqual(state.transitions, []); +}); + test('reopening a labeled PR waits for its new tests, even if old tests passed at the same SHA', async () => { const { state, invoke } = fixture([ run(1, TESTS, { created_at: earlier, conclusion: 'success' }), run(2, BUILD), @@ -312,3 +321,128 @@ for (const [name, overrides] of [ assert.doesNotMatch(result.stdout, /::notice::Awaiting maintainer build approval/); }); } + +// A push to a sync branch must not cancel that PR's multi-hour build; any +// other PR still cancels its superseded build. +test('only same-repository sync branches queue behind an in-flight build', () => { + const expression = workflow.match(/^ cancel-in-progress: \$\{\{(.*)\}\}$/m)[1]; + const cancels = (repo, ref) => new Function('github', 'startsWith', `return (${expression})`)( + { repository: 'omacom/omarchy-pkgs', head_ref: ref, + event: { pull_request: { head: { repo: { full_name: repo } } } } }, + (text, prefix) => text.startsWith(prefix)); + assert.equal(cancels('omacom/omarchy-pkgs', 'auto/sync-upstream'), false); + assert.equal(cancels('omacom/omarchy-pkgs', 'auto/sync-upstream-ttfx'), false); + assert.equal(cancels('omacom/omarchy-pkgs', 'auto/sync-rebuilds'), false); + assert.equal(cancels('omacom/omarchy-pkgs', 'ttfx/fix'), true); + assert.equal(cancels('someone/omarchy-pkgs', 'auto/sync-upstream'), true); + assert.equal(cancels(undefined, ''), true); // workflow_dispatch +}); + +// The sync workflows release their own GITHUB_TOKEN pushes: GitHub creates +// no pull_request_target run for those, so approve-pr.yml never runs. +const approveSyncPush = require('../.github/scripts/approve-sync-push.cjs'); +function syncFixture(options = {}) { + const f = fixture([run(1, BUILD, { head_branch: 'auto/sync-upstream' }), + run(2, TESTS, { head_branch: 'auto/sync-upstream' })], options); + Object.assign(f.state.pr, { + user: { login: 'github-actions[bot]' }, + head: { ...f.state.pr.head, ref: 'auto/sync-upstream', repo: { id: 42, full_name: 'omacom/omarchy-pkgs' } }, + base: { repo: { full_name: 'omacom/omarchy-pkgs' } }, + }); + const push = overrides => approveSyncPush({ + github: f.github, context: { repo: { owner: 'omacom', repo: 'omarchy-pkgs' }, payload: {} }, + core: { info() {} }, number: 390, branch: 'auto/sync-upstream', headSha: pr.head.sha, + since: earlier, attempts: 6, sleep: async () => {}, ...overrides, + }); + return { ...f, push }; +} + +test('a labelled sync PR has its bot push released', async () => { + const { state, push } = syncFixture(); + await push(); + assert.deepEqual(state.approved, [1, 2]); +}); + +test('an unlabelled sync PR stays held for a maintainer', async () => { + const { state, push } = syncFixture(); + state.pr.labels = []; + await push(); + assert.deepEqual(state.approved, []); +}); + +test('runs older than the push are not taken for this push', async () => { + const { state, push } = syncFixture(); + await assert.rejects(push({ since: '2026-09-19T03:00:00Z' }), /Timed out/); + assert.deepEqual(state.approved, []); +}); + +for (const [name, change] of [ + ['a contributor PR', current => { current.user.login = 'someone'; }], + ['a fork PR', current => { current.head.repo.full_name = 'someone/omarchy-pkgs'; }], + ['another branch', current => { current.head.ref = 'auto/sync-rebuilds'; }], +]) { + test(`the sync approver refuses ${name}, even when labelled`, async () => { + const { state, push } = syncFixture(); + change(state.pr); + await assert.rejects(push(), /refusing to approve/); + assert.deepEqual(state.approved, []); + }); +} + +for (const [name, change] of [ + ['closed', current => { current.state = 'closed'; }], + ['moved on', current => { current.head.sha = 'newer-sha'; }], +]) { + test(`a sync PR that has ${name} is left alone`, async () => { + const { state, push } = syncFixture(); + change(state.pr); + await push(); + assert.deepEqual(state.approved, []); + }); +} + +test('the sync approver needs the push it is approving for', async () => { + const { state, push } = syncFixture(); + for (const missing of [{ number: NaN }, { headSha: '' }, { since: '' }, { branch: '' }]) { + await assert.rejects(push(missing), /Missing sync PR/); + } + assert.deepEqual(state.approved, []); +}); + +// A scoped dispatch must not push to the shared branch: it would replace the +// other pending updates in the open sync PR with just the named packages. +const branchScript = join(__dirname, '../.github/scripts/sync-pr-branch.sh'); +const branchFor = (...names) => Object.fromEntries(execFileSync(branchScript, + ['auto/sync-upstream', ...names], { encoding: 'utf8' }) + .trim().split('\n').map(line => line.split(/=(.*)/s).slice(0, 2))); + +test('scheduled runs keep the shared branch; scoped runs get their own', () => { + assert.deepEqual(branchFor(), { branch: 'auto/sync-upstream', scope: '' }); + assert.deepEqual(branchFor('ttfx'), { branch: 'auto/sync-upstream-ttfx', scope: 'ttfx' }); + assert.deepEqual(branchFor('ttfx', 'strata', 'ttfx'), + { branch: 'auto/sync-upstream-strata-ttfx', scope: 'strata ttfx' }); + assert.equal(branchFor('python-foo.bar').branch, 'auto/sync-upstream-python-foo-bar'); + const names = ['a-very-long-package-name-one', 'another-very-long-package-name-two']; + const long = branchFor(...names, 'third'); + assert.ok(long.branch.length <= 'auto/sync-upstream-'.length + 60); + assert.notEqual(long.branch, branchFor(...names).branch); +}); + +test('scoped branch names reject anything that is not a package name', () => { + for (const name of ['../x', 'A', 'x y', 'a@{b', '-x', '.x', 'x;true']) { + assert.equal(spawnSync(branchScript, ['auto/sync-upstream', name]).status, 1, name); + } +}); + +test('sync workflows push scoped runs aside and keep actions: write out of the sync job', () => { + for (const file of ['sync-upstream.yml', 'sync-rebuilds.yml']) { + const text = readFileSync(join(__dirname, '../.github/workflows', file), 'utf8'); + const sync = text.slice(text.indexOf('\n sync:\n'), text.indexOf('\n approve:\n')); + const approveJob = text.slice(text.indexOf('\n approve:\n')); + assert.match(sync, /sync-pr-branch\.sh auto\/sync-[\w-]+ "\$\{package_args\[@\]\}"/, file); + assert.match(sync, /branch: \$\{\{ steps\.branch\.outputs\.branch \}\}/, file); + assert.doesNotMatch(sync, /^ +actions: write$/m, file); + assert.match(approveJob, /^ actions: write$/m, file); + assert.match(approveJob, /needs\.sync\.outputs\.operation == 'updated'/, file); + } +}); From 21f3c4a8c4bc5fdae36dca47974fc96ffc890906 Mon Sep 17 00:00:00 2001 From: Jim Martin Date: Sun, 27 Sep 2026 09:03:08 -0500 Subject: [PATCH 19/32] omarchy-settings: keep the Mac initramfs hooks on aarch64 Apple Silicon Macs install the aarch64 package too, and an unconditional HOOKS= line would replace their asahi hooks. Apply Omarchy's HOOKS line only when the incoming hooks lack asahi, in whichever drop-in carries it, and rebuild omarchy-settings (4.0.4-3) so existing installs get the fix. --- pkgbuilds/omarchy-settings-dev/PKGBUILD | 16 +++ pkgbuilds/omarchy-settings/PKGBUILD | 18 ++- tests/settings-boot-config.sh | 165 +++++++++++++++++++++++- 3 files changed, 197 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index ce5ebbe..714fc61 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -220,6 +220,22 @@ package() { # Thunderbolt module request is x86-specific; ARM kernels lack the module # and mkinitcpio treats a missing explicit module as an error. rm -f "$pkgdir/etc/mkinitcpio.conf.d/thunderbolt_module.conf" + # Apple Silicon Macs install this package too. Their initramfs needs the + # asahi hook, which omarchy-mac-boot's 90-94 fragments (Aurora) or + # mkinitcpio.conf (legacy GRUB Macs) set, and an unconditional HOOKS= here + # would replace it. Omarchy's line applies only when the hooks loaded so far + # lack asahi. The check reads configuration, not the running machine, so it + # also holds when the image is built in a chroot. + local hooks_conf guarded=0 + for hooks_conf in "$pkgdir"/etc/mkinitcpio.conf.d/{00-omarchy-hooks,omarchy_hooks}.conf; do + [[ -f $hooks_conf ]] && grep -q '^HOOKS=(' "$hooks_conf" || continue + sed -i 's/^\(HOOKS=(.*)\)$/if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then\n \1\nfi/' "$hooks_conf" + guarded=1 + done + if (( ! guarded )); then + echo "No HOOKS= line to guard for Apple Silicon in etc/mkinitcpio.conf.d" >&2 + return 1 + fi # Memory stack: no zram device or zswap on the aarch64 install, and # systemd-oomd is not enabled there, so the vm.* reclaim tuning written # for zram would be wrong for it. Keep only the network tuning. diff --git a/pkgbuilds/omarchy-settings/PKGBUILD b/pkgbuilds/omarchy-settings/PKGBUILD index 7ad64a3..655189c 100644 --- a/pkgbuilds/omarchy-settings/PKGBUILD +++ b/pkgbuilds/omarchy-settings/PKGBUILD @@ -13,7 +13,7 @@ pkgname='omarchy-settings' _tag='v4.0.4' _commit='c668141e9c42b13c80c9ca4ea108e11708c5e8a5' pkgver=4.0.4 -pkgrel=2 +pkgrel=3 pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers' # Arch-specific because the shipped /etc tree is not the same on every # architecture: the zram and oomd drop-ins belong to the x86_64 memory stack @@ -218,6 +218,22 @@ package() { # Thunderbolt module request is x86-specific; ARM kernels lack the module # and mkinitcpio treats a missing explicit module as an error. rm -f "$pkgdir/etc/mkinitcpio.conf.d/thunderbolt_module.conf" + # Apple Silicon Macs install this package too. Their initramfs needs the + # asahi hook, which omarchy-mac-boot's 90-94 fragments (Aurora) or + # mkinitcpio.conf (legacy GRUB Macs) set, and an unconditional HOOKS= here + # would replace it. Omarchy's line applies only when the hooks loaded so far + # lack asahi. The check reads configuration, not the running machine, so it + # also holds when the image is built in a chroot. + local hooks_conf guarded=0 + for hooks_conf in "$pkgdir"/etc/mkinitcpio.conf.d/{00-omarchy-hooks,omarchy_hooks}.conf; do + [[ -f $hooks_conf ]] && grep -q '^HOOKS=(' "$hooks_conf" || continue + sed -i 's/^\(HOOKS=(.*)\)$/if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then\n \1\nfi/' "$hooks_conf" + guarded=1 + done + if (( ! guarded )); then + echo "No HOOKS= line to guard for Apple Silicon in etc/mkinitcpio.conf.d" >&2 + return 1 + fi # Memory stack: no zram device or zswap on the aarch64 install, and # systemd-oomd is not enabled there, so the vm.* reclaim tuning written # for zram would be wrong for it. Keep only the network tuning. diff --git a/tests/settings-boot-config.sh b/tests/settings-boot-config.sh index b9dcff7..dfbe742 100755 --- a/tests/settings-boot-config.sh +++ b/tests/settings-boot-config.sh @@ -63,6 +63,10 @@ for path in "${files[@]}"; do printf 'fixture for %s\n' "$path" > "$fixture/$path" done +# Omarchy's HOOKS line, as its sources ship it. +omarchy_hooks='base udev plymouth keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems fsck btrfs-overlayfs' +printf 'HOOKS=(%s)\n' "$omarchy_hooks" > "$fixture/etc/mkinitcpio.conf.d/omarchy_hooks.conf" + for recipe in omarchy-settings omarchy-settings-dev; do for target_arch in aarch64 x86_64; do ( @@ -76,9 +80,18 @@ for recipe in omarchy-settings omarchy-settings-dev; do for path in etc/mkinitcpio.conf.d/omarchy_hooks.conf \ etc/limine-entry-tool.d/omarchy-defaults.conf \ etc/limine-entry-tool.d/omarchy-uki.conf; do - cmp "$fixture/$path" "$pkgdir/$path" printf '%s\n' "${backup[@]}" | grep -Fxq "$path" done + for path in etc/limine-entry-tool.d/omarchy-defaults.conf etc/limine-entry-tool.d/omarchy-uki.conf; do + cmp "$fixture/$path" "$pkgdir/$path" + done + hooks_conf=etc/mkinitcpio.conf.d/omarchy_hooks.conf + if [[ $CARCH == aarch64 ]]; then + grep -Fxq 'if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then' "$pkgdir/$hooks_conf" + bash -n "$pkgdir/$hooks_conf" + else + cmp "$fixture/$hooks_conf" "$pkgdir/$hooks_conf" + fi for template in default.conf limine.conf; do cmp "$fixture/default/limine/$template" "$pkgdir/usr/share/omarchy/default/limine/$template" done @@ -108,3 +121,153 @@ for recipe in omarchy-settings omarchy-settings-dev; do ) done done + +# The aarch64 packages also reach Apple Silicon Macs, whose initramfs needs the +# asahi hook. Source mkinitcpio.conf and the drop-ins in mkinitcpio's order and +# compare the resulting HOOKS for each kind of aarch64 install. +package_aarch64() { + local recipe=$1 source_tree=$2 out=$3 + ( + # package() builds from $srcdir/omarchy. + export CARCH=aarch64 OMARCHY_SRC=$source_tree srcdir=${source_tree%/omarchy} pkgdir=$out + backup=() + # shellcheck disable=SC1090 # Exercise the recipe's actual package function. + source "$BUILD_ROOT/pkgbuilds/$recipe/PKGBUILD" + package + ) +} + +effective_hooks() { + local root=$1 + ( + LC_ALL=C + HOOKS=() + # shellcheck disable=SC1091 + source "$root/mkinitcpio.conf" + shopt -s nullglob + for conf in "$root"/mkinitcpio.conf.d/*.conf; do + # shellcheck disable=SC1090 + source "$conf" + done + echo "${HOOKS[*]}" + ) +} + +# machine NAME MKINITCPIO_HOOKS PACKAGED_ETC [FRAGMENT FRAGMENT_HOOKS] +machine() { + local root=$scratch/machines/$1 + mkdir -p "$root/mkinitcpio.conf.d" + printf 'HOOKS=(%s)\n' "$2" > "$root/mkinitcpio.conf" + cp "$3"/*.conf "$root/mkinitcpio.conf.d/" + if (($# == 5)); then + printf 'HOOKS=(%s)\n' "$5" > "$root/mkinitcpio.conf.d/$4" + fi + printf '%s\n' "$root" +} + +arch_default='base udev autodetect microcode modconf kms keyboard keymap consolefont block filesystems fsck' +snapdragon='base systemd autodetect microcode modconf kms keyboard sd-vconsole block filesystems fsck' +legacy_mac='base udev autodetect modconf kms keyboard keymap consolefont block asahi encrypt filesystems fsck' +aurora_mac='base udev autodetect modconf kms keyboard keymap consolefont block asahi omarchy-vendorfw omarchy-mac-encrypt sd-encrypt filesystems fsck' + +check_machines() { + local layout=$1 packaged=$2/etc/mkinitcpio.conf.d root + rm -rf "$scratch/machines" + root=$(machine snapdragon "$snapdragon" "$packaged") + [[ $(effective_hooks "$root") == "$omarchy_hooks" ]] || + { echo "FAIL: $layout: Snapdragon gets $(effective_hooks "$root")" >&2; exit 1; } + root=$(machine spark "$arch_default" "$packaged") + [[ $(effective_hooks "$root") == "$omarchy_hooks" ]] || + { echo "FAIL: $layout: DGX Spark gets $(effective_hooks "$root")" >&2; exit 1; } + root=$(machine legacy-mac "$legacy_mac" "$packaged") + [[ $(effective_hooks "$root") == "$legacy_mac" ]] || + { echo "FAIL: $layout: a legacy GRUB Mac loses asahi: $(effective_hooks "$root")" >&2; exit 1; } + root=$(machine aurora-mac "$arch_default" "$packaged" 92-omarchy-mac-boot.conf "$aurora_mac") + [[ $(effective_hooks "$root") == "$aurora_mac" ]] || + { echo "FAIL: $layout: an Aurora Mac loses its hooks: $(effective_hooks "$root")" >&2; exit 1; } + echo "PASS: $layout: Snapdragon and the Spark get Omarchy's hooks; Macs keep asahi" +} + +check_machines "HOOKS in omarchy_hooks.conf" "$scratch/omarchy-settings-aarch64" + +# omacom/omarchy#13362 moves the HOOKS line into 00-omarchy-hooks.conf. +split=$scratch/split/omarchy +mkdir -p "$scratch/split" +cp -a "$fixture" "$split" +printf 'HOOKS=(%s)\n' "$omarchy_hooks" > "$split/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" +sed -i '/^HOOKS=/d' "$split/etc/mkinitcpio.conf.d/omarchy_hooks.conf" +package_aarch64 omarchy-settings "$split" "$scratch/split-package" >/dev/null +grep -Fxq 'if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then' \ + "$scratch/split-package/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" +check_machines "HOOKS in 00-omarchy-hooks.conf" "$scratch/split-package" + +sed -i '/^HOOKS=/d' "$split/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" +if package_aarch64 omarchy-settings "$split" "$scratch/unguarded-package" 2>/dev/null; then + echo 'FAIL: the aarch64 package builds without a HOOKS line to guard' >&2 + exit 1 +fi +echo "PASS: the aarch64 package fails to build without a HOOKS line to guard" + +# Upgrades: pacman replaces an unmodified hooks file, keeps a modified one and +# leaves the guarded version as .pacnew, and installs it where it was absent. +if ((EUID != 0)) || ! command -v pacman >/dev/null; then + echo "SKIP: pacman upgrade checks need root" + exit 0 +fi + +unguarded=$fixture/etc/mkinitcpio.conf.d/omarchy_hooks.conf +guarded=$scratch/omarchy-settings-aarch64/etc/mkinitcpio.conf.d/omarchy_hooks.conf +printf '[options]\nArchitecture = auto\nSigLevel = Never\nLocalFileSigLevel = Never\n' > "$scratch/pacman.conf" + +make_pkg() { + local ver=$1 hooks=${2:-} dir=$scratch/pkg-$1 + mkdir -p "$dir/etc/mkinitcpio.conf.d" + [[ -z $hooks ]] || cp "$hooks" "$dir/etc/mkinitcpio.conf.d/omarchy_hooks.conf" + cat > "$dir/.PKGINFO" </dev/null +} + +stripped=$(make_pkg 1-1) +old=$(make_pkg 2-1 "$unguarded") +new=$(make_pkg 3-1 "$guarded") +installed=etc/mkinitcpio.conf.d/omarchy_hooks.conf + +pacman_in "$scratch/unchanged" -U "$old" +pacman_in "$scratch/unchanged" -U "$new" +cmp "$guarded" "$scratch/unchanged/$installed" +[[ ! -e $scratch/unchanged/$installed.pacnew ]] + +pacman_in "$scratch/modified" -U "$old" +echo '# local change' >> "$scratch/modified/$installed" +pacman_in "$scratch/modified" -U "$new" +grep -Fxq '# local change' "$scratch/modified/$installed" +cmp "$guarded" "$scratch/modified/$installed.pacnew" + +pacman_in "$scratch/absent" -U "$stripped" +pacman_in "$scratch/absent" -U "$new" +cmp "$guarded" "$scratch/absent/$installed" + +# Source what the upgrades installed. +for upgrade in unchanged absent; do + etc=$scratch/$upgrade/etc/mkinitcpio.conf.d + [[ $(effective_hooks "$(machine "$upgrade-snapdragon" "$snapdragon" "$etc")") == "$omarchy_hooks" ]] + [[ $(effective_hooks "$(machine "$upgrade-legacy-mac" "$legacy_mac" "$etc")") == "$legacy_mac" ]] +done +echo "PASS: pacman upgrades install the guarded hooks file and keep local changes" From 0cbcd890fdcb483fd45627c975167fb673a006d1 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 27 Sep 2026 15:58:34 -0400 Subject: [PATCH 20/32] flea: drop the source-grep security gate and update to 0.3.5 The upstream hook refused v0.3.5 as missing a required security fix. It was not missing: copy_file_at now opens through open_if_regular_with_meta(src.at, O_NOFOLLOW), which open_if_regular wraps, and the gate's regex wanted '(' right after open_if_regular. The gate dates from 0.1.x, when Omarchy carried four upstream security patches and needed releases to prove they had absorbed them. Every release since 0.1.5 has, and matching literal source lines has since caught only renames (#488 and this one), never a regression. Keep the real checks -- SHASUMS256.txt match, tarball root, minimum release age -- and drop the greps. Update written by bin/sync-upstream; the checksum matches upstream's SHASUMS256.txt. --- pkgbuilds/flea/.omarchy/upstream.sh | 40 +++++------------------------ pkgbuilds/flea/PKGBUILD | 4 +-- 2 files changed, 9 insertions(+), 35 deletions(-) diff --git a/pkgbuilds/flea/.omarchy/upstream.sh b/pkgbuilds/flea/.omarchy/upstream.sh index 15c8853..1dce4ec 100755 --- a/pkgbuilds/flea/.omarchy/upstream.sh +++ b/pkgbuilds/flea/.omarchy/upstream.sh @@ -1,6 +1,12 @@ #!/bin/bash # Verify Flea's published source archive against its checksum manifest when a -# newer stable release exists, then check its root and required security fixes. +# newer stable release exists, then check its root. +# +# Through 0.1.x this also grepped the source for the upstream security fixes +# Omarchy once carried as patches, so the package could not move to a release +# that lacked them. Every release since 0.1.5 has had them, and matching +# literal source lines only ever caught renames (#488, 0.3.5's +# open_if_regular_with_meta), never a regression. set -euo pipefail REPO='thisisgm/flea' @@ -74,38 +80,6 @@ if [[ $served_roots != "$expected_root" ]]; then exit 1 fi -archive_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archive.rs") -archiveops_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archiveops.rs") -run_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/run.rs") -archivereq_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archivereq.rs") -archivework_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archivework.rs") -mediaprobe_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/mediaprobe.rs") -metareq_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/metareq.rs") -sharelink_qml=$(tar -xOzf "$tarball" "$expected_root/ui/ShareLink.qml") -copyfile_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/copyfile.rs") -regfile_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/regfile.rs") - -# Every check below pins a literal line except the O_NOFOLLOW one. That check -# guards a property -- the copy opens its source with O_NOFOLLOW, so a symlink -# swapped in cannot redirect the read -- and pinning the exact call expression -# made it assert the spelling instead. v0.3.0 moved the first argument from -# `src` to `src.at` when directory-relative opens landed, kept O_NOFOLLOW, and -# hardened symlink handling further; the literal still refused it. Match the -# call and the flag together so a rename cannot read as a removed fix, while -# dropping O_NOFOLLOW still fails. -if ! grep -Fq 'a.push("--".to_string());' <<<"$archive_rs" || - ! grep -Fq 'let input = std::fs::canonicalize(input)' <<<"$archiveops_rs" || - ! grep -Fq 'if op != "compress" && op != "extract"' <<<"$run_rs$archivereq_rs" || - ! grep -Fq 'the sandbox is unavailable: bwrap or prlimit is not on PATH' <<<"$archivework_rs" || - ! grep -Fq 'if !sandbox::available()' <<<"$mediaprobe_rs" || - ! grep -Fq 'if !sandbox::available()' <<<"$metareq_rs" || - ! grep -Fq 'copyToClipboard.command = ["wl-copy", url]' <<<"$sharelink_qml" || - ! grep -Eq 'open_if_regular\(.*O_NOFOLLOW' <<<"$copyfile_rs" || - ! grep -Fq '.custom_flags(O_NONBLOCK | extra_flags)' <<<"$regfile_rs"; then - printf 'Release %s does not contain every required upstream security fix\n' "$best_tag" >&2 - exit 1 -fi - jq -n \ --arg pkgver "$best_version" \ --arg published_at "$best_published_at" \ diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index ab73f77..1bb78d5 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: GM pkgname=flea -pkgver=0.3.4 +pkgver=0.3.5 pkgrel=1 pkgdesc='Fast, keyboard-first file manager for Omarchy' arch=('x86_64' 'aarch64') @@ -52,7 +52,7 @@ options=('!debug') source=( "$url/releases/download/v$pkgver/$pkgname-v$pkgver.tar.gz" ) -sha256sums=('e5ad258126ae796d262b51254bfbd4a768416ae4d02da772663e6e24c90cdfe4') +sha256sums=('947bd1ad17238e6402af044f848662a4c20e9a82e5a61062ef2e10bb6c2d4cfe') build() { cd "$pkgname-$pkgver" From 0059492899ec403c90298abb3f44d332c7eba8ca Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 27 Sep 2026 16:09:38 -0400 Subject: [PATCH 21/32] flea: run copymanifest's tests on tmpfs 0.3.5's copymanifest::tests::a_garbage_stream_falls_back_... opens its anonymous (O_TMPFILE) manifest directly in its /tmp test dir, which the x86_64 builder's /tmp refuses with EOPNOTSUPP. The sibling tests pass because Writer::create falls back across directories. Same reason the other filesystem_tests already run on /dev/shm; none of these spawn. --- pkgbuilds/flea/PKGBUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index 1bb78d5..77365f5 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -116,6 +116,7 @@ check() { # identical and undo walks it back. tmpfs allocates inode numbers from a # counter and never reuses one, which is what the test assumes. local -a filesystem_tests=( + backend::copymanifest::tests:: backend::menu_actions::tests:: backend::menudelete::tests:: backend::redo::tests:: From 97bcb320ab7e27b60d8134c8d782c5e4e4f12674 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Mon, 28 Sep 2026 06:41:39 +1000 Subject: [PATCH 22/32] Rebuild aarch64 natively when publish finds no artifact A merged aarch64 tree without a PR build artifact (expired after 7 days, or a dispatch) was rebuilt on the x86 droplet under QEMU: omarchy-mac-boot took ~167 of the 240 minutes. A new job builds it on ubuntu-24.04-arm the way build-pr.yml does and uploads it under the same label, so the publish job signs and uploads it on the droplet like a PR artifact. The plan logs reuse or rebuild for every package; x86_64, signing and the publish concurrency are unchanged. --- .github/workflows/publish.yml | 122 +++++++++++++++++++++++++++++++--- ci/README.md | 5 +- 2 files changed, 117 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a61642d..ab69575 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -36,13 +36,18 @@ jobs: outputs: matrix: ${{ steps.list.outputs.matrix }} count: ${{ steps.list.outputs.count }} + rebuild: ${{ steps.list.outputs.rebuild }} + rebuild_count: ${{ steps.list.outputs.rebuild_count }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - id: list + env: + GH_TOKEN: ${{ github.token }} run: | + set -euo pipefail if [[ -n "${{ github.event.inputs.packages }}" ]]; then names="${{ github.event.inputs.packages }}" else @@ -53,17 +58,102 @@ jobs: echo "matrix=$matrix" >> "$GITHUB_OUTPUT" echo "count=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT" jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix" + # Reuse or rebuild, decided per entry and said out loud. An aarch64 + # tree with no build artifact (PR artifacts last 7 days; a dispatch + # may name any package) goes to the rebuild job, which builds it + # natively on GitHub's arm64 runner. x86_64 builds inside the + # publish job on the droplet, as before. + rebuild=() + echo "| package | arch | tree | decision |" >> "$GITHUB_STEP_SUMMARY" + echo "| --- | --- | --- | --- |" >> "$GITHUB_STEP_SUMMARY" + while read -r entry; do + package=$(jq -r .package <<<"$entry"); arch=$(jq -r .arch <<<"$entry") + hash=$(git rev-parse "HEAD:pkgbuilds/$package") + label="$package-$arch-$hash" + found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \ + | jq -r '[.artifacts[] | select(.expired|not)] | sort_by(.created_at) | last | select(. != null) | "run \(.workflow_run.id), expires \(.expires_at)"') + if [[ -n "$found" ]]; then + decision="reuse the build artifact ($found)" + elif [[ $arch == aarch64 ]]; then + decision="no build artifact: rebuild natively on ubuntu-24.04-arm" + rebuild+=("$entry") + else + decision="no build artifact: build in the publish job on the self-hosted builder" + fi + echo "==> $label: $decision" + echo "| $package | $arch | ${hash:0:12} | $decision |" >> "$GITHUB_STEP_SUMMARY" + done < <(jq -c '.include[]' <<<"$matrix") + echo "rebuild=$(printf '%s\n' "${rebuild[@]}" | jq -sc '{include: .}')" >> "$GITHUB_OUTPUT" + echo "rebuild_count=${#rebuild[@]}" >> "$GITHUB_OUTPUT" + + # The aarch64 half of "build it now when there is none". It builds exactly + # as build-pr.yml's aarch64 path does (same runner, same builder image, + # same bin/build call) and uploads under the same label, so the publish + # job collects this run's artifact the way it collects a PR's. No secret + # reaches this runner; signing and upload stay on the self-hosted builder. + rebuild: + needs: changes + if: needs.changes.outputs.rebuild_count != '0' + runs-on: ubuntu-24.04-arm + timeout-minutes: 180 + permissions: + contents: read + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.changes.outputs.rebuild) }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + # The same check the publish job makes before building: a re-run for a + # package the channel already holds at master's version builds + # nothing, and uploads nothing that could shadow the published file. + - name: Build ${{ matrix.package }} (${{ matrix.arch }}, native) + id: build + env: + CONTAINER_ENGINE: docker + run: | + set -euo pipefail + plan=$(bin/build --dry-run --mirror edge --arch "${{ matrix.arch }}" --package "${{ matrix.package }}" 2>&1 | grep -E '^==> Plan complete' || true) + if [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then + echo "==> ${{ matrix.package }} (${{ matrix.arch }}): already published at master's version, nothing to build" + echo "built=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + bin/build --mirror edge --arch "${{ matrix.arch }}" --package "${{ matrix.package }}" + echo "built=true" >> "$GITHUB_OUTPUT" + - name: Pack artifact + if: steps.build.outputs.built == 'true' + id: pack + run: | + source helpers/artifact-helpers.sh + pack_packages build-output/edge/${{ matrix.arch }} packages.tar + tar -tvf packages.tar + echo "label=${{ matrix.package }}-${{ matrix.arch }}-$(git rev-parse "HEAD:pkgbuilds/${{ matrix.package }}")" >> "$GITHUB_OUTPUT" + - name: Upload artifact + if: steps.build.outputs.built == 'true' + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.pack.outputs.label }} + path: packages.tar + if-no-files-found: error + retention-days: 7 # One job for the whole merge. It collects every PR artifact for the - # merged tree (building only what has none), then walks each channel and + # merged tree (building only what has none; aarch64 comes from the + # rebuild job above), then walks each channel and # architecture slot exactly once: pull that database, add every package # that belongs in it, upload. Six slots, six round trips, however many # packages the merge carried. One process is the only writer, so there # is no race between packages; the run-level concurrency group above # keeps one merge from overlapping the next. + # It waits for the rebuild job and runs whatever that job's result: a + # failed rebuild leaves its package without an artifact, and the collect + # step below records that and stops before any publish. publish: - needs: changes - if: needs.changes.outputs.count != '0' + needs: [changes, rebuild] + if: ${{ !cancelled() && needs.changes.result == 'success' && needs.changes.outputs.count != '0' }} runs-on: [self-hosted, omarchy-builder] environment: publish timeout-minutes: 240 @@ -104,15 +194,22 @@ jobs: label="$package-$arch-$hash" found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \ - | jq -r '[.artifacts[] | select(.expired|not)] | sort_by(.created_at) | last | .archive_download_url // empty') + | jq -r '[.artifacts[] | select(.expired|not)] | sort_by(.created_at) | last | select(. != null) | "\(.archive_download_url) \(.workflow_run.id)"') + read -r found from_run <<<"$found" || true mkdir -p "build-output/edge/$arch" if [[ -n "$found" ]]; then - echo "==> $label: PR artifact" + if [[ $from_run == "${{ github.run_id }}" ]]; then + kind=native-rebuild + echo "==> $label: artifact from this run's native $arch rebuild" + else + kind=pr-artifact + echo "==> $label: reusing the build artifact from run $from_run" + fi rm -rf /tmp/artifact; mkdir -p /tmp/artifact if curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -o /tmp/artifact.zip "$found" \ && unzip -oq /tmp/artifact.zip -d /tmp/artifact \ && unpack_packages /tmp/artifact "build-output/edge/$arch"; then - jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"pr-artifact"}' >> sources.jsonl + jq -nc --arg p "$package" --arg a "$arch" --arg s "$kind" '{package:$p, arch:$a, source:$s}' >> sources.jsonl else jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"artifact-download-failed"}' >> sources.jsonl; failed=1; break fi @@ -128,6 +225,14 @@ jobs: jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"already-published"}' >> sources.jsonl continue fi + # aarch64 never builds here: this droplet is x86 and would + # emulate it. No artifact means the native rebuild failed (see + # the rebuild job), or an artifact expired between planning + # and now (re-run all jobs). + if [[ $arch == aarch64 ]]; then + echo "::error::$label: no artifact from the native rebuild; not building aarch64 under emulation" + jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"native-build-failed"}' >> sources.jsonl; failed=1; break + fi echo "==> $label: no artifact for this tree, building" if OMARCHY_KEEP_BUILD_WORKSPACE=1 bin/build --mirror edge --arch "$arch" --package "$package"; then jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"built"}' >> sources.jsonl @@ -269,7 +374,7 @@ jobs: run: | jq -r --arg outcome "${{ needs.publish.result }}" ' def pkgs: [.packages[] | select(test("-debug-")|not)] | join(", "); - def src: if .source=="pr-artifact" then "PR artifact" elif .source=="built" then "built here" elif .source=="already-published" then "already published, skipped" else "**" + .source + "**" end; + def src: if .source=="pr-artifact" then "PR artifact" elif .source=="native-rebuild" then "rebuilt natively on arm64" elif .source=="built" then "built here" elif .source=="already-published" then "already published, skipped" else "**" + .source + "**" end; "### Publish " + (if $outcome=="success" then "succeeded" else "FAILED" end) + " → **" + .target + "**" + (if .target!="live" then " (proof prefix, not live)" else "" end), "", @@ -322,5 +427,6 @@ jobs: runs-on: ubuntu-latest steps: - run: | - echo "publish result: ${{ needs.publish.result }}" + echo "changes result: ${{ needs.changes.result }}, publish result: ${{ needs.publish.result }}" + [[ "${{ needs.changes.result }}" == "success" ]] [[ "${{ needs.publish.result }}" == "success" || "${{ needs.publish.result }}" == "skipped" ]] diff --git a/ci/README.md b/ci/README.md index edb4f53..416f8d3 100644 --- a/ci/README.md +++ b/ci/README.md @@ -69,8 +69,9 @@ Watch it with `journalctl -u omarchy-controller -f` on the box. different bytes under an existing name, accept identical bytes, upload packages then signatures then the db. - aarch64 under QEMU with credential-preserving binfmt. PR builds now run - aarch64 natively on `ubuntu-24.04-arm` (QEMU was up to ~15x slower); publish.yml - still builds under QEMU when a merged tree has no PR artifact. + aarch64 natively on `ubuntu-24.04-arm` (QEMU was up to ~15x slower). When a + merged aarch64 tree has no artifact, publish.yml rebuilds it there too, in its + own job, and signs and uploads it on the droplet like a PR artifact. - Vouch gate: collaborators, `.github/VOUCHED.td`, or the `build-approved` label; denounced authors cannot be overridden by the label. - Tests run on PRs only; `result`, `self-tests`, `build-isolation` are the From 3dc749e4f206f5957082d65c98a07e24d3c301ca Mon Sep 17 00:00:00 2001 From: Jim Martin Date: Sun, 27 Sep 2026 15:11:35 -0500 Subject: [PATCH 23/32] Install LM Studio's launcher under its own desktop ID and URL scheme --- pkgbuilds/lmstudio-bin/PKGBUILD | 8 ++++---- pkgbuilds/lmstudio-bin/lmstudio.desktop | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkgbuilds/lmstudio-bin/PKGBUILD b/pkgbuilds/lmstudio-bin/PKGBUILD index dc1d90e..21823b9 100644 --- a/pkgbuilds/lmstudio-bin/PKGBUILD +++ b/pkgbuilds/lmstudio-bin/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: noureddinex pkgname=lmstudio-bin pkgver=0.4.25 -pkgrel=1 +pkgrel=2 _build=1 _pkgver=${pkgver}-${_build} pkgdesc="LM Studio - A desktop app for exploring and running large language models locally" @@ -16,7 +16,7 @@ conflicts=(lmstudio) source=("https://installers.lmstudio.ai/linux/x64/${_pkgver}/LM-Studio-${_pkgver}-x64.AppImage" "lmstudio.png" "lmstudio.desktop") -sha256sums=('eca467446c833824697e8befab300fe5269fdf984e3ee4385fcbad8502f07c53' '9f791789c959a11316328692807737a5f1bc1c170ae99ec04c56bfd8ee8263e5' '635dec12f3e3a57136b9e6fd7c2839ed6da7287fa55b482d64debf6eacf36baa') +sha256sums=('eca467446c833824697e8befab300fe5269fdf984e3ee4385fcbad8502f07c53' '9f791789c959a11316328692807737a5f1bc1c170ae99ec04c56bfd8ee8263e5' '16b67b6cd672a05b9a0e012c8b9a91187ceda8f85b3391db471298c54e002bcd') prepare() { chmod +x "${srcdir}/${source[0]##*/}" @@ -33,8 +33,8 @@ package() { install -Dm644 "${srcdir}/lmstudio.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/lmstudio-bin.png" install -Dm644 "${srcdir}/lmstudio.png" "${pkgdir}/usr/share/pixmaps/lmstudio-bin.png" - # Desktop entry - install -Dm644 "$srcdir/lmstudio.desktop" "$pkgdir/usr/share/applications/lmstudio.desktop" + # Desktop entry, under LM Studio's own desktop ID, which matches its window class + install -Dm644 "$srcdir/lmstudio.desktop" "$pkgdir/usr/share/applications/ai.elementlabs.lmstudio.desktop" # Symlink to binary install -dm755 "$pkgdir/usr/bin" diff --git a/pkgbuilds/lmstudio-bin/lmstudio.desktop b/pkgbuilds/lmstudio-bin/lmstudio.desktop index 01068cc..b2b364d 100644 --- a/pkgbuilds/lmstudio-bin/lmstudio.desktop +++ b/pkgbuilds/lmstudio-bin/lmstudio.desktop @@ -8,5 +8,5 @@ Type=Application Categories=Development;ArtificialIntelligence; Terminal=false StartupNotify=true -StartupWMClass=LM-Studio -MimeType=text/plain; +StartupWMClass=ai.elementlabs.lmstudio +MimeType=x-scheme-handler/lmstudio; From 1a94606fc412e6ecfcb1b23b25704b1a9166d53e Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Mon, 28 Sep 2026 07:15:47 +1000 Subject: [PATCH 24/32] omazed: package the 2.2.0 font helper and its dependencies omazed 2.2.0 added omazed-font.sh, which the recipe did not install, so font sync was silently skipped and `omazed set-font` failed. The helper also needs jq and perl. --- pkgbuilds/omazed/PKGBUILD | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/omazed/PKGBUILD b/pkgbuilds/omazed/PKGBUILD index 10c0b71..5af9b4e 100644 --- a/pkgbuilds/omazed/PKGBUILD +++ b/pkgbuilds/omazed/PKGBUILD @@ -1,11 +1,11 @@ pkgname=omazed pkgver=2.2.0 -pkgrel=1 +pkgrel=2 pkgdesc="Live theme switching for Zed in Omarchy - automatically synchronize your Zed editor theme with your Omarchy system theme" arch=('any') url="https://github.com/aps6/omazed" license=('MIT') -depends=('bash') +depends=('bash' 'jq' 'perl') makedepends=('git') backup=() install=omazed.install @@ -19,6 +19,7 @@ package() { install -Dm755 omazed "$pkgdir/usr/bin/omazed" install -Dm755 omazed-generator.sh "$pkgdir/usr/bin/omazed-generator.sh" install -Dm644 omazed-theme.tpl "$pkgdir/usr/bin/omazed-theme.tpl" + install -Dm755 omazed-font.sh "$pkgdir/usr/bin/omazed-font.sh" # Install documentation install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" From 62f3df23781bffbcdb114b97121e4d27fa6cf8f6 Mon Sep 17 00:00:00 2001 From: ryanrhughes <1630358+ryanrhughes@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:31:46 +0000 Subject: [PATCH 25/32] Track upstream branches: omarchy-dev 4.0.0.r6663.g3faafba, omarchy-settings-dev 4.0.0.r6663.g3faafba --- pkgbuilds/omarchy-dev/PKGBUILD | 6 +++--- pkgbuilds/omarchy-settings-dev/PKGBUILD | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkgbuilds/omarchy-dev/PKGBUILD b/pkgbuilds/omarchy-dev/PKGBUILD index eec9e2b..776904a 100644 --- a/pkgbuilds/omarchy-dev/PKGBUILD +++ b/pkgbuilds/omarchy-dev/PKGBUILD @@ -1,13 +1,13 @@ # Maintainer: Ryan Hughes pkgname='omarchy-dev' -pkgver=4.0.0.r6646.gbf44355 +pkgver=4.0.0.r6663.g3faafba pkgrel=1 # Pinned by the upstream watch in .omarchy/package.json (bin/sync-upstream): # every quattro tip becomes a commit pin here, so the package is versioned, # checksummed and built exactly like a release, just more often. The r-number # is the branch's total commit count, not the distance from the last tag: the # published history used the total, and pacman must never see it go down. -_commit=bf44355a985b1bb82cb33df47fc17f765f3b2c4f +_commit=3faafba234e530b0986196b98dc2c38951e7dd6f pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH (quattro branch tip)' # The payload is architecture-independent, but the dependency set is not: the # boot stack differs per architecture (see depends_x86_64 / depends_aarch64), @@ -81,7 +81,7 @@ makedepends=( # build from a local source tree instead (mirrors omarchy-zsh/omarchy-fish DX); # the arrays are emptied below so nothing is downloaded in that case. source=("omarchy::git+https://github.com/basecamp/omarchy.git#commit=${_commit}") -sha256sums=('7a90239fd67124279817d614fa6aa19554f40796c523ba698669fbf57fa41668') +sha256sums=('d97e0f12d9f17bfd78872bdc12edb63184b59483f055178d8d80597ed132882f') if [[ -n "${OMARCHY_SRC:-}" ]]; then source=() sha256sums=() diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 85968ac..9c0310b 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -1,13 +1,13 @@ # Maintainer: Ryan Hughes pkgname='omarchy-settings-dev' -pkgver=4.0.0.r6646.gbf44355 +pkgver=4.0.0.r6663.g3faafba pkgrel=1 # Pinned by the upstream watch in .omarchy/package.json (bin/sync-upstream): # every quattro tip becomes a commit pin here, so the package is versioned, # checksummed and built exactly like a release, just more often. The r-number # is the branch's total commit count, not the distance from the last tag: the # published history used the total, and pacman must never see it go down. -_commit=bf44355a985b1bb82cb33df47fc17f765f3b2c4f +_commit=3faafba234e530b0986196b98dc2c38951e7dd6f pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers (quattro branch tip)' # Arch-specific because the shipped /etc tree is not the same on every # architecture: the Limine, mkinitcpio, zram and oomd drop-ins belong to the @@ -117,7 +117,7 @@ _etc_override_paths=( # build from a local source tree instead (mirrors omarchy-zsh/omarchy-fish DX); # the arrays are emptied below so nothing is downloaded in that case. source=("omarchy::git+https://github.com/basecamp/omarchy.git#commit=${_commit}") -sha256sums=('7a90239fd67124279817d614fa6aa19554f40796c523ba698669fbf57fa41668') +sha256sums=('d97e0f12d9f17bfd78872bdc12edb63184b59483f055178d8d80597ed132882f') if [[ -n "${OMARCHY_SRC:-}" ]]; then source=() sha256sums=() From 4bf72efc0ac776093da88231c1310362f15abe90 Mon Sep 17 00:00:00 2001 From: dhh <2741+dhh@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:34:39 +0000 Subject: [PATCH 26/32] chore: rebuild against updated dependencies --- pkgbuilds/omareel/.omarchy/package.json | 7 ++++++- pkgbuilds/omareel/PKGBUILD | 2 +- pkgbuilds/quickshell-git/.omarchy/package.json | 8 +++++--- pkgbuilds/quickshell-git/PKGBUILD | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pkgbuilds/omareel/.omarchy/package.json b/pkgbuilds/omareel/.omarchy/package.json index fed2378..ad047d1 100644 --- a/pkgbuilds/omareel/.omarchy/package.json +++ b/pkgbuilds/omareel/.omarchy/package.json @@ -2,5 +2,10 @@ "source": "local", "rebuild_on": [ "hyprland" - ] + ], + "rebuilt_against": { + "x86_64": { + "hyprland": "0.56.2-3" + } + } } diff --git a/pkgbuilds/omareel/PKGBUILD b/pkgbuilds/omareel/PKGBUILD index e43106e..ff9155f 100644 --- a/pkgbuilds/omareel/PKGBUILD +++ b/pkgbuilds/omareel/PKGBUILD @@ -2,7 +2,7 @@ pkgname=omareel pkgver=0.1.0 -pkgrel=1 +pkgrel=2 pkgdesc='Screen recorder and editor for Omarchy with a synthetic cursor, auto zooms, and a camera bubble' arch=('x86_64' 'aarch64') url='https://github.com/omacom/omareel' diff --git a/pkgbuilds/quickshell-git/.omarchy/package.json b/pkgbuilds/quickshell-git/.omarchy/package.json index 457456e..4b3a44f 100644 --- a/pkgbuilds/quickshell-git/.omarchy/package.json +++ b/pkgbuilds/quickshell-git/.omarchy/package.json @@ -7,9 +7,11 @@ "qt6-wayland" ], "rebuilt_against": { - "qt6-base": "6.11.2-3", - "qt6-declarative": "6.11.2-1", - "qt6-wayland": "6.11.2-1" + "x86_64": { + "qt6-base": "6.11.2-3", + "qt6-declarative": "6.11.2-2", + "qt6-wayland": "6.11.2-1" + } }, "origin": { "aur": "quickshell-git", diff --git a/pkgbuilds/quickshell-git/PKGBUILD b/pkgbuilds/quickshell-git/PKGBUILD index 5d1dec6..629cbf6 100644 --- a/pkgbuilds/quickshell-git/PKGBUILD +++ b/pkgbuilds/quickshell-git/PKGBUILD @@ -3,7 +3,7 @@ _pkgname=quickshell pkgname="$_pkgname-git" pkgver=0.3.0.r20.g28771c7 -pkgrel=3 +pkgrel=4 pkgdesc='Flexible toolkit for making desktop shells with QtQuick' arch=(x86_64 aarch64) url='https://git.outfoxxed.me/quickshell/quickshell' From 29e03f68a28beaee8154f5708bc5e7dfc0291cf5 Mon Sep 17 00:00:00 2001 From: dhh <2741+dhh@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:43:16 +0000 Subject: [PATCH 27/32] chore: sync upstream releases --- pkgbuilds/ttfx/PKGBUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/ttfx/PKGBUILD b/pkgbuilds/ttfx/PKGBUILD index 8ef0380..38e1721 100644 --- a/pkgbuilds/ttfx/PKGBUILD +++ b/pkgbuilds/ttfx/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: David Heinemeier Hansson pkgname=ttfx -pkgver=0.4.0 +pkgver=0.5.0 pkgrel=1 pkgdesc="Terminal text effects as a single static binary — Rust port of terminaltexteffects" arch=('x86_64' 'aarch64') @@ -14,7 +14,7 @@ makedepends_x86_64=('nasm') options=('!debug') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('90a057971973917a45ae1cb2fc795cfaea33afc265180ba4a963172daf9f64ee') +sha256sums=('2882c7e47011a95f4d136303f7320da71613299840f67f88fd1385ef53d5f2a0') prepare() { cd "$pkgname-$pkgver" From b398111b0eb21dad89c7695490f3262b6eb0b8ad Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 27 Sep 2026 17:43:50 -0400 Subject: [PATCH 28/32] Drop NASM from ttfx's build dependencies ttfx 0.5 has no assembly engine; it is Rust only. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01MxJcVYpBxFDam6czK4bGjp --- pkgbuilds/ttfx/PKGBUILD | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkgbuilds/ttfx/PKGBUILD b/pkgbuilds/ttfx/PKGBUILD index 38e1721..13d3cc5 100644 --- a/pkgbuilds/ttfx/PKGBUILD +++ b/pkgbuilds/ttfx/PKGBUILD @@ -9,8 +9,6 @@ url="https://github.com/omacom/ttfx" license=('MIT') depends=('gcc-libs' 'glibc') makedepends=('cargo') -# the x86-64 assembly engine; without NASM the build falls back to pure Rust -makedepends_x86_64=('nasm') options=('!debug') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") From caac8448bef01d973225115ffcb87e29034abb4e Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Mon, 28 Sep 2026 07:53:14 +1000 Subject: [PATCH 29/32] omarchy-settings: pass #13362's platform-aware hooks through on aarch64 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. --- pkgbuilds/omarchy-settings-dev/PKGBUILD | 57 +++++- pkgbuilds/omarchy-settings/PKGBUILD | 57 +++++- .../omarchy-13362/00-omarchy-hooks.conf | 37 ++++ .../omarchy-13362/omarchy_hooks.conf | 53 ++++++ .../omarchy-mac-boot/90-omarchy-mac.conf | 18 ++ .../91-omarchy-mac-encrypt.conf | 90 ++++++++++ .../omarchy-mac-boot/92-omarchy-mac-hid.conf | 31 ++++ .../93-omarchy-mac-plymouth.conf | 18 ++ .../94-omarchy-mac-vconsole.conf | 46 +++++ .../settings-boot/omarchy_hooks-v4.0.4.conf | 52 ++++++ tests/settings-boot-config.sh | 167 +++++++++++++----- 11 files changed, 564 insertions(+), 62 deletions(-) create mode 100644 tests/fixtures/settings-boot/omarchy-13362/00-omarchy-hooks.conf create mode 100644 tests/fixtures/settings-boot/omarchy-13362/omarchy_hooks.conf create mode 100644 tests/fixtures/settings-boot/omarchy-mac-boot/90-omarchy-mac.conf create mode 100644 tests/fixtures/settings-boot/omarchy-mac-boot/91-omarchy-mac-encrypt.conf create mode 100644 tests/fixtures/settings-boot/omarchy-mac-boot/92-omarchy-mac-hid.conf create mode 100644 tests/fixtures/settings-boot/omarchy-mac-boot/93-omarchy-mac-plymouth.conf create mode 100644 tests/fixtures/settings-boot/omarchy-mac-boot/94-omarchy-mac-vconsole.conf create mode 100644 tests/fixtures/settings-boot/omarchy_hooks-v4.0.4.conf diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 2f22ec8..928ee44 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -135,6 +135,23 @@ prepare() { fi } +# Print the HOOKS that result from sourcing mkinitcpio drop-ins onto a line. +_omarchy_settings_hooks_after() { + local start=$1 + shift + ( + # Keep the build host's own tools, such as a platform detector, out of it. + PATH=/nonexistent + read -ra HOOKS <<<"$start" + MODULES=() FILES=() + for conf in "$@"; do + # shellcheck disable=SC1090 + source "$conf" || exit 1 + done + echo "${HOOKS[*]}" + ) +} + package() { cd "$srcdir/omarchy" @@ -203,6 +220,9 @@ package() { # stage separately below). install -d "$pkgdir/etc" cp -a etc/. "$pkgdir/etc/" + # omacom/omarchy#13362 moves the HOOKS baseline into its own drop-in. + [[ ! -f $pkgdir/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf ]] || + backup+=('etc/mkinitcpio.conf.d/00-omarchy-hooks.conf') if [[ $CARCH == aarch64 ]]; then # Keep omarchy_hooks.conf and the Limine entry-tool config: without them a # kernel update on an encrypted aarch64 install rebuilds an initramfs with @@ -213,17 +233,29 @@ package() { # Apple Silicon Macs install this package too. Their initramfs needs the # asahi hook, which omarchy-mac-boot's 90-94 fragments (Aurora) or # mkinitcpio.conf (legacy GRUB Macs) set, and an unconditional HOOKS= here - # would replace it. Omarchy's line applies only when the hooks loaded so far - # lack asahi. The check reads configuration, not the running machine, so it - # also holds when the image is built in a chroot. - local hooks_conf guarded=0 + # would replace it. A source that sets HOOKS outright (v4.0.4) gets its line + # wrapped so it applies only when the hooks loaded so far lack asahi; one that + # already decides per platform (omacom/omarchy#13362) ships as it is. The + # wrapper reads configuration, not the running machine, so it also holds + # when the image is built in a chroot. + local hooks_conf hooks_confs=() for hooks_conf in "$pkgdir"/etc/mkinitcpio.conf.d/{00-omarchy-hooks,omarchy_hooks}.conf; do - [[ -f $hooks_conf ]] && grep -q '^HOOKS=(' "$hooks_conf" || continue - sed -i 's/^\(HOOKS=(.*)\)$/if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then\n \1\nfi/' "$hooks_conf" - guarded=1 + [[ -f $hooks_conf ]] || continue + hooks_confs+=("$hooks_conf") + sed -i 's/^\(HOOKS=([^#]*)\)[[:space:]]*$/if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then\n \1\nfi/' "$hooks_conf" + if grep -q '^HOOKS=' "$hooks_conf"; then + echo "${hooks_conf#"$pkgdir"/}: cannot guard this HOOKS= line for Apple Silicon" >&2 + return 1 + fi done - if (( ! guarded )); then - echo "No HOOKS= line to guard for Apple Silicon in etc/mkinitcpio.conf.d" >&2 + # Whatever the layout, a Mac's asahi line must come through the shipped + # files, and a stock line must not: that is where Omarchy's hooks come from. + local mac_line='base udev block asahi encrypt filesystems fsck' stock_line='base udev block filesystems fsck' + local mac_hooks stock_hooks + if ! mac_hooks=$(_omarchy_settings_hooks_after "$mac_line" "${hooks_confs[@]}") || + ! stock_hooks=$(_omarchy_settings_hooks_after "$stock_line" "${hooks_confs[@]}") || + [[ $mac_hooks != "$mac_line" || $stock_hooks == "$stock_line" ]]; then + echo "etc/mkinitcpio.conf.d: the aarch64 hooks must keep a Mac's asahi line and set everyone else's" >&2 return 1 fi # Memory stack: no zram device or zswap on the aarch64 install, and @@ -376,6 +408,13 @@ EOF install -Dm755 bin/omarchy-hw-platform \ "$pkgdir/usr/share/libalpm/scripts/omarchy-hw-platform" fi + # 00-omarchy-hooks.conf places a Mac through this detector copy; without it + # an Aurora Mac gets the busybox line and its initramfs cannot unlock root. + if grep -qs omarchy-hw-platform "$pkgdir/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" && + [[ ! -x $pkgdir/usr/share/libalpm/scripts/omarchy-hw-platform ]]; then + echo "etc/mkinitcpio.conf.d/00-omarchy-hooks.conf needs the omarchy-hw-platform copy the source does not ship" >&2 + return 1 + fi # Branding assets (logos, icons). install -Dm644 logo.txt "$pkgdir/usr/share/omarchy/logo.txt" diff --git a/pkgbuilds/omarchy-settings/PKGBUILD b/pkgbuilds/omarchy-settings/PKGBUILD index 655189c..2043463 100644 --- a/pkgbuilds/omarchy-settings/PKGBUILD +++ b/pkgbuilds/omarchy-settings/PKGBUILD @@ -140,6 +140,23 @@ prepare() { fi } +# Print the HOOKS that result from sourcing mkinitcpio drop-ins onto a line. +_omarchy_settings_hooks_after() { + local start=$1 + shift + ( + # Keep the build host's own tools, such as a platform detector, out of it. + PATH=/nonexistent + read -ra HOOKS <<<"$start" + MODULES=() FILES=() + for conf in "$@"; do + # shellcheck disable=SC1090 + source "$conf" || exit 1 + done + echo "${HOOKS[*]}" + ) +} + package() { cd "$srcdir/omarchy" @@ -211,6 +228,9 @@ package() { # stage separately below). install -d "$pkgdir/etc" cp -a etc/. "$pkgdir/etc/" + # omacom/omarchy#13362 moves the HOOKS baseline into its own drop-in. + [[ ! -f $pkgdir/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf ]] || + backup+=('etc/mkinitcpio.conf.d/00-omarchy-hooks.conf') if [[ $CARCH == aarch64 ]]; then # Keep omarchy_hooks.conf and the Limine entry-tool config: without them a # kernel update on an encrypted aarch64 install rebuilds an initramfs with @@ -221,17 +241,29 @@ package() { # Apple Silicon Macs install this package too. Their initramfs needs the # asahi hook, which omarchy-mac-boot's 90-94 fragments (Aurora) or # mkinitcpio.conf (legacy GRUB Macs) set, and an unconditional HOOKS= here - # would replace it. Omarchy's line applies only when the hooks loaded so far - # lack asahi. The check reads configuration, not the running machine, so it - # also holds when the image is built in a chroot. - local hooks_conf guarded=0 + # would replace it. A source that sets HOOKS outright (v4.0.4) gets its line + # wrapped so it applies only when the hooks loaded so far lack asahi; one that + # already decides per platform (omacom/omarchy#13362) ships as it is. The + # wrapper reads configuration, not the running machine, so it also holds + # when the image is built in a chroot. + local hooks_conf hooks_confs=() for hooks_conf in "$pkgdir"/etc/mkinitcpio.conf.d/{00-omarchy-hooks,omarchy_hooks}.conf; do - [[ -f $hooks_conf ]] && grep -q '^HOOKS=(' "$hooks_conf" || continue - sed -i 's/^\(HOOKS=(.*)\)$/if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then\n \1\nfi/' "$hooks_conf" - guarded=1 + [[ -f $hooks_conf ]] || continue + hooks_confs+=("$hooks_conf") + sed -i 's/^\(HOOKS=([^#]*)\)[[:space:]]*$/if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then\n \1\nfi/' "$hooks_conf" + if grep -q '^HOOKS=' "$hooks_conf"; then + echo "${hooks_conf#"$pkgdir"/}: cannot guard this HOOKS= line for Apple Silicon" >&2 + return 1 + fi done - if (( ! guarded )); then - echo "No HOOKS= line to guard for Apple Silicon in etc/mkinitcpio.conf.d" >&2 + # Whatever the layout, a Mac's asahi line must come through the shipped + # files, and a stock line must not: that is where Omarchy's hooks come from. + local mac_line='base udev block asahi encrypt filesystems fsck' stock_line='base udev block filesystems fsck' + local mac_hooks stock_hooks + if ! mac_hooks=$(_omarchy_settings_hooks_after "$mac_line" "${hooks_confs[@]}") || + ! stock_hooks=$(_omarchy_settings_hooks_after "$stock_line" "${hooks_confs[@]}") || + [[ $mac_hooks != "$mac_line" || $stock_hooks == "$stock_line" ]]; then + echo "etc/mkinitcpio.conf.d: the aarch64 hooks must keep a Mac's asahi line and set everyone else's" >&2 return 1 fi # Memory stack: no zram device or zswap on the aarch64 install, and @@ -384,6 +416,13 @@ EOF install -Dm755 bin/omarchy-hw-platform \ "$pkgdir/usr/share/libalpm/scripts/omarchy-hw-platform" fi + # 00-omarchy-hooks.conf places a Mac through this detector copy; without it + # an Aurora Mac gets the busybox line and its initramfs cannot unlock root. + if grep -qs omarchy-hw-platform "$pkgdir/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" && + [[ ! -x $pkgdir/usr/share/libalpm/scripts/omarchy-hw-platform ]]; then + echo "etc/mkinitcpio.conf.d/00-omarchy-hooks.conf needs the omarchy-hw-platform copy the source does not ship" >&2 + return 1 + fi # Branding assets (logos, icons). install -Dm644 logo.txt "$pkgdir/usr/share/omarchy/logo.txt" diff --git a/tests/fixtures/settings-boot/omarchy-13362/00-omarchy-hooks.conf b/tests/fixtures/settings-boot/omarchy-13362/00-omarchy-hooks.conf new file mode 100644 index 0000000..8789786 --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy-13362/00-omarchy-hooks.conf @@ -0,0 +1,37 @@ +# The platform's HOOKS baseline. mkinitcpio sources mkinitcpio.conf and then +# every drop-in here in name order, so the baseline sorts first and the drop-ins +# after it add their hooks to it instead of being overwritten by it. +# +# Apple Silicon boots a systemd initramfs, and its platform package adds the +# firmware and encryption hooks. Every other machine keeps the busybox line. The +# runtime's detector answers, or else the copy omarchy-settings ships for its +# platform guard, so a settings package newer than the runtime still places a +# Mac. Without either, the busybox line stays. A detector that cannot place the +# machine stops the build rather than let it produce an image for the wrong +# platform. +_omarchy_platform="" +_omarchy_detector=$(command -v omarchy-hw-platform) || _omarchy_detector=/usr/share/libalpm/scripts/omarchy-hw-platform +if [[ -x $_omarchy_detector ]]; then + _omarchy_platform=$("$_omarchy_detector") || return 1 +fi + +# A Mac whose mkinitcpio.conf carries the asahi hook, or the busybox encrypt +# hook that unlocks it through cryptdevice= (sd-encrypt cannot parse that), boots +# the initramfs its platform set up without the Apple boot package, as a legacy +# install does. Its line stays as it is. The asahi hook also marks such a root +# off a Mac, as in a chroot or a VM. +if [[ " ${HOOKS[*]:-} " == *" asahi "* ]] || + [[ $_omarchy_platform == "apple-silicon" && " ${HOOKS[*]:-} " == *" encrypt "* ]]; then + _omarchy_platform=platform-owned +fi + +case $_omarchy_platform in + platform-owned) ;; + apple-silicon) + HOOKS=(base systemd plymouth autodetect microcode modconf kms keyboard sd-vconsole block filesystems fsck) + ;; + *) + HOOKS=(base udev plymouth keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems fsck btrfs-overlayfs) + ;; +esac +unset _omarchy_platform _omarchy_detector diff --git a/tests/fixtures/settings-boot/omarchy-13362/omarchy_hooks.conf b/tests/fixtures/settings-boot/omarchy-13362/omarchy_hooks.conf new file mode 100644 index 0000000..0dfcb8e --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy-13362/omarchy_hooks.conf @@ -0,0 +1,53 @@ +# Adjusts the baseline HOOKS from 00-omarchy-hooks.conf. It stays apart from +# the baseline because it reads what the hardware drop-ins sorting before it set. + +# The proprietary NVIDIA driver does early KMS itself: nvidia.conf (written by +# install/hardware/nvidia.sh, sourced before this file) early-loads nvidia_drm +# with modeset=1. Keeping the kms hook on such a system makes autodetect pull +# in nouveau — and ~100 MB of its GSP firmware — for a driver that never runs. +# Drop kms only when nvidia_drm is early-loaded and NVIDIA owns every display +# controller. Hybrid systems keep kms: their iGPU still needs it for early +# KMS at the LUKS prompt. So does anything unexpected, like a PCI tree that +# cannot be read. +# +# This reads MODULES midway through mkinitcpio's drop-in sourcing, so a +# later-sorting drop-in that resets MODULES outright — surface_device_modules.conf +# does — would strip nvidia_drm after kms was already dropped. Every machine +# Omarchy writes such a file for carries an Intel iGPU, which keeps kms here +# through the scan below; keep it that way. +if [[ " ${MODULES[*]:-} " == *" nvidia_drm "* ]]; then + _omarchy_nvidia_gpu=0 + _omarchy_other_gpu=0 + for _omarchy_pci in "${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}"/*; do + if [[ ! -r $_omarchy_pci/class || ! -r $_omarchy_pci/vendor ]]; then + # An unreadable device could be another GPU. Inconclusive keeps kms. + _omarchy_other_gpu=1 + continue + fi + [[ $(<"$_omarchy_pci/class") == "0x03"* ]] || continue + if [[ $(<"$_omarchy_pci/vendor") == "0x10de" ]]; then + _omarchy_nvidia_gpu=1 + else + _omarchy_other_gpu=1 + fi + done + if ((_omarchy_nvidia_gpu && !_omarchy_other_gpu)); then + _omarchy_hooks=() + for _omarchy_hook in "${HOOKS[@]}"; do + [[ $_omarchy_hook == "kms" ]] || _omarchy_hooks+=("$_omarchy_hook") + done + HOOKS=("${_omarchy_hooks[@]}") + fi + unset _omarchy_nvidia_gpu _omarchy_other_gpu _omarchy_pci _omarchy_hooks _omarchy_hook +fi + +# Bundle vconsole.conf so Plymouth uses the configured keyboard layout at the +# LUKS prompt, but only when that layout types Latin letters. Passphrases are +# Latin characters, so bundling a Hebrew/Greek/Cyrillic/Arabic layout would +# make the correct passphrase untypeable and lock the user out. +if [[ -f /etc/vconsole.conf ]]; then + case $(. /etc/vconsole.conf && echo "${XKBLAYOUT%%,*}") in + af | am | ara | bd | bg | by | et | ge | gr | il | in | iq | ir | kg | kh | kz | la | lk | mk | mm | mn | mv | np | rs | ru | sy | th | tj | ua) ;; + *) FILES+=(/etc/vconsole.conf) ;; + esac +fi diff --git a/tests/fixtures/settings-boot/omarchy-mac-boot/90-omarchy-mac.conf b/tests/fixtures/settings-boot/omarchy-mac-boot/90-omarchy-mac.conf new file mode 100644 index 0000000..0457b32 --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy-mac-boot/90-omarchy-mac.conf @@ -0,0 +1,18 @@ +# Generated by the Omarchy Apple Silicon image builder. +_omarchy_asahi_hooks=() +_omarchy_asahi_added=false +for _omarchy_asahi_hook in "${HOOKS[@]}"; do + if [[ $_omarchy_asahi_hook == asahi ]]; then + _omarchy_asahi_added=true + fi + if [[ $_omarchy_asahi_hook == filesystems && $_omarchy_asahi_added == false ]]; then + _omarchy_asahi_hooks+=(asahi omarchy-vendorfw) + _omarchy_asahi_added=true + fi + _omarchy_asahi_hooks+=("$_omarchy_asahi_hook") +done +if [[ $_omarchy_asahi_added == false ]]; then + _omarchy_asahi_hooks+=(asahi omarchy-vendorfw) +fi +HOOKS=("${_omarchy_asahi_hooks[@]}") +unset _omarchy_asahi_hooks _omarchy_asahi_hook _omarchy_asahi_added diff --git a/tests/fixtures/settings-boot/omarchy-mac-boot/91-omarchy-mac-encrypt.conf b/tests/fixtures/settings-boot/omarchy-mac-boot/91-omarchy-mac-encrypt.conf new file mode 100644 index 0000000..306582f --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy-mac-boot/91-omarchy-mac-encrypt.conf @@ -0,0 +1,90 @@ +# Insert omarchy-mac-encrypt after vendorfw/block and sd-encrypt immediately +# before filesystems, each only if that hook is absent. 90-omarchy-mac.conf +# already put asahi and omarchy-vendorfw before filesystems; this drop-in is +# sourced after it. +# +# Both are systemd initrd units: the systemd hook replaces udev (mkinitcpio's +# stock HOOKS line) and keymap/consolefont become sd-vconsole. A HOOKS line +# carrying the busybox encrypt hook belongs to a Mac unlocked by cryptdevice=, +# which sd-encrypt cannot parse: that line is left exactly as it is. +_omarchy_mac_encrypt_hooks=() +_omarchy_mac_encrypt_have_systemd=false +_omarchy_mac_encrypt_have_vconsole=false +_omarchy_mac_encrypt_have_encrypt=false +for _omarchy_mac_encrypt_hook in "${HOOKS[@]}"; do + case $_omarchy_mac_encrypt_hook in + systemd) _omarchy_mac_encrypt_have_systemd=true ;; + sd-vconsole) _omarchy_mac_encrypt_have_vconsole=true ;; + encrypt) _omarchy_mac_encrypt_have_encrypt=true ;; + esac +done +if [[ $_omarchy_mac_encrypt_have_encrypt == false ]]; then +for _omarchy_mac_encrypt_hook in "${HOOKS[@]}"; do + case $_omarchy_mac_encrypt_hook in + udev) + if [[ $_omarchy_mac_encrypt_have_systemd == false ]]; then + _omarchy_mac_encrypt_hooks+=(systemd) + _omarchy_mac_encrypt_have_systemd=true + fi + ;; + keymap|consolefont) + if [[ $_omarchy_mac_encrypt_have_vconsole == false ]]; then + _omarchy_mac_encrypt_hooks+=(sd-vconsole) + _omarchy_mac_encrypt_have_vconsole=true + fi + ;; + *) _omarchy_mac_encrypt_hooks+=("$_omarchy_mac_encrypt_hook") ;; + esac +done +if [[ $_omarchy_mac_encrypt_have_systemd == false ]]; then + if [[ ${_omarchy_mac_encrypt_hooks[0]:-} == base ]]; then + _omarchy_mac_encrypt_hooks=(base systemd "${_omarchy_mac_encrypt_hooks[@]:1}") + else + _omarchy_mac_encrypt_hooks=(systemd "${_omarchy_mac_encrypt_hooks[@]}") + fi +fi +HOOKS=("${_omarchy_mac_encrypt_hooks[@]}") +_omarchy_mac_encrypt_hooks=() +_omarchy_mac_encrypt_have_ours=false +_omarchy_mac_encrypt_have_sd=false +_omarchy_mac_encrypt_added_ours=false +_omarchy_mac_encrypt_added_sd=false +for _omarchy_mac_encrypt_hook in "${HOOKS[@]}"; do + if [[ $_omarchy_mac_encrypt_hook == omarchy-mac-encrypt ]]; then + _omarchy_mac_encrypt_have_ours=true + fi + if [[ $_omarchy_mac_encrypt_hook == sd-encrypt ]]; then + _omarchy_mac_encrypt_have_sd=true + fi +done +for _omarchy_mac_encrypt_hook in "${HOOKS[@]}"; do + if [[ $_omarchy_mac_encrypt_hook == sd-encrypt && $_omarchy_mac_encrypt_have_ours == false && + $_omarchy_mac_encrypt_added_ours == false ]]; then + _omarchy_mac_encrypt_hooks+=(omarchy-mac-encrypt) + _omarchy_mac_encrypt_added_ours=true + fi + if [[ $_omarchy_mac_encrypt_hook == filesystems ]]; then + if [[ $_omarchy_mac_encrypt_have_ours == false && $_omarchy_mac_encrypt_added_ours == false ]]; then + _omarchy_mac_encrypt_hooks+=(omarchy-mac-encrypt) + _omarchy_mac_encrypt_added_ours=true + fi + if [[ $_omarchy_mac_encrypt_have_sd == false && $_omarchy_mac_encrypt_added_sd == false ]]; then + _omarchy_mac_encrypt_hooks+=(sd-encrypt) + _omarchy_mac_encrypt_added_sd=true + fi + fi + _omarchy_mac_encrypt_hooks+=("$_omarchy_mac_encrypt_hook") +done +if [[ $_omarchy_mac_encrypt_have_ours == false && $_omarchy_mac_encrypt_added_ours == false ]]; then + _omarchy_mac_encrypt_hooks+=(omarchy-mac-encrypt) +fi +if [[ $_omarchy_mac_encrypt_have_sd == false && $_omarchy_mac_encrypt_added_sd == false ]]; then + _omarchy_mac_encrypt_hooks+=(sd-encrypt) +fi +HOOKS=("${_omarchy_mac_encrypt_hooks[@]}") +fi +unset _omarchy_mac_encrypt_hooks _omarchy_mac_encrypt_hook \ + _omarchy_mac_encrypt_have_ours _omarchy_mac_encrypt_have_sd \ + _omarchy_mac_encrypt_added_ours _omarchy_mac_encrypt_added_sd \ + _omarchy_mac_encrypt_have_systemd _omarchy_mac_encrypt_have_vconsole \ + _omarchy_mac_encrypt_have_encrypt diff --git a/tests/fixtures/settings-boot/omarchy-mac-boot/92-omarchy-mac-hid.conf b/tests/fixtures/settings-boot/omarchy-mac-boot/92-omarchy-mac-hid.conf new file mode 100644 index 0000000..b75e8a5 --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy-mac-boot/92-omarchy-mac-hid.conf @@ -0,0 +1,31 @@ +# Origin: omarchy-mx-mac install/hardware/apple/fix-asahi-hid-race.sh +# Apple Silicon internal keyboard and trackpad at the sd-encrypt passphrase +# prompt. dockchannel-hid creates the HID devices; hid_apple binds the +# keyboard and hid_magicmouse the SPI trackpad. MTP machines (M2 Air, M2 Max +# j416c) use dockchannel-hid — linux-aurora has no apple-mtp module. SPI HID +# (M1 Air, M1 Pro j314s) is spi-hid-apple-of, built-in on aurora. usbhid is +# an external USB keyboard at the prompt. thunderbolt (CONFIG_USB4=m) and +# thunderbolt_apple, the Apple Silicon USB4 host router (CONFIG_USB4_APPLE_SOC=m +# on linux-aurora), bring up the USB4/Thunderbolt tunnels, so a keyboard behind +# a USB-C or Thunderbolt dock types at the prompt too (omarchy-mx-mac#86). +# +# mkinitcpio fails the whole image over a MODULES entry it cannot find, or a +# built-in it reports as "(builtin)". Each name is added only when modinfo +# returns a real path. Ending on unset keeps the exit status zero. + +for _omarchy_mac_hid_module in \ + hid_apple hid_magicmouse dockchannel-hid usbhid \ + spi-apple spi-hid-apple spi-hid-apple-of \ + apple-dockchannel apple-rtkit-helper thunderbolt thunderbolt_apple; do + _omarchy_mac_hid_path=$(modinfo -k "${KERNELVERSION:-$(uname -r)}" -F filename \ + "$_omarchy_mac_hid_module" 2>/dev/null) || continue + [[ $_omarchy_mac_hid_path == /* ]] || continue + MODULES+=("$_omarchy_mac_hid_module") +done +unset _omarchy_mac_hid_module _omarchy_mac_hid_path + +# MTP trackpad firmware (apple/tpmtfw-.bin) is not a MODULES entry. +# omarchy-vendorfw-initrd.service unpacks ESP vendorfw/firmware.cpio onto +# /lib/firmware/vendor (symlink to /vendorfw) before cryptsetup-pre.target. +# Do not FILES+= under /lib/firmware/vendor, and do not pre-create +# /vendorfw/apple in the image: that skipped ESP extraction. diff --git a/tests/fixtures/settings-boot/omarchy-mac-boot/93-omarchy-mac-plymouth.conf b/tests/fixtures/settings-boot/omarchy-mac-boot/93-omarchy-mac-plymouth.conf new file mode 100644 index 0000000..75cbbb5 --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy-mac-boot/93-omarchy-mac-plymouth.conf @@ -0,0 +1,18 @@ +# Plymouth draws the disk password prompt and the boot splash, as on x86 +# Omarchy. It goes right after systemd so its initrd units order correctly, +# only when the hook is installed, and never twice. +if [[ -f /usr/lib/initcpio/install/plymouth && " ${HOOKS[*]} " != *" plymouth "* ]]; then + _omarchy_mac_plymouth_hooks=() + _omarchy_mac_plymouth_added=false + for _omarchy_mac_plymouth_hook in "${HOOKS[@]}"; do + _omarchy_mac_plymouth_hooks+=("$_omarchy_mac_plymouth_hook") + if [[ $_omarchy_mac_plymouth_hook == systemd && $_omarchy_mac_plymouth_added == false ]]; then + _omarchy_mac_plymouth_hooks+=(plymouth) + _omarchy_mac_plymouth_added=true + fi + done + if [[ $_omarchy_mac_plymouth_added == true ]]; then + HOOKS=("${_omarchy_mac_plymouth_hooks[@]}") + fi + unset _omarchy_mac_plymouth_hooks _omarchy_mac_plymouth_added _omarchy_mac_plymouth_hook +fi diff --git a/tests/fixtures/settings-boot/omarchy-mac-boot/94-omarchy-mac-vconsole.conf b/tests/fixtures/settings-boot/omarchy-mac-boot/94-omarchy-mac-vconsole.conf new file mode 100644 index 0000000..9547df7 --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy-mac-boot/94-omarchy-mac-vconsole.conf @@ -0,0 +1,46 @@ +# The disk passphrase prompt types with the owner's keyboard layout, as on +# x86 Omarchy: sd-vconsole loads KEYMAP on the console (systemd-ask-password) +# and /etc/vconsole.conf gives Plymouth its XKBLAYOUT. The aarch64 +# omarchy-settings drops upstream's omarchy_hooks.conf, so this drop-in +# carries its guard: a layout that does not type Latin letters stays out of +# the initramfs, because a Latin passphrase would be untypeable in it +# (upstream #6229). The prompt then uses the kernel's US map, which is what +# such a passphrase was typed with. +# +# A systemd HOOKS line gets sd-vconsole exactly once, after keyboard (or +# after systemd without one); keymap and consolefont are its busybox +# counterparts and never belong on such a line. A busybox line (a Mac +# unlocked by cryptdevice=, which 91 leaves alone) keeps its hooks; it only +# gets the file for Plymouth, as upstream does. +# No vconsole.conf is the kernel's US map: sd-vconsole stays, nothing to bundle. +_omarchy_mac_vconsole_latin=true +if [[ -f /etc/vconsole.conf ]]; then + _omarchy_mac_vconsole_layout=$(unset XKBLAYOUT; . /etc/vconsole.conf 2>/dev/null; printf '%s' "${XKBLAYOUT:-}") + case ${_omarchy_mac_vconsole_layout%%,*} in + af | am | ara | bd | bg | by | et | ge | gr | il | in | iq | ir | kg | kh | kz | la | lk | mk | mm | mn | mv | np | rs | ru | sy | th | tj | ua) + _omarchy_mac_vconsole_latin=false ;; + esac +fi + +if [[ " ${HOOKS[*]} " == *" systemd "* ]]; then + _omarchy_mac_vconsole_hooks=() + _omarchy_mac_vconsole_anchor=systemd + [[ " ${HOOKS[*]} " != *" keyboard "* ]] || _omarchy_mac_vconsole_anchor=keyboard + for _omarchy_mac_vconsole_hook in "${HOOKS[@]}"; do + case $_omarchy_mac_vconsole_hook in + sd-vconsole | keymap | consolefont) continue ;; + esac + _omarchy_mac_vconsole_hooks+=("$_omarchy_mac_vconsole_hook") + if [[ $_omarchy_mac_vconsole_hook == "$_omarchy_mac_vconsole_anchor" && $_omarchy_mac_vconsole_latin == true ]]; then + _omarchy_mac_vconsole_hooks+=(sd-vconsole) + _omarchy_mac_vconsole_anchor= + fi + done + HOOKS=("${_omarchy_mac_vconsole_hooks[@]}") +fi + +if [[ $_omarchy_mac_vconsole_latin == true && -f /etc/vconsole.conf ]]; then + FILES+=(/etc/vconsole.conf) +fi +unset _omarchy_mac_vconsole_latin _omarchy_mac_vconsole_layout _omarchy_mac_vconsole_hooks \ + _omarchy_mac_vconsole_anchor _omarchy_mac_vconsole_hook diff --git a/tests/fixtures/settings-boot/omarchy_hooks-v4.0.4.conf b/tests/fixtures/settings-boot/omarchy_hooks-v4.0.4.conf new file mode 100644 index 0000000..68408b0 --- /dev/null +++ b/tests/fixtures/settings-boot/omarchy_hooks-v4.0.4.conf @@ -0,0 +1,52 @@ +HOOKS=(base udev plymouth keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems fsck btrfs-overlayfs) + +# The proprietary NVIDIA driver does early KMS itself: nvidia.conf (written by +# install/hardware/nvidia.sh, sourced before this file) early-loads nvidia_drm +# with modeset=1. Keeping the kms hook on such a system makes autodetect pull +# in nouveau — and ~100 MB of its GSP firmware — for a driver that never runs. +# Drop kms only when nvidia_drm is early-loaded and NVIDIA owns every display +# controller. Hybrid systems keep kms: their iGPU still needs it for early +# KMS at the LUKS prompt. So does anything unexpected, like a PCI tree that +# cannot be read. +# +# This reads MODULES midway through mkinitcpio's drop-in sourcing, so a +# later-sorting drop-in that resets MODULES outright — surface_device_modules.conf +# does — would strip nvidia_drm after kms was already dropped. Every machine +# Omarchy writes such a file for carries an Intel iGPU, which keeps kms here +# through the scan below; keep it that way. +if [[ " ${MODULES[*]:-} " == *" nvidia_drm "* ]]; then + _omarchy_nvidia_gpu=0 + _omarchy_other_gpu=0 + for _omarchy_pci in "${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}"/*; do + if [[ ! -r $_omarchy_pci/class || ! -r $_omarchy_pci/vendor ]]; then + # An unreadable device could be another GPU. Inconclusive keeps kms. + _omarchy_other_gpu=1 + continue + fi + [[ $(<"$_omarchy_pci/class") == "0x03"* ]] || continue + if [[ $(<"$_omarchy_pci/vendor") == "0x10de" ]]; then + _omarchy_nvidia_gpu=1 + else + _omarchy_other_gpu=1 + fi + done + if ((_omarchy_nvidia_gpu && !_omarchy_other_gpu)); then + _omarchy_hooks=() + for _omarchy_hook in "${HOOKS[@]}"; do + [[ $_omarchy_hook == "kms" ]] || _omarchy_hooks+=("$_omarchy_hook") + done + HOOKS=("${_omarchy_hooks[@]}") + fi + unset _omarchy_nvidia_gpu _omarchy_other_gpu _omarchy_pci _omarchy_hooks _omarchy_hook +fi + +# Bundle vconsole.conf so Plymouth uses the configured keyboard layout at the +# LUKS prompt, but only when that layout types Latin letters. Passphrases are +# Latin characters, so bundling a Hebrew/Greek/Cyrillic/Arabic layout would +# make the correct passphrase untypeable and lock the user out. +if [[ -f /etc/vconsole.conf ]]; then + case $(. /etc/vconsole.conf && echo "${XKBLAYOUT%%,*}") in + af | am | ara | bd | bg | by | et | ge | gr | il | in | iq | ir | kg | kh | kz | la | lk | mk | mm | mn | mv | np | rs | ru | sy | th | tj | ua) ;; + *) FILES+=(/etc/vconsole.conf) ;; + esac +fi diff --git a/tests/settings-boot-config.sh b/tests/settings-boot-config.sh index dfbe742..5528925 100755 --- a/tests/settings-boot-config.sh +++ b/tests/settings-boot-config.sh @@ -63,9 +63,10 @@ for path in "${files[@]}"; do printf 'fixture for %s\n' "$path" > "$fixture/$path" done -# Omarchy's HOOKS line, as its sources ship it. +fixtures=$BUILD_ROOT/tests/fixtures/settings-boot +# Omarchy's HOOKS line, as v4.0.4 ships it (omarchy_hooks-v4.0.4.conf). omarchy_hooks='base udev plymouth keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems fsck btrfs-overlayfs' -printf 'HOOKS=(%s)\n' "$omarchy_hooks" > "$fixture/etc/mkinitcpio.conf.d/omarchy_hooks.conf" +cp "$fixtures/omarchy_hooks-v4.0.4.conf" "$fixture/etc/mkinitcpio.conf.d/omarchy_hooks.conf" for recipe in omarchy-settings omarchy-settings-dev; do for target_arch in aarch64 x86_64; do @@ -95,6 +96,10 @@ for recipe in omarchy-settings omarchy-settings-dev; do for template in default.conf limine.conf; do cmp "$fixture/default/limine/$template" "$pkgdir/usr/share/omarchy/default/limine/$template" done + if printf '%s\n' "${backup[@]}" | grep -Fxq etc/mkinitcpio.conf.d/00-omarchy-hooks.conf; then + echo 'FAIL: backup names a 00-omarchy-hooks.conf the source does not ship' >&2 + exit 1 + fi # The installer owns the machine-specific live configuration. [[ ! -e $pkgdir/etc/default/limine ]] if printf '%s\n' "${backup[@]}" | grep -Fxq 'etc/default/limine'; then @@ -124,7 +129,8 @@ done # The aarch64 packages also reach Apple Silicon Macs, whose initramfs needs the # asahi hook. Source mkinitcpio.conf and the drop-ins in mkinitcpio's order and -# compare the resulting HOOKS for each kind of aarch64 install. +# compare the resulting HOOKS for each kind of aarch64 install. Aurora Macs use +# omarchy-mac-boot's real 90-94 fragments (omacom/omarchy-mac ff7ce0d4d). package_aarch64() { local recipe=$1 source_tree=$2 out=$3 ( @@ -133,15 +139,25 @@ package_aarch64() { backup=() # shellcheck disable=SC1090 # Exercise the recipe's actual package function. source "$BUILD_ROOT/pkgbuilds/$recipe/PKGBUILD" - package + package || exit 1 + printf '%s\n' "${backup[@]}" > "$out.backup" ) } +# omacom/omarchy#13362 asks omarchy-hw-platform which machine it is on. +for platform in apple-silicon qualcomm generic-aarch64; do + mkdir -p "$scratch/detector-$platform" + printf '#!/bin/sh\necho %s\n' "$platform" > "$scratch/detector-$platform/omarchy-hw-platform" + chmod +x "$scratch/detector-$platform/omarchy-hw-platform" +done + +# effective_hooks ROOT [PLATFORM] effective_hooks() { - local root=$1 + local root=$1 platform=${2:-} ( LC_ALL=C - HOOKS=() + [[ -z $platform ]] || PATH=$scratch/detector-$platform:$PATH + HOOKS=() MODULES=() FILES=() # shellcheck disable=SC1091 source "$root/mkinitcpio.conf" shopt -s nullglob @@ -153,63 +169,119 @@ effective_hooks() { ) } -# machine NAME MKINITCPIO_HOOKS PACKAGED_ETC [FRAGMENT FRAGMENT_HOOKS] +# machine NAME MKINITCPIO_HOOKS PACKAGED_ETC [mac-boot] machine() { local root=$scratch/machines/$1 + rm -rf "$root" mkdir -p "$root/mkinitcpio.conf.d" printf 'HOOKS=(%s)\n' "$2" > "$root/mkinitcpio.conf" - cp "$3"/*.conf "$root/mkinitcpio.conf.d/" - if (($# == 5)); then - printf 'HOOKS=(%s)\n' "$5" > "$root/mkinitcpio.conf.d/$4" - fi + [[ -z $3 ]] || cp "$3"/*.conf "$root/mkinitcpio.conf.d/" + [[ ${4:-} != mac-boot ]] || cp "$fixtures"/omarchy-mac-boot/*.conf "$root/mkinitcpio.conf.d/" printf '%s\n' "$root" } +expect() { + local layout=$1 what=$2 got=$3 want=$4 + [[ $got == "$want" ]] || { echo "FAIL: $layout: $what gets '$got', want '$want'" >&2; exit 1; } +} + arch_default='base udev autodetect microcode modconf kms keyboard keymap consolefont block filesystems fsck' snapdragon='base systemd autodetect microcode modconf kms keyboard sd-vconsole block filesystems fsck' legacy_mac='base udev autodetect modconf kms keyboard keymap consolefont block asahi encrypt filesystems fsck' -aurora_mac='base udev autodetect modconf kms keyboard keymap consolefont block asahi omarchy-vendorfw omarchy-mac-encrypt sd-encrypt filesystems fsck' -check_machines() { - local layout=$1 packaged=$2/etc/mkinitcpio.conf.d root - rm -rf "$scratch/machines" - root=$(machine snapdragon "$snapdragon" "$packaged") - [[ $(effective_hooks "$root") == "$omarchy_hooks" ]] || - { echo "FAIL: $layout: Snapdragon gets $(effective_hooks "$root")" >&2; exit 1; } - root=$(machine spark "$arch_default" "$packaged") - [[ $(effective_hooks "$root") == "$omarchy_hooks" ]] || - { echo "FAIL: $layout: DGX Spark gets $(effective_hooks "$root")" >&2; exit 1; } - root=$(machine legacy-mac "$legacy_mac" "$packaged") - [[ $(effective_hooks "$root") == "$legacy_mac" ]] || - { echo "FAIL: $layout: a legacy GRUB Mac loses asahi: $(effective_hooks "$root")" >&2; exit 1; } - root=$(machine aurora-mac "$arch_default" "$packaged" 92-omarchy-mac-boot.conf "$aurora_mac") - [[ $(effective_hooks "$root") == "$aurora_mac" ]] || - { echo "FAIL: $layout: an Aurora Mac loses its hooks: $(effective_hooks "$root")" >&2; exit 1; } - echo "PASS: $layout: Snapdragon and the Spark get Omarchy's hooks; Macs keep asahi" +# Macs must come out exactly as they would without omarchy-settings' drop-ins. +check_macs() { + local layout=$1 packaged=$2 base + for base in "$arch_default" "$snapdragon"; do + expect "$layout" "an Aurora Mac" \ + "$(effective_hooks "$(machine aurora "$base" "$packaged" mac-boot)")" \ + "$(effective_hooks "$(machine aurora-bare "$base" "" mac-boot)")" + done + expect "$layout" "a legacy GRUB Mac" \ + "$(effective_hooks "$(machine legacy "$legacy_mac" "$packaged")")" "$legacy_mac" + expect "$layout" "a legacy GRUB Mac with the Apple fragments" \ + "$(effective_hooks "$(machine legacy-boot "$legacy_mac" "$packaged" mac-boot)")" \ + "$(effective_hooks "$(machine legacy-boot-bare "$legacy_mac" "" mac-boot)")" } -check_machines "HOOKS in omarchy_hooks.conf" "$scratch/omarchy-settings-aarch64" +layout="HOOKS in omarchy_hooks.conf (v4.0.4)" +packaged=$scratch/omarchy-settings-aarch64/etc/mkinitcpio.conf.d +expect "$layout" Snapdragon "$(effective_hooks "$(machine snapdragon "$snapdragon" "$packaged")")" "$omarchy_hooks" +expect "$layout" "the DGX Spark" "$(effective_hooks "$(machine spark "$arch_default" "$packaged")")" "$omarchy_hooks" +check_macs "$layout" "$packaged" +echo "PASS: $layout: Snapdragon and the Spark get Omarchy's hooks; Macs keep theirs" -# omacom/omarchy#13362 moves the HOOKS line into 00-omarchy-hooks.conf. +# omacom/omarchy#13362 decides per platform in 00-omarchy-hooks.conf; the +# package ships both of its files unchanged. split=$scratch/split/omarchy mkdir -p "$scratch/split" cp -a "$fixture" "$split" -printf 'HOOKS=(%s)\n' "$omarchy_hooks" > "$split/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" -sed -i '/^HOOKS=/d' "$split/etc/mkinitcpio.conf.d/omarchy_hooks.conf" -package_aarch64 omarchy-settings "$split" "$scratch/split-package" >/dev/null -grep -Fxq 'if [[ " ${HOOKS[*]:-} " != *" asahi "* ]]; then' \ - "$scratch/split-package/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" -check_machines "HOOKS in 00-omarchy-hooks.conf" "$scratch/split-package" +cp "$fixtures"/omarchy-13362/*.conf "$split/etc/mkinitcpio.conf.d/" +# Its 00-omarchy-hooks.conf asks the detector copy the platform guard ships. +for path in default/libalpm/hooks/00-omarchy-platform-guard.hook \ + default/libalpm/scripts/omarchy-platform-guard bin/omarchy-hw-platform; do + mkdir -p "$(dirname "$split/$path")" + printf 'fixture for %s\n' "$path" > "$split/$path" +done +for recipe in omarchy-settings omarchy-settings-dev; do + package_aarch64 "$recipe" "$split" "$scratch/split-$recipe" >/dev/null + for conf in 00-omarchy-hooks.conf omarchy_hooks.conf; do + cmp "$fixtures/omarchy-13362/$conf" "$scratch/split-$recipe/etc/mkinitcpio.conf.d/$conf" + done + grep -Fxq etc/mkinitcpio.conf.d/00-omarchy-hooks.conf "$scratch/split-$recipe.backup" + [[ -x $scratch/split-$recipe/usr/share/libalpm/scripts/omarchy-hw-platform ]] +done +layout="omacom/omarchy#13362 (00-omarchy-hooks.conf)" +packaged=$scratch/split-omarchy-settings/etc/mkinitcpio.conf.d +for platform in "" qualcomm generic-aarch64; do + expect "$layout" "Snapdragon (${platform:-no detector})" \ + "$(effective_hooks "$(machine snapdragon "$snapdragon" "$packaged")" "$platform")" "$omarchy_hooks" + expect "$layout" "the DGX Spark (${platform:-no detector})" \ + "$(effective_hooks "$(machine spark "$arch_default" "$packaged")" "$platform")" "$omarchy_hooks" +done +expect "$layout" "a legacy GRUB Mac" \ + "$(effective_hooks "$(machine legacy "$legacy_mac" "$packaged")" apple-silicon)" "$legacy_mac" +# An Aurora Mac builds on the systemd baseline and unlocks with sd-encrypt. +hooks=" $(effective_hooks "$(machine aurora "$arch_default" "$packaged" mac-boot)" apple-silicon) " +for hook in systemd asahi omarchy-vendorfw omarchy-mac-encrypt sd-encrypt; do + [[ $hooks == *" $hook "* ]] || { echo "FAIL: $layout: an Aurora Mac lacks $hook:$hooks" >&2; exit 1; } +done +for hook in udev encrypt; do + [[ $hooks != *" $hook "* ]] || { echo "FAIL: $layout: an Aurora Mac keeps $hook:$hooks" >&2; exit 1; } +done +echo "PASS: $layout: shipped unchanged and backed up; Snapdragon and the Spark get Omarchy's hooks; Macs keep theirs" -sed -i '/^HOOKS=/d' "$split/etc/mkinitcpio.conf.d/00-omarchy-hooks.conf" -if package_aarch64 omarchy-settings "$split" "$scratch/unguarded-package" 2>/dev/null; then - echo 'FAIL: the aarch64 package builds without a HOOKS line to guard' >&2 - exit 1 -fi -echo "PASS: the aarch64 package fails to build without a HOOKS line to guard" +# Sources the recipe cannot make safe for Macs stop the aarch64 build, each +# with its own reason. +refuse() { + local what=$1 reason=$2 conf=$3 body=$4 bad=$scratch/bad/omarchy + rm -rf "$scratch/bad" "$scratch/bad-package" + mkdir -p "$scratch/bad" + cp -a "$fixture" "$bad" + rm -f "$bad"/etc/mkinitcpio.conf.d/{00-omarchy-hooks,omarchy_hooks}.conf + [[ -z $conf ]] || printf '%s\n' "$body" > "$bad/etc/mkinitcpio.conf.d/$conf" + if package_aarch64 omarchy-settings "$bad" "$scratch/bad-package" 2>"$scratch/bad.err"; then + echo "FAIL: the aarch64 package builds with $what" >&2 + exit 1 + fi + grep -Fq "$reason" "$scratch/bad.err" || + { echo "FAIL: $what stops the build for another reason: $(cat "$scratch/bad.err")" >&2; exit 1; } + echo "PASS: the aarch64 package refuses $what" +} +unsafe="must keep a Mac's asahi line" +unguardable="cannot guard this HOOKS= line" +refuse "no hooks file" "$unsafe" "" "" +refuse "a hooks file that sets no HOOKS" "$unsafe" omarchy_hooks.conf 'FILES+=(/etc/vconsole.conf)' +refuse "a HOOKS line with a trailing comment" "$unguardable" omarchy_hooks.conf "HOOKS=($omarchy_hooks) # local" +refuse "a HOOKS line split over lines" "$unguardable" omarchy_hooks.conf "HOOKS=(base udev"$'\n'" block encrypt filesystems)" +refuse "an indented HOOKS that ignores asahi" "$unsafe" 00-omarchy-hooks.conf "if true; then"$'\n'" HOOKS=($omarchy_hooks)"$'\n'"fi" +refuse "#13362's hooks without the platform detector" "needs the omarchy-hw-platform copy" \ + 00-omarchy-hooks.conf "$(cat "$fixtures/omarchy-13362/00-omarchy-hooks.conf")" # Upgrades: pacman replaces an unmodified hooks file, keeps a modified one and # leaves the guarded version as .pacnew, and installs it where it was absent. +# A file restored by hand after the stripped package (as the Spark and Surface +# owners did) is adopted: it stays in place and the guarded one is .pacnew. if ((EUID != 0)) || ! command -v pacman >/dev/null; then echo "SKIP: pacman upgrade checks need root" exit 0 @@ -264,10 +336,17 @@ pacman_in "$scratch/absent" -U "$stripped" pacman_in "$scratch/absent" -U "$new" cmp "$guarded" "$scratch/absent/$installed" +pacman_in "$scratch/restored" -U "$stripped" +mkdir -p "$scratch/restored/etc/mkinitcpio.conf.d" +cp "$unguarded" "$scratch/restored/$installed" +pacman_in "$scratch/restored" -U "$new" +cmp "$unguarded" "$scratch/restored/$installed" +cmp "$guarded" "$scratch/restored/$installed.pacnew" + # Source what the upgrades installed. for upgrade in unchanged absent; do etc=$scratch/$upgrade/etc/mkinitcpio.conf.d - [[ $(effective_hooks "$(machine "$upgrade-snapdragon" "$snapdragon" "$etc")") == "$omarchy_hooks" ]] - [[ $(effective_hooks "$(machine "$upgrade-legacy-mac" "$legacy_mac" "$etc")") == "$legacy_mac" ]] + expect "$upgrade upgrade" Snapdragon "$(effective_hooks "$(machine "$upgrade-snapdragon" "$snapdragon" "$etc")")" "$omarchy_hooks" + check_macs "$upgrade upgrade" "$etc" done echo "PASS: pacman upgrades install the guarded hooks file and keep local changes" From 45ae938a58418dde912406e782e21236ef52f700 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 27 Sep 2026 20:01:44 -0400 Subject: [PATCH 30/32] flea: put only copymanifest's O_TMPFILE test on tmpfs Moving the whole module broke the rest of it: Writer::create needs a runtime directory on a different filesystem from the copy, and with the test root on /dev/shm none qualifies. Only a_garbage_stream_falls_back_with_the_tree_untouched opens its manifest in its own test dir, which the builder's /tmp refuses (EOPNOTSUPP). --- pkgbuilds/flea/PKGBUILD | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index 77365f5..23c3674 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -115,8 +115,12 @@ check() { # back to the next create, so on the builder's /tmp the stranger is # identical and undo walks it back. tmpfs allocates inode numbers from a # counter and never reuses one, which is what the test assumes. + # copymanifest's garbage-stream test opens its O_TMPFILE manifest in its + # own test dir; the module's other tests must stay off tmpfs, because + # Writer::create wants a runtime dir on a different filesystem from the + # copy and finds none when both live on /dev/shm. local -a filesystem_tests=( - backend::copymanifest::tests:: + backend::copymanifest::tests::a_garbage_stream_falls_back_with_the_tree_untouched backend::menu_actions::tests:: backend::menudelete::tests:: backend::redo::tests:: From aa143d79b7f135c2d5ac303e2500c7846ba31c07 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 27 Sep 2026 21:36:12 -0400 Subject: [PATCH 31/32] Stop the PR overlay preview failing stale PRs with SIGPIPE 'git status --short | head' under set -o pipefail: once the PR's pkgbuilds/ differs from base in more than ten files, head exits, git takes SIGPIPE and the step fails with 141 before anything builds. Every PR branched far enough behind master hit it (omadev #423, llmman-bin #428 and others on 2026-09-28). sed -n '1,10p' prints the same preview and drains the stream. --- .github/workflows/build-pr.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 76fc6cc..42c34aa 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -202,7 +202,10 @@ jobs: git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.head.sha }}" git checkout "${{ github.event.pull_request.head.sha }}" -- pkgbuilds/ echo "tooling: $(git rev-parse --short HEAD) (base) packages: ${{ github.event.pull_request.head.sha }} (PR head)" - git status --short | head + # A preview only. `head` exits after ten lines and, under pipefail, + # git's SIGPIPE (141) failed the step for any PR far enough behind + # master to differ in more files; sed reads the whole stream. + git status --short | sed -n '1,10p' - name: Build ${{ matrix.package }} (${{ matrix.arch }}, ships to ${{ matrix.channels }}) id: build env: From 7abad3786dbc58dad3f3aae3244ac3b8cab0fca9 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 27 Sep 2026 21:45:40 -0400 Subject: [PATCH 32/32] Plan PR builds from the PR's own files, not a diff to master's tip The planner took 'git diff base.sha head.sha', a two-dot diff between the current master tip and the PR head. For a PR that branched before later merges, that counts every package master has changed since, so the PR plans those too and builds its own stale copies of them: wasted builds, and 'already up to date' Pack failures that turn the PR red for packages it never touched. #397 (lazyjournal) planned 22 packages for a one-package change. The checkout is shallow, so ask GitHub for the PR's files, which are listed against the merge base. --- .github/workflows/build-pr.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 42c34aa..8825c82 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -95,8 +95,13 @@ jobs: if [[ -n "${{ github.event.inputs.packages }}" ]]; then names="${{ github.event.inputs.packages }}" else + # The PR's own files, as GitHub lists them against the merge base. + # A two-dot diff against the current base tip also counted every + # package master changed after the PR branched, so a stale PR + # planned dozens of unrelated packages at its old versions. The + # checkout here is shallow, so there is no merge base to diff from. # A package the PR deletes has nothing to build. - names=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" -- pkgbuilds \ + names=$(gh api --paginate "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --jq '.[].filename' \ | awk -F/ '$1=="pkgbuilds" && NF>2 {print $2}' | sort -u \ | while read -r name; do if git cat-file -e "${{ github.event.pull_request.head.sha }}:pkgbuilds/$name" 2>/dev/null; then echo "$name"; fi