From aad293a22d93556c78b2ff349ba4d5226bdb629d Mon Sep 17 00:00:00 2001 From: Jeremy Dixon Date: Thu, 27 Aug 2026 22:03:34 -0400 Subject: [PATCH 01/63] Update JetBrains Mono Nerd Font to 3.5.1 --- .../.omarchy/package.json | 10 +++++++++- pkgbuilds/ttf-jetbrains-mono-nerd-basic/PKGBUILD | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkgbuilds/ttf-jetbrains-mono-nerd-basic/.omarchy/package.json b/pkgbuilds/ttf-jetbrains-mono-nerd-basic/.omarchy/package.json index 2a9719d3..eeacccb2 100644 --- a/pkgbuilds/ttf-jetbrains-mono-nerd-basic/.omarchy/package.json +++ b/pkgbuilds/ttf-jetbrains-mono-nerd-basic/.omarchy/package.json @@ -1,3 +1,11 @@ { - "source": "local" + "source": "local", + "min_release_age": "24h", + "upstream": { + "github": "ryanoasis/nerd-fonts", + "checksums": "SHA-256.txt", + "assets": { + "any": "JetBrainsMono.zip" + } + } } diff --git a/pkgbuilds/ttf-jetbrains-mono-nerd-basic/PKGBUILD b/pkgbuilds/ttf-jetbrains-mono-nerd-basic/PKGBUILD index c74acb32..1accc2ed 100644 --- a/pkgbuilds/ttf-jetbrains-mono-nerd-basic/PKGBUILD +++ b/pkgbuilds/ttf-jetbrains-mono-nerd-basic/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Omarchy pkgname=ttf-jetbrains-mono-nerd-basic -pkgver=3.5.0 +pkgver=3.5.1 pkgrel=1 pkgdesc="Basic JetBrains Mono Nerd Font family" arch=("any") @@ -11,7 +11,7 @@ groups=("nerd-fonts") provides=("ttf-font-nerd" "ttf-jetbrains-mono-nerd=$pkgver") conflicts=("ttf-jetbrains-mono-nerd" "nerd-fonts-jetbrains-mono") source=("JetBrainsMono-${pkgver}.zip::https://github.com/ryanoasis/nerd-fonts/releases/download/v${pkgver}/JetBrainsMono.zip") -sha256sums=("9577de1ae84ec523df16fc69bac5338b89497a5b4fb91489e2dcb79dc06ac2b5") +sha256sums=('fab782a66f7d3019da64f6572db9fc5d3a4bcb19f9fa13e2d8a62e3693d6396e') package() { local font From eda9236c67acdd7dba4a40b356285d7f6ec05019 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Mon, 31 Aug 2026 20:49:53 -0500 Subject: [PATCH 02/63] Add perplexity, the official Perplexity desktop app Tracks Perplexity's own Debian repository, the feed the app updates itself from, via .omarchy/upstream.sh -- same shape as openai-codex-desktop. pkgver carries the build number from the pool filename because the index's Version field drops it and upstream rebuilds under the same marketing version; the pool wants the '+' percent-encoded. The launcher replaces the postinst symlink pacman never creates and defaults Chromium to Wayland. --- pkgbuilds/perplexity/.omarchy/package.json | 4 + pkgbuilds/perplexity/.omarchy/upstream.sh | 82 ++++++++++++++ pkgbuilds/perplexity/PKGBUILD | 114 ++++++++++++++++++++ pkgbuilds/perplexity/perplexity-launcher.sh | 32 ++++++ 4 files changed, 232 insertions(+) create mode 100644 pkgbuilds/perplexity/.omarchy/package.json create mode 100755 pkgbuilds/perplexity/.omarchy/upstream.sh create mode 100644 pkgbuilds/perplexity/PKGBUILD create mode 100755 pkgbuilds/perplexity/perplexity-launcher.sh diff --git a/pkgbuilds/perplexity/.omarchy/package.json b/pkgbuilds/perplexity/.omarchy/package.json new file mode 100644 index 00000000..db153c3e --- /dev/null +++ b/pkgbuilds/perplexity/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "release_ring": "fast" +} diff --git a/pkgbuilds/perplexity/.omarchy/upstream.sh b/pkgbuilds/perplexity/.omarchy/upstream.sh new file mode 100755 index 00000000..0e3997e0 --- /dev/null +++ b/pkgbuilds/perplexity/.omarchy/upstream.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Perplexity ships its desktop app from its own Debian repository. The +# per-architecture package index carries the filename and SHA256 of every deb, +# so an update costs two small HTTP requests instead of a 175 MB download. The +# index's Version field drops the build number the pool filename carries, and +# upstream rebuilds under the same marketing version, so the version of record +# here is the full string embedded in the filename. +set -euo pipefail + +BASE_URL="https://packages.perplexity.ai/deb" +declare -A DEB_ARCHES=([x86_64]=amd64 [aarch64]=arm64) + +# Print " " for the newest stanza in a Packages index, version +# meaning the full string from the pool filename. Newest is vercmp's opinion, +# which is the one bin/sync-upstream and pacman both use; sort -V disagrees +# with it over versions like 1.0a. +newest_release() { + local index="$1" + local debarch="$2" + local filename sha256 version best_version="" best_sha256="" + local prefix="pool/main/p/perplexity/perplexity_" + local suffix="_${debarch}.deb" + + while read -r filename sha256; do + # The PKGBUILD reconstructs the pool URL from pkgver alone, so a stanza + # naming anything else -- a rename, or a new pool layout -- has to stop + # the sync rather than pin a checksum to a URL nobody will fetch. + if [[ "$filename" != "${prefix}"*"${suffix}" ]]; then + echo "Unexpected pool filename in the upstream index: $filename" >&2 + return 1 + fi + version="${filename#"$prefix"}" + version="${version%"$suffix"}" + + if [[ -z "$best_version" ]] || [[ "$(vercmp "$version" "$best_version")" -gt 0 ]]; then + best_version="$version" + best_sha256="$sha256" + fi + done < <(awk ' + { sub(/\r$/, "") } + /^Filename:/ { filename = $2 } + /^SHA256:/ { sha256 = $2 } + /^$/ { if (filename && sha256) print filename, sha256; filename = sha256 = "" } + END { if (filename && sha256) print filename, sha256 } + ' <<<"$index") + + [[ -n "$best_version" ]] || return 1 + echo "$best_version $best_sha256" +} + +versions=() +declare -A checksums=() + +for arch in "${!DEB_ARCHES[@]}"; do + index=$(curl -fsSL "$BASE_URL/dists/stable/main/binary-${DEB_ARCHES[$arch]}/Packages") + + read -r version sha256 <<<"$(newest_release "$index" "${DEB_ARCHES[$arch]}")" + if [[ -z "${version:-}" || -z "${sha256:-}" ]]; then + echo "No usable release found for $arch in the upstream package index" >&2 + exit 1 + fi + + versions+=("$version") + checksums[$arch]="$sha256" +done + +# A release lands one architecture at a time, and a single pkgver has to cover +# both -- including the build number, since that is what the download URLs are +# built from. Report no update until they agree; the next run picks it up. +for version in "${versions[@]}"; do + if [[ "$version" != "${versions[0]}" ]]; then + echo "Upstream architectures are mid-release (${versions[*]}); skipping" >&2 + echo '{}' + exit 0 + fi +done + +jq -n \ + --arg pkgver "${versions[0]}" \ + --arg x86_64 "${checksums[x86_64]}" \ + --arg aarch64 "${checksums[aarch64]}" \ + '{pkgver: $pkgver, sha256sums: {x86_64: [$x86_64], aarch64: [$aarch64]}}' diff --git a/pkgbuilds/perplexity/PKGBUILD b/pkgbuilds/perplexity/PKGBUILD new file mode 100644 index 00000000..e8911034 --- /dev/null +++ b/pkgbuilds/perplexity/PKGBUILD @@ -0,0 +1,114 @@ +# Maintainer: Spencer Bull + +# Omarchy tracks Perplexity's own Debian repository, the feed the app updates +# itself from. .omarchy/upstream.sh rewrites the version and checksums below +# from that repository's package index. + +pkgname=perplexity +pkgver=26.8.4+build50522 +pkgrel=1 +pkgdesc="Official Perplexity desktop app" +arch=('x86_64' 'aarch64') +url="https://www.perplexity.ai" +# Upstream ships no license text with the app; its terms live at +# https://www.perplexity.ai/hub/legal/terms-of-service +license=('custom') + +depends=( + 'alsa-lib' + 'at-spi2-core' + 'bash' + 'bubblewrap' + 'cairo' + 'dbus' + 'expat' + 'gcc-libs' + 'gdk-pixbuf2' + 'glib2' + 'glibc' + 'gtk3' + 'hicolor-icon-theme' + 'libcups' + 'libdrm' + 'libglvnd' + 'libnotify' + 'libsecret' + 'libx11' + 'libxcb' + 'libxcomposite' + 'libxdamage' + 'libxext' + 'libxfixes' + 'libxkbcommon' + 'libxrandr' + 'libxss' + 'libxtst' + 'mesa' + 'nspr' + 'nss' + 'pango' + 'systemd-libs' + 'util-linux-libs' + 'vulkan-icd-loader' + 'xdg-utils' +) + +optdepends=( + 'apparmor: confine the app with the bundled user-namespace profile' + 'libappindicator-gtk3: tray icon support' +) + +makedepends=('libarchive') +options=('!debug' '!strip') + +# Omarchy: the build image compresses with zstd at its default level. Maximum +# zstd buys roughly a third off this ~600 MB Electron tree for a few extra +# minutes of build time -- worth it for a package this large on the fast ring, +# same reasoning as openai-codex-desktop. +COMPRESSZST=(zstd -c -z -q --ultra -22 --threads=0 -) + +# The pool filename carries a build number the index's Version field drops, and +# upstream rebuilds under the same marketing version, so pkgver is the full +# string from the filename. The pool's storage treats a literal '+' in a URL as +# a space and answers 403, hence the %2B in the download URL only. +_deb_x86_64="perplexity_${pkgver}_amd64.deb" +_deb_aarch64="perplexity_${pkgver}_arm64.deb" +_pool="https://packages.perplexity.ai/deb/pool/main/p/perplexity" +source=('perplexity-launcher.sh') +source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64//+/%2B}") +source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64//+/%2B}") +noextract=("${_deb_x86_64}" "${_deb_aarch64}") +sha256sums=('b180ca6fbd268712a277a34d4215f176001c0031a4f6360686458623f47fad65') +sha256sums_x86_64=('47ef208aaee9395497cefe8fbc65beb2a5c37dd8bd974125e4c4a0e3be2fd103') +sha256sums_aarch64=('5ac7b5f597e03a888f92a3aaa643138f6cc78a50fd891bc7afabfe86e1e2b994') + +package() { + cd "${srcdir}" + + local deb_var="_deb_${CARCH}" + local deb="${!deb_var}" + + bsdtar -xOf "${deb}" data.tar.xz | + bsdtar --no-same-owner -xf - -C "${pkgdir}" + + # pacman never runs the deb's postinst, so the /usr/bin entry it would have + # symlinked is a launcher here instead, and the menu entry goes through it so + # the flags file applies there too. The scheme handler stays upstream's. + install -Dm755 perplexity-launcher.sh "${pkgdir}/usr/bin/perplexity" + sed -i 's|^Exec=.*|Exec=perplexity %U|' \ + "${pkgdir}/usr/share/applications/perplexity.desktop" + + # Arch ships Chromium's and Electron's own sandbox helper setuid, which is + # what keeps the sandbox up on a kernel that denies unprivileged user + # namespaces -- linux-hardened, mainly -- instead of aborting the app. The + # deb leaves this bit to its postinst. + chmod 4755 "${pkgdir}/opt/Perplexity/chrome-sandbox" + + # The profile upstream's postinst would install. It names /opt/Perplexity + # paths, which this package keeps. + install -Dm644 "${pkgdir}/opt/Perplexity/resources/apparmor-profile" \ + "${pkgdir}/etc/apparmor.d/perplexity" + + # Debian package-policy files are not used on Arch Linux. + rm -rf "${pkgdir}/usr/share/doc" +} diff --git a/pkgbuilds/perplexity/perplexity-launcher.sh b/pkgbuilds/perplexity/perplexity-launcher.sh new file mode 100755 index 00000000..2ff179fd --- /dev/null +++ b/pkgbuilds/perplexity/perplexity-launcher.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -euo pipefail + +user_flags=() +config_home="${XDG_CONFIG_HOME:-}" +[[ -n "$config_home" || -z "${HOME:-}" ]] || config_home="$HOME/.config" +flags_file="${config_home:+$config_home/perplexity-flags.conf}" + +if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then + while IFS= read -r line || [[ -n "$line" ]]; do + line="${line%%#*}" + [[ -n "${line//[[:space:]]/}" ]] || continue + read -r -a flags <<<"$line" + user_flags+=("${flags[@]}") + done <"$flags_file" +fi + +# Chromium's own Ozone detection falls back to XWayland often enough to matter, +# and the result is a blurry window on every scaled display. Ask for Wayland +# directly, unless the user has already picked a platform themselves. +platform_flags=() +if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then + platform_flags=(--ozone-platform=wayland) + + for flag in "${user_flags[@]}" "$@"; do + case "$flag" in + --ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; + esac + done +fi + +exec /opt/Perplexity/perplexity "${platform_flags[@]}" "${user_flags[@]}" "$@" From b0cba5bf8b53f0c6c3387ce6054451131d280ac9 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Tue, 1 Sep 2026 23:30:10 -0400 Subject: [PATCH 03/63] Drop obsolete asusctl 6.4.0 GitHub-source patch (#269) The AUR caught up: asusctl 6.4.0-1 (b0ec6ca) repoints source at the GitHub repo upstream, which is what our patch existed to do. The new PKGBUILD uses a release tarball instead of git+, so the patch context no longer matches and every scheduled Sync AUR Packages run failed applying it. Remove the patch and resync from the AUR. With no .omarchy/patches left, the package is no longer customized, so it tracks 6.4.0-1 without the .1 pkgrel suffix. --- pkgbuilds/asusctl/.nvchecker.toml | 3 +- pkgbuilds/asusctl/.omarchy/package.json | 2 +- .../patches/6.4.0-github-source.patch | 18 --- pkgbuilds/asusctl/PKGBUILD | 125 ++++++------------ pkgbuilds/asusctl/asusctl.install | 27 ++-- 5 files changed, 53 insertions(+), 122 deletions(-) delete mode 100644 pkgbuilds/asusctl/.omarchy/patches/6.4.0-github-source.patch diff --git a/pkgbuilds/asusctl/.nvchecker.toml b/pkgbuilds/asusctl/.nvchecker.toml index d1a0c7f1..e98ca307 100644 --- a/pkgbuilds/asusctl/.nvchecker.toml +++ b/pkgbuilds/asusctl/.nvchecker.toml @@ -1,4 +1,3 @@ [asusctl] source = "git" -git = "https://gitlab.com/asus-linux/asusctl.git" - +git = "https://github.com/OpenGamingCollective/asusctl.git" diff --git a/pkgbuilds/asusctl/.omarchy/package.json b/pkgbuilds/asusctl/.omarchy/package.json index f2bc93fa..1b2f22af 100644 --- a/pkgbuilds/asusctl/.omarchy/package.json +++ b/pkgbuilds/asusctl/.omarchy/package.json @@ -1,4 +1,4 @@ { "source": "aur", - "upstream_commit": "ed336f7dbd5e8bee333aeaa6246da89241f1c8e6" + "upstream_commit": "b0ec6ca495eb331a684db91b0fe12085a868b632" } diff --git a/pkgbuilds/asusctl/.omarchy/patches/6.4.0-github-source.patch b/pkgbuilds/asusctl/.omarchy/patches/6.4.0-github-source.patch deleted file mode 100644 index af9cfcf3..00000000 --- a/pkgbuilds/asusctl/.omarchy/patches/6.4.0-github-source.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/PKGBUILD b/PKGBUILD -index 55d0c22..8897aec 100644 ---- a/PKGBUILD -+++ b/PKGBUILD -@@ -6,7 +6,7 @@ pkgname=( - asusctl - rog-control-center - ) --pkgver=6.3.8 -+pkgver=6.4.0 - pkgrel=1 - pkgdesc="A control daemon, tools, and a collection of crates for interacting with ASUS ROG laptops" - arch=('x86_64') -@@ -28,2 +28,2 @@ --source=("git+https://gitlab.com/asus-linux/asusctl.git#tag=$pkgver") --b2sums=('01269bc9fe63c1ce37568f9085c25dbcaaaa5cd9f51339d4c6904600d179ec826dace289d6e4e3501988fce5800da98e9cfa18b21c40da152d91c4f7fe821ffe') -+source=("git+https://github.com/opengamingcollective/asusctl.git#tag=$pkgver") -+b2sums=('0cfdc7f792fc6499cde9e4c9c6d82e484605e1b2878bb9f3a0e0696f5910b9ba7898f124fecd5fb3f39a0787f81e5163721606290099d757365dc3d6715a1bcf') diff --git a/pkgbuilds/asusctl/PKGBUILD b/pkgbuilds/asusctl/PKGBUILD index 3d39c41b..470e7e60 100644 --- a/pkgbuilds/asusctl/PKGBUILD +++ b/pkgbuilds/asusctl/PKGBUILD @@ -1,108 +1,59 @@ -# Maintainer: Fabian Bornschein +# Maintainer: Mahdi Sarikhani +# Contributor: Fabian Bornschein # Contributor: Static_Rocket pkgbase=asusctl -pkgname=( - asusctl - rog-control-center -) +pkgname=(asusctl rog-control-center) pkgver=6.4.0 -pkgrel=1.1 -pkgdesc="A control daemon, tools, and a collection of crates for interacting with ASUS ROG laptops" +pkgrel=1 +pkgdesc="Daemon and tools to control your ASUS ROG laptop" arch=('x86_64') url="https://asus-linux.org" license=('MPL-2.0') -makedepends=( - clang - cmake - fontconfig - git - hicolor-icon-theme - libayatana-appindicator - libinput - libusb - rust - seatd - systemd -) -source=("git+https://github.com/opengamingcollective/asusctl.git#tag=$pkgver") -b2sums=('0cfdc7f792fc6499cde9e4c9c6d82e484605e1b2878bb9f3a0e0696f5910b9ba7898f124fecd5fb3f39a0787f81e5163721606290099d757365dc3d6715a1bcf') +makedepends=('cargo' 'fontconfig') +source=("${pkgbase}-${pkgver}.tar.gz::https://github.com/OpenGamingCollective/asusctl/archive/${pkgver}.tar.gz") +b2sums=('e90074e904f364386ad661784bd9fc2e929e83ea06ac62fd1d34eb03490cefe8aae3bed2722162f1e8ea5d69248138cb5fd9f90c3a18443d1d8c04cf732b928a') prepare() { - cd "${pkgbase}" - - # Keep rust/cargo build-dependency management inside the build directory - export CARGO_HOME="${srcdir}/cargo" - - # Follow Rust package guidelines - ## https://wiki.archlinux.org/title/Rust_package_guidelines - export RUSTUP_TOOLCHAIN=stable - cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')" + cd "${pkgbase}-${pkgver}" + export RUSTUP_TOOLCHAIN=stable + cargo fetch --locked --target host-tuple } build() { - cd "${pkgbase}" - - # Keep rust/cargo build-dependency management inside the build directory - export CARGO_HOME="${srcdir}/cargo" - - # Follow Rust package guidelines - ## https://wiki.archlinux.org/title/Rust_package_guidelines - export RUSTUP_TOOLCHAIN=stable - export CARGO_TARGET_DIR=target - - make build + cd "${pkgbase}-${pkgver}" + export RUSTUP_TOOLCHAIN=stable + export CARGO_TARGET_DIR=target + make build } package_asusctl() { - pkgdesc="${pkgdesc/tools/CLI tools}" - depends=( - glibc - libgcc - libusb - systemd - systemd-libs - ) - conflicts=(gnome-shell-extension-asusctl-gnome) - install=asusctl.install - optdepends=( - 'acpi_call: fan control' - 'asusctltray: tray profile switcher' - 'rog-control-center: app to control asusctl' - 'supergfxctl: hybrid GPU control' - ) + pkgdesc="An utility for Linux to control many aspects of various ASUS laptops" + depends=('glibc' 'libgcc' 'libusb' 'systemd-libs') + optdepends=( + 'acpi_call: fan control' + 'asusctltray: tray profile switcher' + 'rog-control-center: graphical user interface for asusctl' + 'supergfxctl: hybrid GPU control' + ) + install=asusctl.install - cd "${pkgbase}" - export CARGO_HOME="${srcdir}/cargo" - make DESTDIR="${pkgdir}" \ - install-asusctl \ - install-asusd \ - install-asusd_user \ - install-asus-shutdown \ - install-data-asusd \ - install-data-asusd_user + cd "${pkgbase}-${pkgver}" + make DESTDIR="${pkgdir}" \ + install-asusctl \ + install-asusd \ + install-asusd_user \ + install-asus-shutdown \ + install-data-asusd \ + install-data-asusd_user } package_rog-control-center() { - depends=( - asusctl - fontconfig - freetype2 - glibc - hicolor-icon-theme - libayatana-appindicator - libgcc - libinput - libxkbcommon - mesa - seatd - systemd-libs - ) - pkgdesc="App to control asusctl" + pkgdesc="Graphical user interface for asusctl" + depends=('asusctl' 'fontconfig' 'glibc' 'hicolor-icon-theme' 'libgcc' 'systemd-libs') - cd "${pkgbase}" - export CARGO_HOME="${srcdir}/cargo" - make DESTDIR="${pkgdir}" \ - install-data-rog_gui \ - install-rog_gui + cd "${pkgbase}-${pkgver}" + make DESTDIR="${pkgdir}" \ + install-rog_gui \ + install-data-rog_gui } diff --git a/pkgbuilds/asusctl/asusctl.install b/pkgbuilds/asusctl/asusctl.install index 2f5a1c36..c6ccfe76 100644 --- a/pkgbuilds/asusctl/asusctl.install +++ b/pkgbuilds/asusctl/asusctl.install @@ -1,19 +1,18 @@ post_install() { - printf ":: asusd provides a service that is activated by an udev rule on\n" - printf ":: startup. Please reboot the system or run\n" - printf ":: # systemctl start asusd.service\n" - printf ":: to make it work.\n" - printf ":: See https://gitlab.com/asus-linux/asusctl#kernel-support.\n" - printf ":: for latest required kernel patches/versions\n" + printf ":: asusd provides a service that is activated by an udev rule on\n" + printf ":: startup. Please reboot the system or run\n" + printf ":: # systemctl start asusd.service\n" + printf ":: to make it work.\n" + printf ":: See https://github.com/OpenGamingCollective/asusctl#kernel-requirements\n" + printf ":: for latest required kernel patches/versions\n" } post_upgrade() { - if systemctl is-active asusd.service --quiet - then - printf ":: asusd service will be restarted…\n" - systemctl daemon-reload - systemctl restart asusd.service - fi - printf ":: See https://gitlab.com/asus-linux/asusctl#kernel-support.\n" - printf ":: for latest required kernel patches/versions\n" + if systemctl is-active asusd.service --quiet; then + printf ":: asusd service will be restarted…\n" + systemctl daemon-reload + systemctl restart asusd.service + fi + printf ":: See https://github.com/OpenGamingCollective/asusctl#kernel-requirements\n" + printf ":: for latest required kernel patches/versions\n" } From d562ecb388551a6539820ffe9d0cd6e016a53a4c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Tue, 1 Sep 2026 23:44:16 -0400 Subject: [PATCH 04/63] t3code-bin: 0.0.38 --- pkgbuilds/t3code-bin/PKGBUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/t3code-bin/PKGBUILD b/pkgbuilds/t3code-bin/PKGBUILD index fbb9abe9..7a3206fc 100644 --- a/pkgbuilds/t3code-bin/PKGBUILD +++ b/pkgbuilds/t3code-bin/PKGBUILD @@ -7,7 +7,7 @@ # from the release feed the app updates itself from. pkgname=t3code-bin -pkgver=0.0.37 +pkgver=0.0.38 pkgrel=1 pkgdesc="Open-source control plane for coding agents" arch=('x86_64') @@ -61,7 +61,7 @@ noextract=("${_appimage}") sha256sums=('cb905ff341372ef2ef6e402cf485959f8bd1df8f0efebee4cda1afdd5e6abc0a' 'c5b3f2a9f0b14b12cfd973b79319f0f018b7ae49a1d43d8ca346100c3f7de28f' '935d8f2af0c703f9c39517ee57cc4930b19d02d533be930b63f0e82f93614b43') -sha256sums_x86_64=('ea88807ccc4b18b2c3f85726ef1235e695b656ab37c8e29c9289f7a28de336c2') +sha256sums_x86_64=('1f1ccd922b2efaffd50447b028ee0d6e2525502905087cf8ae3fe41efebe346f') prepare() { chmod +x "${srcdir}/${_appimage}" From b66905a4370e3802424c83cfb187aeafdf78f25c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Tue, 1 Sep 2026 23:44:16 -0400 Subject: [PATCH 05/63] Drop t3code-patched-bin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The environment-theme patches shipped as a forked AppImage while upstream lacked them. Keeping it current means a hand-built artifact per release, and it has fallen three behind — 0.0.35 against 0.0.38. t3code-bin tracks the upstream feed on its own, so drop the fork. Nothing else in the repo referenced the package. --- .../t3code-patched-bin/.omarchy/package.json | 4 - pkgbuilds/t3code-patched-bin/LICENSE | 21 --- pkgbuilds/t3code-patched-bin/PKGBUILD | 134 ------------------ pkgbuilds/t3code-patched-bin/t3-launcher.sh | 10 -- .../t3code-patched-bin/t3code-launcher.sh | 31 ---- 5 files changed, 200 deletions(-) delete mode 100644 pkgbuilds/t3code-patched-bin/.omarchy/package.json delete mode 100644 pkgbuilds/t3code-patched-bin/LICENSE delete mode 100644 pkgbuilds/t3code-patched-bin/PKGBUILD delete mode 100755 pkgbuilds/t3code-patched-bin/t3-launcher.sh delete mode 100644 pkgbuilds/t3code-patched-bin/t3code-launcher.sh diff --git a/pkgbuilds/t3code-patched-bin/.omarchy/package.json b/pkgbuilds/t3code-patched-bin/.omarchy/package.json deleted file mode 100644 index db153c3e..00000000 --- a/pkgbuilds/t3code-patched-bin/.omarchy/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "source": "local", - "release_ring": "fast" -} diff --git a/pkgbuilds/t3code-patched-bin/LICENSE b/pkgbuilds/t3code-patched-bin/LICENSE deleted file mode 100644 index 55ee675b..00000000 --- a/pkgbuilds/t3code-patched-bin/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 T3 Tools Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/pkgbuilds/t3code-patched-bin/PKGBUILD b/pkgbuilds/t3code-patched-bin/PKGBUILD deleted file mode 100644 index 1facc566..00000000 --- a/pkgbuilds/t3code-patched-bin/PKGBUILD +++ /dev/null @@ -1,134 +0,0 @@ -# Maintainer: Ryan Hughes - -# T3 Code built from the environment-theme branch of ryanrhughes/t3code, until -# upstream ships it: the desktop follows the machine's published themes -# (Omarchy retints it live) and `t3 theme set` works. Packaging matches -# t3code-bin exactly, so this installs in its place and the AppImage is -# unpacked the same way. -# -# Shipping a new artifact, every time: -# 1. The t3code worktree needs a repo-root .env carrying the public T3 -# Connect config (relay URL, Clerk publishable key, CLI OAuth client id) -# or Connect ships broken -- the 0.0.35-2 mistake. The values are public -# and recoverable from the published npm `t3` package if lost. -# 2. Build: `pnpm dist:desktop:linux` with --build-version . -# 3. Tag the built commit v-omarchy., create a GitHub release on -# the fork with the AppImage as its asset. -# 4. Update _patchtag and sha256sums_x86_64 here, and bump pkgrel (or, on a -# rebase to a new upstream release, bump pkgver and reset pkgrel) -- the -# version change is what makes the build server rebuild at all; a new -# release asset with an untouched PKGBUILD is invisible to it. - -pkgname=t3code-patched-bin -pkgver=0.0.35 -pkgrel=5 -pkgdesc="Open-source control plane for coding agents, with Omarchy environment-theme patches" -arch=('x86_64') -url="https://t3.codes" -license=('MIT') - -depends=( - 'alsa-lib' - 'at-spi2-core' - 'cairo' - 'dbus' - 'expat' - 'gcc-libs' - 'glib2' - 'glibc' - 'gtk3' - 'hicolor-icon-theme' - 'libcups' - 'libnotify' - 'libx11' - 'libxcb' - 'libxcomposite' - 'libxdamage' - 'libxext' - 'libxfixes' - 'libxkbcommon' - 'libxrandr' - 'mesa' - 'nspr' - 'nss' - 'pango' - 'systemd-libs' - 'xdg-utils' -) - -optdepends=( - 'claude-code: drive Claude Code from the app' - 'cursor-cli: drive Cursor from the app' - 'github-copilot-cli: drive Copilot from the app' - 'openai-codex: drive Codex from the app' -) - -provides=("t3code=${pkgver}") -conflicts=('t3code' 't3code-bin') -options=('!debug' '!strip') - -_patchtag="v${pkgver}-omarchy.5" -_appimage="T3-Code-${pkgver}-x86_64.AppImage" -source=('t3code-launcher.sh' 't3-launcher.sh' 'LICENSE') -source_x86_64=("${_appimage}::https://github.com/ryanrhughes/t3code/releases/download/${_patchtag}/${_appimage}") -noextract=("${_appimage}") -sha256sums=('cb905ff341372ef2ef6e402cf485959f8bd1df8f0efebee4cda1afdd5e6abc0a' - 'c5b3f2a9f0b14b12cfd973b79319f0f018b7ae49a1d43d8ca346100c3f7de28f' - '935d8f2af0c703f9c39517ee57cc4930b19d02d533be930b63f0e82f93614b43') -sha256sums_x86_64=('6f58306d54525ca695c7b902aac610d111f85a1f3ba8a9bf04e6e1950337b1f6') - -prepare() { - chmod +x "${srcdir}/${_appimage}" - rm -rf "${srcdir}/squashfs-root" - "${srcdir}/${_appimage}" --appimage-extract >/dev/null -} - -package() { - cd "${srcdir}/squashfs-root" - - # The AppImage's usr/ tree is compatibility libraries for distributions that - # lack them, and Arch is not one; its icons are the only part worth keeping. - # A release that starts shipping something else there stops the build rather - # than having it deleted quietly on the way past. - local unexpected - unexpected=$(find usr \( -type f -o -type l \) | grep -vE \ - '^usr/share/icons/hicolor/[0-9]+x[0-9]+/apps/t3code\.png$|^usr/lib/lib(Xss\.so\.1|Xtst\.so\.6|appindicator\.so\.1|gconf-2\.so\.4|indicator\.so\.7|notify\.so\.4)$' || true) - if [[ -n "${unexpected}" ]]; then - echo "Unexpected files in the AppImage's usr/ tree:" >&2 - echo "${unexpected}" >&2 - return 1 - fi - - local icon size - for icon in usr/share/icons/hicolor/*/apps/t3code.png; do - size="${icon#usr/share/icons/hicolor/}" - install -Dm644 "${icon}" "${pkgdir}/usr/share/icons/hicolor/${size%%/*}/apps/t3code.png" - done - - # Upstream's own entry rather than a hand-written one: it carries the - # t3code:// scheme handlers that make the app's deep links resolve. - # The name drops the "(Alpha)" upstream's electron-builder still appends; the - # app itself now calls that string legacy and keeps its data under t3code. - sed -e 's|^Exec=.*|Exec=t3code %U|' -e 's|^Name=.*|Name=T3 Code|' \ - -e '/^X-AppImage-Version=/d' t3code.desktop \ - > "${srcdir}/t3code.desktop.arch" - install -Dm644 "${srcdir}/t3code.desktop.arch" \ - "${pkgdir}/usr/share/applications/t3code.desktop" - - rm -rf AppRun .DirIcon usr t3code.desktop t3code.png - install -d "${pkgdir}/usr/lib/t3code" - cp -a . "${pkgdir}/usr/lib/t3code/" - chmod -R a+rX "${pkgdir}/usr/lib/t3code" - - # Arch ships Chromium's and Electron's own sandbox helper setuid, which is - # what keeps the sandbox up on a kernel that denies unprivileged user - # namespaces -- linux-hardened, mainly -- instead of aborting the app. - chmod 4755 "${pkgdir}/usr/lib/t3code/chrome-sandbox" - - install -Dm755 "${srcdir}/t3code-launcher.sh" "${pkgdir}/usr/bin/t3code" - # The server CLI, run through the bundled Electron as Node. Same entrypoint - # npm's `t3` package runs, so `t3 serve`, `t3 pair`, and the rest work - # without a separate install. - install -Dm755 "${srcdir}/t3-launcher.sh" "${pkgdir}/usr/bin/t3" - install -Dm644 "${srcdir}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" -} diff --git a/pkgbuilds/t3code-patched-bin/t3-launcher.sh b/pkgbuilds/t3code-patched-bin/t3-launcher.sh deleted file mode 100755 index af1988f7..00000000 --- a/pkgbuilds/t3code-patched-bin/t3-launcher.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# The T3 Code server CLI (`t3`) ships inside the app bundle; upstream only -# distributes it separately through npm. The bundled Electron doubles as the -# Node runtime for it, so the desktop package can put the CLI on PATH without -# shipping a second runtime. Electron's fs layer reads app.asar transparently -# in this mode. -set -euo pipefail - -export ELECTRON_RUN_AS_NODE=1 -exec /usr/lib/t3code/t3code /usr/lib/t3code/resources/app.asar/apps/server/dist/bin.mjs "$@" diff --git a/pkgbuilds/t3code-patched-bin/t3code-launcher.sh b/pkgbuilds/t3code-patched-bin/t3code-launcher.sh deleted file mode 100644 index 2157d1c9..00000000 --- a/pkgbuilds/t3code-patched-bin/t3code-launcher.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -set -euo pipefail - -user_flags=() -config_home="${XDG_CONFIG_HOME:-}" -[[ -n "$config_home" || -z "${HOME:-}" ]] || config_home="$HOME/.config" -flags_file="${config_home:+$config_home/t3code-flags.conf}" - -if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then - while IFS= read -r line || [[ -n "$line" ]]; do - line="${line%%#*}" - [[ -n "${line//[[:space:]]/}" ]] || continue - read -r -a flags <<<"$line" - user_flags+=("${flags[@]}") - done <"$flags_file" -fi - -# Chromium's own Ozone detection falls back to XWayland often enough to matter, -# and the result is a blurry window on every scaled display. -platform_flags=() -if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then - platform_flags=(--ozone-platform=wayland) - - for flag in "${user_flags[@]}" "$@"; do - case "$flag" in - --ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; - esac - done -fi - -exec /usr/lib/t3code/t3code "${platform_flags[@]}" "${user_flags[@]}" "$@" From 4ed5f14629843251b52a5217597b112fc485e2c0 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Wed, 2 Sep 2026 21:59:45 +1000 Subject: [PATCH 06/63] Build the omarchy package pair for aarch64 The Omarchy payload is architecture-independent, but the package is not. On x86_64 omarchy pulls the Limine + mkinitcpio hook + Snapper boot stack; on Apple Silicon the system boots through m1n1 + GRUB from the Asahi packages on Arch Linux ARM's kernel, so that stack does not apply, and Wi-Fi on the Broadcom parts needs the iwd backend. The shipped /etc tree differs too: mkinitcpio reads every file under /etc/mkinitcpio.conf.d/, so shipping omarchy_hooks.conf on aarch64 injects the Limine hooks into the Asahi kernel's initramfs, and the zram/zswap/oomd drop-ins and the zram-tuned vm.* sysctls belong to the x86_64 memory stack. makepkg only honours depends_ and optdepends_ on arch-specific packages, so arch=('any') becomes ('x86_64' 'aarch64'). backup=() has no arch-suffixed form, so the x86_64-only entries are appended under CARCH and each of those paths is removed from the aarch64 package in package(). The x86_64 package keeps exactly the contents it had; only its filename suffix changes. The install scriptlet applies the hardened cups-files.conf on every platform, then on Apple Silicon keeps the Arch Linux ARM system identity (/etc/os-release stays the distribution's symlink) instead of the etc-overrides. The -dev pair carries the same change so the pairs stay in lockstep. --- pkgbuilds/omarchy-dev/PKGBUILD | 40 +++++++---- pkgbuilds/omarchy-settings-dev/PKGBUILD | 70 ++++++++++++++++--- .../omarchy-settings-dev.install | 29 ++++++-- pkgbuilds/omarchy-settings/PKGBUILD | 70 ++++++++++++++++--- .../omarchy-settings/omarchy-settings.install | 29 ++++++-- pkgbuilds/omarchy/PKGBUILD | 40 +++++++---- 6 files changed, 222 insertions(+), 56 deletions(-) diff --git a/pkgbuilds/omarchy-dev/PKGBUILD b/pkgbuilds/omarchy-dev/PKGBUILD index fac61dba..58630edd 100644 --- a/pkgbuilds/omarchy-dev/PKGBUILD +++ b/pkgbuilds/omarchy-dev/PKGBUILD @@ -5,7 +5,10 @@ pkgrel=1 _pkgver_base=4.0.0 _pkgver_base_tag=v3.8.2 pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH (quattro branch tip)' -arch=('any') +# The payload is architecture-independent, but the dependency set is not: the +# boot stack differs per architecture (see depends_x86_64 / depends_aarch64), +# and makepkg only honours arch-suffixed arrays on arch-specific packages. +arch=('x86_64' 'aarch64') url='https://github.com/basecamp/omarchy' license=('MIT') provides=('omarchy') @@ -17,18 +20,6 @@ depends=( 'omarchy-keyring' 'omarchy-settings-dev' - # Bootloader stack. Lives here rather than on omarchy-settings because - # omarchy-settings is also installed into the live ISO env (for plymouth - # / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with - # 'Cannot detect an ESP path' inside the chroot. The omarchy-settings - # config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/, - # and /etc/snapper/config-templates/ only do anything when these are - # installed alongside. - 'limine' - 'limine-mkinitcpio-hook' - 'limine-snapper-sync' - 'snapper' - # Wayland / Hyprland core 'hyprland' 'quickshell' @@ -55,6 +46,29 @@ depends=( 'ttf-jetbrains-mono-nerd-basic' ) +# Bootloader stack. Lives here rather than on omarchy-settings because +# omarchy-settings is also installed into the live ISO env (for plymouth +# / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with +# 'Cannot detect an ESP path' inside the chroot. The omarchy-settings +# config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/, +# and /etc/snapper/config-templates/ only do anything when these are +# installed alongside. +depends_x86_64=( + 'limine' + 'limine-mkinitcpio-hook' + 'limine-snapper-sync' + 'snapper' +) + +# Apple Silicon boots through m1n1 + GRUB from the Asahi packages and keeps +# Arch Linux ARM's kernel and boot layout, so the Limine/Snapper stack does not +# apply. Wi-Fi on the Broadcom parts only scans reliably through the iwd +# backend, which install/hardware/network.sh selects on Apple Silicon. +depends_aarch64=( + 'iwd' + 'networkmanager' +) + makedepends=( 'git' ) diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 9fe2d8af..8cda17b2 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -5,7 +5,12 @@ pkgrel=1 _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=('any') +# 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. +arch=('x86_64' 'aarch64') url='https://github.com/basecamp/omarchy' license=('MIT') provides=('omarchy-settings') @@ -44,15 +49,10 @@ makedepends=( # scriptlet WILL clobber user edits on each upgrade (documented limitation). backup=( '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/docker/daemon.json' 'etc/gnupg/dirmngr.conf' 'etc/sysctl.d/99-omarchy-sysctl.conf' 'etc/sysctl.d/90-omarchy-file-watchers.conf' - 'etc/modprobe.d/omarchy-usb-autosuspend.conf' 'etc/systemd/logind.conf.d/10-ignore-power-button.conf' 'etc/systemd/logind.conf.d/20-inhibit-delay.conf' 'etc/systemd/resolved.conf.d/10-disable-multicast.conf' @@ -63,18 +63,31 @@ backup=( 'etc/systemd/system/plocate-updatedb.service.d/ac-only.conf' 'etc/systemd/system.conf.d/20-omarchy-nofile.conf' 'etc/systemd/user.conf.d/20-omarchy-nofile.conf' - 'etc/systemd/oomd.conf.d/10-omarchy.conf' - 'etc/systemd/zram-generator.conf' 'etc/sddm.conf.d/10-theme.conf' 'etc/sddm.conf.d/10-wayland.conf' 'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-wifi-powersave.rules' ) +# 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' + ) +fi optdepends=( 'omarchy-dev: Full Omarchy desktop (quattro branch tip)' 'inxi: System information for omarchy-debug' 'fastfetch: System information for omarchy-upload-log' +) +optdepends_x86_64=( 'limine: Bootloader the shipped /etc/mkinitcpio.conf.d/ and /etc/limine-entry-tool.d/ drop-ins are for' 'limine-mkinitcpio-hook: Picks up the shipped /etc/mkinitcpio.conf.d/omarchy_hooks.conf' 'limine-snapper-sync: Boot-entry sync for snapper snapshots' @@ -147,6 +160,14 @@ 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, @@ -174,9 +195,13 @@ package() { install -Dm644 default/systemd/user/omarchy-crash-watch.service "$pkgdir/usr/lib/systemd/user/omarchy-crash-watch.service" # Marks app.slice (and only app.slice) as a systemd-oomd kill candidate, so # memory pressure costs one app scope instead of the compositor session. - # Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. - install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" - install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" + # Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. Both this and + # the zram vendor drop-in belong to the x86_64 memory stack: the aarch64 + # install neither enables systemd-oomd nor configures zram swap. + if [[ $CARCH == x86_64 ]]; then + install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" + install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" + fi # Same for applications/** — used by omarchy-refresh-applications, # omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share. @@ -190,6 +215,25 @@ package() { # stage separately below). 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" + # 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. + rm -rf "$pkgdir/etc/systemd/oomd.conf.d" + rm -f "$pkgdir/etc/systemd/zram-generator.conf" "$pkgdir/etc/tmpfiles.d/omarchy-zswap.conf" + cat >"$pkgdir/etc/sysctl.d/99-omarchy-sysctl.conf" <<'EOF' +# Solve common flakiness with SSH (MTU discovery on flaky links). +net.ipv4.tcp_mtu_probing=1 +EOF + # USB autosuspend is left at the kernel default on Apple Silicon. + rm -f "$pkgdir/etc/modprobe.d/omarchy-usb-autosuspend.conf" + rmdir "$pkgdir/etc/modprobe.d" "$pkgdir/etc/tmpfiles.d" 2>/dev/null || true + fi # sudoers.d files must be mode 0440 or sudo will refuse them, and the # directory must be 0750 to match the Arch sudo package's filesystem # convention (otherwise pacman warns about dir-mode drift on every install). @@ -280,6 +324,10 @@ 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 \ diff --git a/pkgbuilds/omarchy-settings-dev/omarchy-settings-dev.install b/pkgbuilds/omarchy-settings-dev/omarchy-settings-dev.install index fd977b16..19f54e70 100644 --- a/pkgbuilds/omarchy-settings-dev/omarchy-settings-dev.install +++ b/pkgbuilds/omarchy-settings-dev/omarchy-settings-dev.install @@ -1,6 +1,31 @@ +_apple_silicon() { + [[ $(uname -m) == aarch64 ]] && grep -aq 'apple,' /proc/device-tree/compatible 2>/dev/null +} + _etc_overrides_apply() { echo "Applying Omarchy etc-overrides..." + # The hardened CUPS authorization applies on every platform. It waits for the + # file the cups package owns: an ISO bootstrap without cups installed is left + # alone and picks it up once cups lands. + if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then + systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf + install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf + fi + + # Apple Silicon installs run on Arch Linux ARM's base with the Asahi + # packages, and keep that system identity: /etc/os-release stays the + # symlink to the distribution's /usr/lib/os-release, and the PAM, NSS, + # plymouth and skel overrides below are not applied there. + if _apple_silicon; then + echo "Preserving Arch Linux ARM system configuration on Apple Silicon..." + if [[ -f /usr/lib/os-release ]]; then + rm -f /etc/os-release + ln -s ../usr/lib/os-release /etc/os-release + fi + return 0 + fi + # Ensure parent dirs exist. cups-browsed may not be installed yet during ISO # bootstrap; do not create /etc/cups/cups-browsed.conf before the package is # installed or pacman will preserve its packaged file as a noisy .pacnew. @@ -21,10 +46,6 @@ _etc_overrides_apply() { install -d /etc/cups cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf fi - if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then - systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf - install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf - fi cp -f /usr/share/omarchy/etc-overrides/plymouth-plymouthd.conf /etc/plymouth/plymouthd.conf cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc chmod 0644 /etc/os-release /etc/security/faillock.conf /etc/nsswitch.conf \ diff --git a/pkgbuilds/omarchy-settings/PKGBUILD b/pkgbuilds/omarchy-settings/PKGBUILD index e48f36bc..a457e6d8 100644 --- a/pkgbuilds/omarchy-settings/PKGBUILD +++ b/pkgbuilds/omarchy-settings/PKGBUILD @@ -15,7 +15,12 @@ _commit='346e69e1cec6c4e8924531874af6ba010a1bc99e' pkgver=4.0.2 pkgrel=1 pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers' -arch=('any') +# 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. +arch=('x86_64' 'aarch64') url='https://github.com/basecamp/omarchy' license=('MIT') conflicts=('omarchy-settings-dev') @@ -53,15 +58,10 @@ makedepends=( # scriptlet WILL clobber user edits on each upgrade (documented limitation). backup=( '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/docker/daemon.json' 'etc/gnupg/dirmngr.conf' 'etc/sysctl.d/99-omarchy-sysctl.conf' 'etc/sysctl.d/90-omarchy-file-watchers.conf' - 'etc/modprobe.d/omarchy-usb-autosuspend.conf' 'etc/systemd/logind.conf.d/10-ignore-power-button.conf' 'etc/systemd/logind.conf.d/20-inhibit-delay.conf' 'etc/systemd/resolved.conf.d/10-disable-multicast.conf' @@ -72,18 +72,31 @@ backup=( 'etc/systemd/system/plocate-updatedb.service.d/ac-only.conf' 'etc/systemd/system.conf.d/20-omarchy-nofile.conf' 'etc/systemd/user.conf.d/20-omarchy-nofile.conf' - 'etc/systemd/oomd.conf.d/10-omarchy.conf' - 'etc/systemd/zram-generator.conf' 'etc/sddm.conf.d/10-theme.conf' 'etc/sddm.conf.d/10-wayland.conf' 'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-wifi-powersave.rules' ) +# 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' + ) +fi optdepends=( 'omarchy: Full Omarchy desktop' 'inxi: System information for omarchy-debug' 'fastfetch: System information for omarchy-upload-log' +) +optdepends_x86_64=( 'limine: Bootloader the shipped /etc/mkinitcpio.conf.d/ and /etc/limine-entry-tool.d/ drop-ins are for' 'limine-mkinitcpio-hook: Picks up the shipped /etc/mkinitcpio.conf.d/omarchy_hooks.conf' 'limine-snapper-sync: Boot-entry sync for snapper snapshots' @@ -142,6 +155,14 @@ 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, @@ -169,9 +190,13 @@ package() { install -Dm644 default/systemd/user/omarchy-crash-watch.service "$pkgdir/usr/lib/systemd/user/omarchy-crash-watch.service" # Marks app.slice (and only app.slice) as a systemd-oomd kill candidate, so # memory pressure costs one app scope instead of the compositor session. - # Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. - install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" - install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" + # Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. Both this and + # the zram vendor drop-in belong to the x86_64 memory stack: the aarch64 + # install neither enables systemd-oomd nor configures zram swap. + if [[ $CARCH == x86_64 ]]; then + install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" + install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" + fi # Same for applications/** — used by omarchy-refresh-applications, # omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share. @@ -185,6 +210,25 @@ package() { # stage separately below). 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" + # 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. + rm -rf "$pkgdir/etc/systemd/oomd.conf.d" + rm -f "$pkgdir/etc/systemd/zram-generator.conf" "$pkgdir/etc/tmpfiles.d/omarchy-zswap.conf" + cat >"$pkgdir/etc/sysctl.d/99-omarchy-sysctl.conf" <<'EOF' +# Solve common flakiness with SSH (MTU discovery on flaky links). +net.ipv4.tcp_mtu_probing=1 +EOF + # USB autosuspend is left at the kernel default on Apple Silicon. + rm -f "$pkgdir/etc/modprobe.d/omarchy-usb-autosuspend.conf" + rmdir "$pkgdir/etc/modprobe.d" "$pkgdir/etc/tmpfiles.d" 2>/dev/null || true + fi # sudoers.d files must be mode 0440 or sudo will refuse them, and the # directory must be 0750 to match the Arch sudo package's filesystem # convention (otherwise pacman warns about dir-mode drift on every install). @@ -275,6 +319,10 @@ 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 \ diff --git a/pkgbuilds/omarchy-settings/omarchy-settings.install b/pkgbuilds/omarchy-settings/omarchy-settings.install index c02cc276..4ebb226d 100644 --- a/pkgbuilds/omarchy-settings/omarchy-settings.install +++ b/pkgbuilds/omarchy-settings/omarchy-settings.install @@ -1,6 +1,31 @@ +_apple_silicon() { + [[ $(uname -m) == aarch64 ]] && grep -aq 'apple,' /proc/device-tree/compatible 2>/dev/null +} + _etc_overrides_apply() { echo "Applying Omarchy etc-overrides..." + # The hardened CUPS authorization applies on every platform. It waits for the + # file the cups package owns: an ISO bootstrap without cups installed is left + # alone and picks it up once cups lands. + if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then + systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf + install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf + fi + + # Apple Silicon installs run on Arch Linux ARM's base with the Asahi + # packages, and keep that system identity: /etc/os-release stays the + # symlink to the distribution's /usr/lib/os-release, and the PAM, NSS, + # plymouth and skel overrides below are not applied there. + if _apple_silicon; then + echo "Preserving Arch Linux ARM system configuration on Apple Silicon..." + if [[ -f /usr/lib/os-release ]]; then + rm -f /etc/os-release + ln -s ../usr/lib/os-release /etc/os-release + fi + return 0 + fi + # Ensure parent dirs exist. cups-browsed may not be installed yet during ISO # bootstrap; do not create /etc/cups/cups-browsed.conf before the package is # installed or pacman will preserve its packaged file as a noisy .pacnew. @@ -21,10 +46,6 @@ _etc_overrides_apply() { install -d /etc/cups cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf fi - if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then - systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf - install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf - fi cp -f /usr/share/omarchy/etc-overrides/plymouth-plymouthd.conf /etc/plymouth/plymouthd.conf cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc chmod 0644 /etc/os-release /etc/security/faillock.conf /etc/nsswitch.conf \ diff --git a/pkgbuilds/omarchy/PKGBUILD b/pkgbuilds/omarchy/PKGBUILD index a3af2e87..2e20a38d 100644 --- a/pkgbuilds/omarchy/PKGBUILD +++ b/pkgbuilds/omarchy/PKGBUILD @@ -15,7 +15,10 @@ _commit='346e69e1cec6c4e8924531874af6ba010a1bc99e' pkgver=4.0.2 pkgrel=1 pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH' -arch=('any') +# The payload is architecture-independent, but the dependency set is not: the +# boot stack differs per architecture (see depends_x86_64 / depends_aarch64), +# and makepkg only honours arch-suffixed arrays on arch-specific packages. +arch=('x86_64' 'aarch64') url='https://github.com/basecamp/omarchy' license=('MIT') conflicts=('omarchy-dev') @@ -31,18 +34,6 @@ depends=( # from the same _commit, and forces the pair to upgrade together. "omarchy-settings=${pkgver}" - # Bootloader stack. Lives here rather than on omarchy-settings because - # omarchy-settings is also installed into the live ISO env (for plymouth - # / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with - # 'Cannot detect an ESP path' inside the chroot. The omarchy-settings - # config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/, - # and /etc/snapper/config-templates/ only do anything when these are - # installed alongside. - 'limine' - 'limine-mkinitcpio-hook' - 'limine-snapper-sync' - 'snapper' - # Wayland / Hyprland core 'hyprland' 'quickshell' @@ -69,6 +60,29 @@ depends=( 'ttf-jetbrains-mono-nerd-basic' ) +# Bootloader stack. Lives here rather than on omarchy-settings because +# omarchy-settings is also installed into the live ISO env (for plymouth +# / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with +# 'Cannot detect an ESP path' inside the chroot. The omarchy-settings +# config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/, +# and /etc/snapper/config-templates/ only do anything when these are +# installed alongside. +depends_x86_64=( + 'limine' + 'limine-mkinitcpio-hook' + 'limine-snapper-sync' + 'snapper' +) + +# Apple Silicon boots through m1n1 + GRUB from the Asahi packages and keeps +# Arch Linux ARM's kernel and boot layout, so the Limine/Snapper stack does not +# apply. Wi-Fi on the Broadcom parts only scans reliably through the iwd +# backend, which install/hardware/network.sh selects on Apple Silicon. +depends_aarch64=( + 'iwd' + 'networkmanager' +) + makedepends=( 'git' ) From 191e1e7db5e81fff30e7768489236ce8ece3b5ae Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Wed, 2 Sep 2026 23:15:30 +1000 Subject: [PATCH 07/63] Add builder flags for CI and resumed builds Three opt-in knobs for bin/build, each defaulting to today's behaviour: OMARCHY_KEEP_BUILD_WORKSPACE=1 keeps build-output/$MIRROR/$ARCH instead of wiping it, and build/build.sh now folds any packages already there into omarchy-build.db even when no database exists yet, so packages built by an earlier job (or a previous, interrupted run) resolve as dependencies of what builds next. OMARCHY_SKIP_BUILDER_IMAGE=1 uses the omarchy-pkg-builder image already present instead of building it, so a workflow can build the image once with an external BuildKit cache and fan out over package jobs that all run the same bytes. A missing image is an error, not a silent rebuild. OMARCHY_DEFER_RUNTIME_DEPS=true builds the omarchy/omarchy-settings pair with --nodeps, installing only their makedepends and checkdepends explicitly. The pair depends on each other and on packages a sharded pipeline builds in other jobs, so they cannot resolve in isolation; the assembled set is installed in one verified transaction downstream. The request is refused for anything but exactly that pair, on the host before Docker starts and again inside the container. Also fix make_dir_writable: chown -R can succeed on part of the tree and fail on files a previous container left behind as another uid, and the old `|| chmod` fallback only ran when chown failed outright. Always follow with chmod. --- bin/build | 90 ++++++++++++++++++++++++++++++++++++--- build/build.sh | 85 ++++++++++++++++++++++++++++++++---- helpers/docker-helpers.sh | 9 +++- 3 files changed, 167 insertions(+), 17 deletions(-) diff --git a/bin/build b/bin/build index 90808cb2..7051f8e6 100755 --- a/bin/build +++ b/bin/build @@ -13,6 +13,27 @@ print_header "Omarchy Package Builder" DRY_RUN=false +# Knobs for builds driven from CI or resumed by hand. Each defaults to the +# historical behaviour, so an unadorned `bin/build` is unchanged. +# +# OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output/$MIRROR/$ARCH instead of +# wiping it, so packages built by an earlier +# job (or a previous, interrupted run) seed +# the build database and resolve as +# dependencies of what builds now. +# OMARCHY_SKIP_BUILDER_IMAGE=1 use the omarchy-pkg-builder image already +# present instead of building it; a workflow +# that builds the image once with an external +# BuildKit cache can then fan out over many +# package jobs without each one rebuilding it. +# OMARCHY_DEFER_RUNTIME_DEPS=true build the omarchy/omarchy-settings pair +# with --nodeps (see build/build.sh); only +# for a pipeline that verifies the install +# transaction afterwards. +KEEP_BUILD_WORKSPACE=${OMARCHY_KEEP_BUILD_WORKSPACE:-0} +SKIP_BUILDER_IMAGE=${OMARCHY_SKIP_BUILDER_IMAGE:-0} +DEFER_RUNTIME_DEPS=${OMARCHY_DEFER_RUNTIME_DEPS:-false} + # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in @@ -67,6 +88,11 @@ while [[ $# -gt 0 ]]; do echo " $0 --package yay" echo " $0 --package yay elephant cursor-bin" echo "" + echo "Environment (for CI and resumed builds; defaults keep today's behaviour):" + echo " OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output and reuse packages already there" + echo " OMARCHY_SKIP_BUILDER_IMAGE=1 use the existing builder image instead of building it" + echo " OMARCHY_DEFER_RUNTIME_DEPS=true build the omarchy pair with --nodeps (transaction verified later)" + echo "" exit 0 ;; *) @@ -76,11 +102,45 @@ while [[ $# -gt 0 ]]; do esac done +if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then + print_error "OMARCHY_DEFER_RUNTIME_DEPS must be true or false" + exit 1 +fi + +# Deferring runtime dependencies is only sound for the omarchy pair, and only +# when both halves are built together: the pair depends on each other and on +# packages that a sharded pipeline builds in other jobs, and the consumer of +# this mode installs the assembled set in one verified transaction. Check the +# request here so a misuse fails before Docker starts. +if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then + deferred_runtime=0 + deferred_settings=0 + deferred_count=0 + for package in $PACKAGES; do + ((deferred_count += 1)) + case $package in + omarchy|omarchy-dev) deferred_runtime=1 ;; + omarchy-settings|omarchy-settings-dev) deferred_settings=1 ;; + *) + print_error "OMARCHY_DEFER_RUNTIME_DEPS only applies to the omarchy/omarchy-settings pair, not $package" + exit 1 + ;; + esac + done + if (( deferred_runtime != 1 || deferred_settings != 1 || deferred_count != 2 )); then + print_error "OMARCHY_DEFER_RUNTIME_DEPS requires --package with exactly the omarchy pair" + exit 1 + fi +fi + # Show target architecture and mirror after parsing args print_info "Target architecture: $ARCH" print_info "Mirror: $MIRROR" print_info "Build workspace: $BUILD_OUTPUT_DIR" print_info "Final output: $REPO_DIR" +if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then + print_info "Runtime dependency checks: deferred to the install transaction" +fi if [[ "$DRY_RUN" == true ]]; then print_warning "DRY RUN MODE - build plan only; no Docker or makepkg will run" @@ -88,6 +148,7 @@ if [[ "$DRY_RUN" == true ]]; then MIRROR="$MIRROR" \ PACKAGES="$PACKAGES" \ DRY_RUN=true \ + DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS" \ PKGBUILDS_DIR="$PKGBUILDS_DIR" \ BUILD_OUTPUT_DIR="$BUILD_OUTPUT_DIR" \ FINAL_OUTPUT_DIR="$REPO_DIR" \ @@ -112,9 +173,14 @@ if [[ "$(uname -m)" == "x86_64" && "$ARCH" == "aarch64" ]]; then fi fi -# Clean build-output directory to start fresh -print_info "Cleaning build workspace..." -rm -rf "$BUILD_OUTPUT_DIR"/* +# Clean build-output directory to start fresh, unless the caller seeded it +# with packages from an earlier job or is resuming an interrupted run. +if [[ $KEEP_BUILD_WORKSPACE == "1" ]]; then + print_info "Keeping existing build workspace..." +else + print_info "Cleaning build workspace..." + rm -rf "$BUILD_OUTPUT_DIR"/* +fi mkdir -p "$BUILD_OUTPUT_DIR" # Show package info @@ -124,8 +190,20 @@ else print_info "Building unscoped packages for $MIRROR mirror" fi -# Build/update the Docker image -build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR" +# Build/update the Docker image, unless the caller prepared the exact image +# already (a workflow building it once with an external BuildKit cache). A +# missing image is an error rather than a silent rebuild: the point of the +# flag is that every job runs the same bytes. +IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR" +if [[ $SKIP_BUILDER_IMAGE == "1" ]]; then + if ! docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then + print_error "Prepared builder image is unavailable: $IMAGE_TAG" + exit 1 + fi + print_info "Using prepared builder image: $IMAGE_TAG" +else + build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR" +fi print_info "Running package build..." @@ -144,6 +222,7 @@ DOCKER_ARGS=( -e MIRROR="$MIRROR" -e PACKAGES="$PACKAGES" -e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}" + -e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS" -v "$BUILD_ROOT/build-output:/build-output" -v "$REPO_ROOT:/pkgs.omarchy.org" -v "$BUILD_DIR:/build:ro" @@ -152,7 +231,6 @@ DOCKER_ARGS=( ) # Run the builder with assembled args -IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR" PLATFORM_ARG=$(get_platform_arg "$ARCH") docker run $PLATFORM_ARG "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh diff --git a/build/build.sh b/build/build.sh index 42f9f2ea..e0f69acb 100755 --- a/build/build.sh +++ b/build/build.sh @@ -12,9 +12,37 @@ BUILD_OUTPUT_DIR=${BUILD_OUTPUT_DIR:-/build-output/$MIRROR/$ARCH} FINAL_OUTPUT_DIR=${FINAL_OUTPUT_DIR:-/pkgs.omarchy.org/$MIRROR/$ARCH} HELPERS_DIR=${HELPERS_DIR:-/helpers} SRC_DIR=${SRC_DIR:-/src} +# Set by bin/build from OMARCHY_DEFER_RUNTIME_DEPS after it has checked the +# request; re-checked here so the container never trusts a stray value. +DEFER_RUNTIME_DEPS=${DEFER_RUNTIME_DEPS:-false} source "$HELPERS_DIR/package-metadata.sh" +if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then + echo "DEFER_RUNTIME_DEPS must be true or false" >&2 + exit 1 +fi +if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then + deferred_runtime=0 + deferred_settings=0 + deferred_count=0 + for package in $PACKAGES; do + ((deferred_count += 1)) + case $package in + omarchy|omarchy-dev) deferred_runtime=1 ;; + omarchy-settings|omarchy-settings-dev) deferred_settings=1 ;; + *) + echo "Runtime dependency deferral only applies to the omarchy pair, not $package" >&2 + exit 1 + ;; + esac + done + if (( deferred_runtime != 1 || deferred_settings != 1 || deferred_count != 2 )); then + echo "Runtime dependency deferral requires exactly the omarchy pair" >&2 + exit 1 + fi +fi + if [[ "$DRY_RUN" != true ]]; then # Import GPG keys /build/import-gpg-keys.sh || exit 1 @@ -48,13 +76,16 @@ EOF # Create an empty database repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 ln -sf omarchy-build.db.tar.zst omarchy-build.db - else - # Database exists, check if we need to rebuild it from packages - if ls *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | grep -q .; then - echo "==> Rebuilding build database from existing packages..." - ls *.pkg.tar.* | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | xargs -r repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 - ln -sf omarchy-build.db.tar.zst omarchy-build.db - fi + fi + # Fold any packages already in the workspace into the database, whether + # they came with an existing database or were dropped in by an earlier + # workflow job (OMARCHY_KEEP_BUILD_WORKSPACE). Without this a seeded + # workspace with no database would leave those packages invisible to + # dependency resolution. + if ls *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | grep -q .; then + echo "==> Rebuilding build database from existing packages..." + ls *.pkg.tar.* | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | xargs -r repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 + ln -sf omarchy-build.db.tar.zst omarchy-build.db fi # Add omarchy repo if it has a database (stable packages) @@ -224,6 +255,31 @@ refresh_vcs_pkgver_preserving_local_pkgrel() { fi } +# With runtime dependency checks deferred, makepkg runs --nodeps, so the +# build-time dependencies it would normally install with -s have to be +# installed explicitly: makedepends and checkdepends, including the +# architecture-suffixed variants for the current CARCH. +install_deferred_build_dependencies() { + local pkg="$1" + local -a build_deps=() + + mapfile -t build_deps < <( + CARCH="$ARCH" bash -c ' + source PKGBUILD + arch_makedepends="makedepends_${CARCH}[@]" + arch_checkdepends="checkdepends_${CARCH}[@]" + printf "%s\n" \ + "${makedepends[@]}" "${!arch_makedepends}" \ + "${checkdepends[@]}" "${!arch_checkdepends}" + ' | awk 'NF && !seen[$0]++' + ) + + if (( ${#build_deps[@]} )); then + echo " Installing build-only dependencies for $pkg..." + sudo pacman -S --needed --noconfirm -- "${build_deps[@]}" + fi +} + # Build a package build_package() { local pkg="$1" @@ -280,9 +336,20 @@ build_package() { # Build package without signing (signing is done separately) # PACMAN override uses a wrapper that adds --ask 4 to auto-resolve conflicts # (e.g. rustup replacing rust) since --noconfirm defaults to 'N' on those prompts - MAKEPKG_FLAGS="-scf --noconfirm" + local -a makepkg_flags=(-scf --noconfirm) + if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then + # The pair's runtime dependencies (each other, and packages other jobs + # of the same pipeline build) are not resolvable here; the assembled set + # is installed in one verified transaction downstream. Only the + # build-time dependencies are installed, then makepkg skips the check. + install_deferred_build_dependencies "$pkg" || { + FAILED_PACKAGES="$FAILED_PACKAGES $pkg" + return 1 + } + makepkg_flags=(-cf --noconfirm --nodeps) + fi - if PACMAN=/usr/local/bin/pacman-for-makepkg makepkg $MAKEPKG_FLAGS; then + if PACMAN=/usr/local/bin/pacman-for-makepkg makepkg "${makepkg_flags[@]}"; then # Ensure output directory exists mkdir -p "$BUILD_OUTPUT_DIR" diff --git a/helpers/docker-helpers.sh b/helpers/docker-helpers.sh index 53f63065..4a269041 100644 --- a/helpers/docker-helpers.sh +++ b/helpers/docker-helpers.sh @@ -61,9 +61,14 @@ get_platform_arg() { make_dir_writable() { local dir="$1" - if [ "$(id -u)" -eq 0 ]; then + if (( EUID == 0 )); then chmod -R 777 "$dir" else - sudo chown -R $(id -u):$(id -g) "$dir" 2>/dev/null || chmod -R 777 "$dir" + # chown can succeed on part of the tree and fail on the rest (files a + # previous container left behind as another uid); the old `|| chmod` + # fallback only ran when chown failed outright, leaving those files + # unwritable. Always follow with chmod so the whole tree is usable. + sudo chown -R "$(id -u):$(id -g)" "$dir" 2>/dev/null || true + chmod -R 777 "$dir" fi } From 59d557ad5efa6d7fa3307c6811c1141480cc2724 Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Wed, 2 Sep 2026 21:59:45 +1000 Subject: [PATCH 08/63] Pin retroarch-joypad-autoconfig-git to a commit The recipe fetched the branch tip, so a rebuild on another architecture could package a different tree than the one already published. Pin the source to a commit and set pkgver to what that commit describes (1.22.0.r96.g0331510, as an aarch64 build of it reports); bump both together from now on. --- pkgbuilds/retroarch-joypad-autoconfig-git/PKGBUILD | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/retroarch-joypad-autoconfig-git/PKGBUILD b/pkgbuilds/retroarch-joypad-autoconfig-git/PKGBUILD index 83cdd485..1b859182 100644 --- a/pkgbuilds/retroarch-joypad-autoconfig-git/PKGBUILD +++ b/pkgbuilds/retroarch-joypad-autoconfig-git/PKGBUILD @@ -1,7 +1,10 @@ # Maintainer: Alexandre Bouvier _pkgname=retroarch-joypad-autoconfig +# Pinned so every architecture packages the same tree and a rebuild of the +# same pkgver reproduces the same contents; bump both together. +_commit=033151045d378b64e712a92592467800d7924227 pkgname=$_pkgname-git -pkgver=1.22.0.r24.g739cee1 +pkgver=1.22.0.r96.g0331510 pkgrel=1 pkgdesc="RetroArch joypad autoconfig files" arch=('any') @@ -12,7 +15,7 @@ makedepends=('git') optdepends=('retroarch') provides=("$_pkgname") conflicts=("$_pkgname") -source=("$_pkgname::git+$url.git") +source=("$_pkgname::git+$url.git#commit=$_commit") b2sums=('SKIP') pkgver() { From 1e6cde39169f7dd14a94ae5550029895f7311de8 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 00:43:30 -0400 Subject: [PATCH 09/63] Add Strata file manager --- pkgbuilds/strata/.omarchy/package.json | 4 ++ pkgbuilds/strata/PKGBUILD | 82 ++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 pkgbuilds/strata/.omarchy/package.json create mode 100644 pkgbuilds/strata/PKGBUILD diff --git a/pkgbuilds/strata/.omarchy/package.json b/pkgbuilds/strata/.omarchy/package.json new file mode 100644 index 00000000..db153c3e --- /dev/null +++ b/pkgbuilds/strata/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "release_ring": "fast" +} diff --git a/pkgbuilds/strata/PKGBUILD b/pkgbuilds/strata/PKGBUILD new file mode 100644 index 00000000..ee5087fd --- /dev/null +++ b/pkgbuilds/strata/PKGBUILD @@ -0,0 +1,82 @@ +pkgname=strata +pkgver=0.8.0 +pkgrel=1 +pkgdesc='Fast, keyboard-first file manager for modern Linux desktops' +arch=('x86_64' 'aarch64') +url='https://github.com/lgse/strata' +license=('GPL-3.0-or-later') + +depends=( + 'bash' + 'bubblewrap' + 'cairo' + 'ffmpeg' + 'ffmpegthumbnailer' + 'fontconfig' + 'gcc-libs' + 'gdk-pixbuf2' + 'glib2' + 'glibc' + 'graphene' + 'gst-libav' + 'gst-plugins-good' + 'gtk4' + 'gtksourceview5' + 'hicolor-icon-theme' + 'pango' + 'poppler-glib' + 'util-linux' + 'xdg-terminal-exec' +) +makedepends=('cargo' 'glib2-devel' 'pkgconf') +optdepends=( + 'gvfs-smb: browse SMB network shares' + 'imagemagick: additional camera RAW preview support' + 'libraw: additional camera RAW preview support' +) +conflicts=('strata-git') +options=('!debug' '!lto') + +_commit='61b4b0bc74a5b8ca262c7c080e9fea57bba65ff5' +source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('8a6179ad5673afd8036ff6640c1740f6fa676516a05346c180d67ef98c4a56f7') + +prepare() { + cd "$pkgname-$pkgver" + + cargo fetch --locked --target "$CARCH-unknown-linux-gnu" +} + +build() { + cd "$pkgname-$pkgver" + + export CARGO_TARGET_DIR=target + export STRATA_BUILD_COMMIT="$_commit" + export STRATA_RELEASE_TAG="v$pkgver" + export STRATA_BUILD_KIND=stable + cargo build --frozen --release +} + +check() { + cd "$pkgname-$pkgver" + + export CARGO_TARGET_DIR=target + export STRATA_BUILD_COMMIT="$_commit" + export STRATA_RELEASE_TAG="v$pkgver" + export STRATA_BUILD_KIND=stable + cargo test --frozen --release --all-targets --all-features +} + +package() { + cd "$pkgname-$pkgver" + + install -Dm755 target/release/strata "$pkgdir/usr/bin/strata" + install -Dm644 data/io.github.lgse.Strata.desktop \ + "$pkgdir/usr/share/applications/io.github.lgse.Strata.desktop" + install -Dm644 data/icons/scalable/apps/io.github.lgse.Strata.svg \ + "$pkgdir/usr/share/icons/hicolor/scalable/apps/io.github.lgse.Strata.svg" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 THIRD_PARTY_LICENSES.md \ + "$pkgdir/usr/share/licenses/$pkgname/THIRD_PARTY_LICENSES.md" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" +} From 66f86213fbf25927356a34959382e7489fd018ff Mon Sep 17 00:00:00 2001 From: ryanrhughes <1630358+ryanrhughes@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:01:03 +0000 Subject: [PATCH 10/63] chore: sync AUR packages --- pkgbuilds/claude-code/.omarchy/package.json | 2 +- pkgbuilds/claude-code/PKGBUILD | 6 +-- pkgbuilds/cliamp/.omarchy/package.json | 2 +- pkgbuilds/cliamp/PKGBUILD | 6 +-- pkgbuilds/cursor-bin/.omarchy/package.json | 2 +- pkgbuilds/cursor-bin/PKGBUILD | 8 ++-- .../openai-codex-bin/.omarchy/package.json | 2 +- pkgbuilds/openai-codex-bin/PKGBUILD | 10 ++-- .../.omarchy/package.json | 2 +- pkgbuilds/visual-studio-code-bin/PKGBUILD | 8 ++-- pkgbuilds/voxtype-bin/.omarchy/package.json | 2 +- pkgbuilds/voxtype-bin/PKGBUILD | 46 +++++++++++-------- pkgbuilds/voxtype-bin/voxtype-bin.install | 16 +++++++ 13 files changed, 68 insertions(+), 44 deletions(-) diff --git a/pkgbuilds/claude-code/.omarchy/package.json b/pkgbuilds/claude-code/.omarchy/package.json index 47a3b893..78236624 100644 --- a/pkgbuilds/claude-code/.omarchy/package.json +++ b/pkgbuilds/claude-code/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "29ca1941149bbf080d7eb6d2bb53cddc76478a1a" + "upstream_commit": "6505bc21a7c41ec546cf2fd1a7ffbee9a58b8327" } diff --git a/pkgbuilds/claude-code/PKGBUILD b/pkgbuilds/claude-code/PKGBUILD index 3f92f5b3..0baa2494 100644 --- a/pkgbuilds/claude-code/PKGBUILD +++ b/pkgbuilds/claude-code/PKGBUILD @@ -4,7 +4,7 @@ # Automation repository: https://github.com/fabifont/claude-code-aur pkgname=claude-code -pkgver=2.1.252 +pkgver=2.1.259 pkgrel=1 pkgdesc="An agentic coding tool that lives in your terminal" arch=('x86_64' 'aarch64') @@ -29,8 +29,8 @@ source_x86_64=("claude-${pkgver}-x86_64::https://downloads.claude.ai/claude-code source_aarch64=("claude-${pkgver}-aarch64::https://downloads.claude.ai/claude-code-releases/${pkgver}/linux-arm64/claude") sha256sums=('SKIP') -sha256sums_x86_64=('a715a45105e593fc9808d035d77781f88480b9897975a9df41837f0c591bd4b3') -sha256sums_aarch64=('6c0b32eaa936954a0f4d4ad2595f8416af288d9b3cbaf19e3cdd9a95d7c8853e') +sha256sums_x86_64=('f7dd62ae415378018cd21dd950eb3bac174ab085830304d3b8b098146bfd47b6') +sha256sums_aarch64=('c6ff03c389ccdeae0f19e9dc32488eeba61fbef4796f531dbfba6c00f45040d0') package() { install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude" diff --git a/pkgbuilds/cliamp/.omarchy/package.json b/pkgbuilds/cliamp/.omarchy/package.json index d9084bbe..78c29884 100644 --- a/pkgbuilds/cliamp/.omarchy/package.json +++ b/pkgbuilds/cliamp/.omarchy/package.json @@ -1,4 +1,4 @@ { "source": "aur", - "upstream_commit": "76b9ce02837a49de0fa50cfa3da5d1c0c692ece5" + "upstream_commit": "0e012cbfe1ea6cbbf15e2ada9cf93cca7aaa9ca0" } diff --git a/pkgbuilds/cliamp/PKGBUILD b/pkgbuilds/cliamp/PKGBUILD index 34e938f7..bc972e01 100644 --- a/pkgbuilds/cliamp/PKGBUILD +++ b/pkgbuilds/cliamp/PKGBUILD @@ -1,17 +1,17 @@ # Maintainer: bjarneo pkgname=cliamp -pkgver=1.63.2 +pkgver=2.0.1 pkgrel=1 pkgdesc='A retro terminal music player inspired by Winamp 2.x' arch=('x86_64' 'aarch64') url='https://github.com/bjarneo/cliamp' license=('MIT') -depends=('alsa-lib' 'flac' 'libvorbis' 'libogg' 'ffmpeg' 'yt-dlp') +depends=('alsa-lib' 'flac' 'libvorbis' 'libogg' 'mpg123' 'ffmpeg' 'yt-dlp') optdepends=('pipewire-alsa: audio output on PipeWire systems' 'pulseaudio-alsa: audio output on PulseAudio systems') makedepends=('go') source=("${pkgname}-${pkgver}.tar.gz::https://github.com/bjarneo/cliamp/archive/refs/tags/v${pkgver}.tar.gz") -sha256sums=('968ff98c1e49bae8a0ce63acf5c77a9621ef70756a048e3df5e79454b82a9eef') +sha256sums=('2c5885665dba5ed2e8dc156bce64751199a92efed7f63959c65e985759b73732') build() { cd "${pkgname}-${pkgver}" diff --git a/pkgbuilds/cursor-bin/.omarchy/package.json b/pkgbuilds/cursor-bin/.omarchy/package.json index 77bb17de..3cdbb867 100644 --- a/pkgbuilds/cursor-bin/.omarchy/package.json +++ b/pkgbuilds/cursor-bin/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "00756d82c31b0056800bb98220c83800ebb85e72" + "upstream_commit": "e796ff912a08aa8fa30b8140ffcacb9d43386fa5" } diff --git a/pkgbuilds/cursor-bin/PKGBUILD b/pkgbuilds/cursor-bin/PKGBUILD index e4088b65..9d1c5c25 100644 --- a/pkgbuilds/cursor-bin/PKGBUILD +++ b/pkgbuilds/cursor-bin/PKGBUILD @@ -1,18 +1,18 @@ # Maintainer: Gunther Schulz pkgname=cursor-bin -pkgver=3.18.25 +pkgver=3.19.7 pkgrel=1 pkgdesc='AI-first coding environment' arch=('x86_64') url="https://www.cursor.com" license=('LicenseRef-Cursor_EULA') # upstream uses Electron newer than internal VSCode -_electron=electron40 +_electron=electron42 depends=(xdg-utils ripgrep $_electron nodejs 'gcc-libs' 'hicolor-icon-theme' 'libxkbfile') options=(!strip !debug) # Don't break ext of VSCode -_commit=280eca2911f1774689696e5f1efa5a4f97a87af3 +_commit=90de2327392570a5f5f625c656c6749d228e6437 source=("https://downloads.cursor.com/production/${_commit}/linux/x64/deb/amd64/deb/cursor_${pkgver}_amd64.deb" "https://gitlab.archlinux.org/archlinux/packaging/packages/code/-/raw/main/code."{sh,mjs} rg.sh) @@ -20,7 +20,7 @@ sha512sums=('SKIP' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') -sha512sums[0]=5f25c9dccf75c764b6266e74136ad5cf6e99330804c7ae3ed22d4210304791b1ac1233efb07b291da490fbfe3d4801cb46680a171ddf6b2815e0267741ec339a +sha512sums[0]=f1749bb4824dd5bff10853ba341320ca79b4d606151bc1ad54ccd9fea3c44aa4b838f3d767ba9396a63c15c461c7d1a389f5a1ecbaf26ed64000cae5e981c342 noextract=(cursor_${pkgver}_amd64.deb) # avoid double tarball _app=usr/share/cursor/resources/app package() { diff --git a/pkgbuilds/openai-codex-bin/.omarchy/package.json b/pkgbuilds/openai-codex-bin/.omarchy/package.json index cbdb2338..627b4bdc 100644 --- a/pkgbuilds/openai-codex-bin/.omarchy/package.json +++ b/pkgbuilds/openai-codex-bin/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "7298bbbde771f2f33d0942fc429bd27643a9986a" + "upstream_commit": "4ec833f5a082fc52c771a92c60746ce130234d1f" } diff --git a/pkgbuilds/openai-codex-bin/PKGBUILD b/pkgbuilds/openai-codex-bin/PKGBUILD index a08a92f9..42f5997f 100644 --- a/pkgbuilds/openai-codex-bin/PKGBUILD +++ b/pkgbuilds/openai-codex-bin/PKGBUILD @@ -2,7 +2,7 @@ # shellcheck disable=SC2034 # Maintainer: Chmouel Boudjnah pkgname=openai-codex-bin -pkgver=0.152.0 +pkgver=0.153.0 pkgrel=1 pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated" arch=('x86_64' 'aarch64') @@ -21,10 +21,10 @@ source_x86_64=( "codex-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-x86_64-unknown-linux-musl.tar.gz" "codex-code-mode-host-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz" ) -sha256sums_x86_64=('05f942d3d3c5b5acd9edad56ce2797b6fe72dbb1462b24e5c9bf7dcec9a28a11' - '449cefe35b9f347e3fdbf121e816339b37825eb0bfee7de8298a0a61b6687cba') -sha256sums_aarch64=('37da6b486503c8a42cc4604d2a3d80d388df896dd251e9225f4f3d49b08c2e8c' - '134183c7f5bb2245ed4bb15cfcf00d1a64950adbdd089d33c003c8bcf13267e9') +sha256sums_x86_64=('35a82c153d83959de09c2cb84ac70ba69d05788aeeb08d4a95ca68e39f86680e' + '2b817a495e35a53333e94b35af9ed879e180f9b28fd57ef15ace9a857ba86f2c') +sha256sums_aarch64=('cc2c0c365d4d51c18163bba93cce5b922836900ca867f5bcf89b0e714a952a53' + '269d6006fb55c95f876454fc03ed3755c367c1c376b77a985d0029395a5a7736') source_aarch64=( "codex-${pkgver}-aarch64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-aarch64-unknown-linux-musl.tar.gz" diff --git a/pkgbuilds/visual-studio-code-bin/.omarchy/package.json b/pkgbuilds/visual-studio-code-bin/.omarchy/package.json index e70b1af8..4d00cbf9 100644 --- a/pkgbuilds/visual-studio-code-bin/.omarchy/package.json +++ b/pkgbuilds/visual-studio-code-bin/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "69b361eca02a84aa97c43c885e23d463eec96770" + "upstream_commit": "70a29667af6ac26dc2631a1910ac356924066b48" } diff --git a/pkgbuilds/visual-studio-code-bin/PKGBUILD b/pkgbuilds/visual-studio-code-bin/PKGBUILD index 210e18e3..1124e28e 100644 --- a/pkgbuilds/visual-studio-code-bin/PKGBUILD +++ b/pkgbuilds/visual-studio-code-bin/PKGBUILD @@ -2,7 +2,7 @@ pkgname=visual-studio-code-bin _pkgname=visual-studio-code -pkgver=1.135.0 +pkgver=1.136.1 pkgrel=1 pkgdesc="Visual Studio Code (vscode): Editor for building and debugging modern web and cloud applications (official binary version)" arch=('x86_64' 'aarch64' 'armv7h') @@ -27,9 +27,9 @@ source_x86_64=(code_${pkgver}_amd64.deb::https://update.code.visualstudio.com/${ source_aarch64=(code_${pkgver}_arm64.deb::https://update.code.visualstudio.com/${pkgver}/linux-deb-arm64/stable) source_armv7h=(code_${pkgver}_armhf.deb::https://update.code.visualstudio.com/${pkgver}/linux-deb-armhf/stable) sha256sums=('bd0d9edf69283ebdf4e73e0a7b168d2fcf50acbd01f63674cad93ed4fe42fdad') -sha256sums_x86_64=('e8101720f0a48f8e6ae5a4112d10ffea5f13d298e7d0a7012b4e36eebc53022c') -sha256sums_aarch64=('20f1717805ed723c8846e14df3280c6132a663ff71e32068aaa70f3e4c8b1ca4') -sha256sums_armv7h=('78e64befb925f85a34239db6e9c9c7c28d368df2ad42e7ff6b2f2876c394a22a') +sha256sums_x86_64=('4463c4e249a19d7746603f2ec3745c345d3cd55b009d36e824158d15c35509ef') +sha256sums_aarch64=('baa72f92d3feaa76d015202c57271475ea15809e635587279171a972cdd612a4') +sha256sums_armv7h=('02ddb5c5cbc2d5f4db25a3d7bb365c60eef0a017fa64b1994b131544677319e3') package() { bsdtar -xf data.tar.xz -C "${pkgdir}/" diff --git a/pkgbuilds/voxtype-bin/.omarchy/package.json b/pkgbuilds/voxtype-bin/.omarchy/package.json index f9b00a03..0e2fc7fa 100644 --- a/pkgbuilds/voxtype-bin/.omarchy/package.json +++ b/pkgbuilds/voxtype-bin/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "436615cc12ff968819fec3b2bb5ea87b3b185108" + "upstream_commit": "6dbf4bedbba0a2f90d8e813becfa5ba289653562" } diff --git a/pkgbuilds/voxtype-bin/PKGBUILD b/pkgbuilds/voxtype-bin/PKGBUILD index 42e415a0..62ad9b9f 100644 --- a/pkgbuilds/voxtype-bin/PKGBUILD +++ b/pkgbuilds/voxtype-bin/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Peter Jackson pkgname=voxtype-bin -pkgver=1.0.0 -pkgrel=2 +pkgver=1.0.1 +pkgrel=1 pkgdesc="Push-to-talk voice-to-text for Linux (pre-built binaries)" arch=('x86_64') url="https://voxtype.io" @@ -121,66 +121,70 @@ source=( "quickshell-voxtype-shared-Theme-$pkgver.qml::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/Theme.qml" "quickshell-voxtype-shared-StateReader-$pkgver.qml::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/StateReader.qml" "quickshell-voxtype-shared-AudioBridge-$pkgver.qml::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/AudioBridge.qml" + "quickshell-voxtype-shared-StyleLoader-$pkgver.qml::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/StyleLoader.qml" + "quickshell-voxtype-shared-RecipeRenderer-$pkgver.qml::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/RecipeRenderer.qml" "quickshell-voxtype-shared-qmldir-$pkgver::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/qmldir" ) sha256sums=( # Whisper binaries - '7323514a48aee6a73c0124463bd0276aad2d77f61c537ae753ca527700860572' # voxtype-avx2 + 'cb3843a894ef47aca230b30bb1c45c2ef8e0d015adf2fa754d60e55123165fd0' # voxtype-avx2 'SKIP' # voxtype-avx2.asc - 'bf7d6c0cb9573ed49a23b17b3846e6adcc36f59bca56ba1ebd71ca55f3ffe6ef' # voxtype-avx512 + '77d49275ae4c3a6d93671014278b0dabd69ba52e65d2ee6ab3ec1bff0af34d55' # voxtype-avx512 'SKIP' # voxtype-avx512.asc - 'b3d5b64b9e5ef07278966e429dfc49f52df990c637a9425110b9ed6a3b498a62' # voxtype-vulkan + 'c569d038057464aa60290296794bcbd79b928ee0efd038e33062a4c015558ed8' # voxtype-vulkan 'SKIP' # voxtype-vulkan.asc # ONNX CPU binaries - '99757afc3e1507ecf54ef4fc1c2fe5d940d671a0931e0ec88cd28025e110034b' # voxtype-onnx-avx2 + '19b8716ff7bd388ffb18843fc0ee5e614faa84544d22918ecf72a7c3d6c02929' # voxtype-onnx-avx2 'SKIP' # voxtype-onnx-avx2.asc - 'ba225151b93e6f4e5d741347e3fb592eede3b726f91d692c812abf78330d2e88' # voxtype-onnx-avx512 + '69a6e6fc1c04a55eeb62cf1eaa5fe81873c70eabffff120ce1a4ff7ef62fd559' # voxtype-onnx-avx512 'SKIP' # voxtype-onnx-avx512.asc # ONNX CUDA 12 binary + companion .so - 'b92790a76dd989444d5f1f5e796895f158fe603005a0b2c7fa5171403dfa4153' # voxtype-onnx-cuda-12 + 'e7180f17add10bb441368d8495fc99bde0ba1a1978ea5f7a52d7e626779eca16' # voxtype-onnx-cuda-12 'SKIP' # voxtype-onnx-cuda-12.asc 'a8584727d51ba646ac63fc991c2f36ad6cd5b8cc8b1141896e46938700b888d0' # cuda-12.libonnxruntime_providers_cuda.so '1b028afc079628d76a28d7eb09700a4baead4a27f9634ba82c35398486134114' # cuda-12.libonnxruntime_providers_shared.so # ONNX CUDA 13 binary + companion .so + dlopen-target libonnxruntime - '3fe24444779c15b0755bb66841e225aa69594614c95b8f2bddf3c2300736e254' # voxtype-onnx-cuda-13 + '4de2aac42b67a05c34ac0b23f771fc83968670fd77d14781cbf4483524304cd1' # voxtype-onnx-cuda-13 'SKIP' # voxtype-onnx-cuda-13.asc 'b6cb7744d0efd2faced5c83ead374c13e7f2630b5a249ffaf393cdb1e092c92b' # cuda-13.libonnxruntime_providers_cuda.so 'c6a12593396095f5670160e284c35d1700b7708cf3037b7042e2a5200ccae772' # cuda-13.libonnxruntime_providers_shared.so '1aacefdf0b4afa145d410b2381bbc3db3d978c485fb182c42a2b0b09f91f5310' # cuda-13.libonnxruntime.so.1.24.4 # ONNX MIGraphX binary + companion .so - '954ca9a9fe98cfe3e6da93568e41a63472051800616a267340d9e8b8cffa9a96' # voxtype-onnx-migraphx + 'f7a4cc0deaf35110b5106f1fc1f83dded41c0b1780f9fb5aa9cccc3c62e5eb6a' # voxtype-onnx-migraphx 'SKIP' # voxtype-onnx-migraphx.asc 'ddd67e6193ade819ee21f1706d1b03b9151f1d2d2843701e2d19d8b183631707' # migraphx.libonnxruntime_providers_migraphx.so '17f7cf47ad0d7b5ac895ae588fd62c7f85a13842588161b6a24c7d480f062be4' # migraphx.libonnxruntime_providers_shared.so # OSD launcher + GTK4 frontend - 'ec78f39c7381ea8bbd974e0d49c35618152557c2b3c2b9e2a9f2e34a8ab68598' # voxtype-osd + '7250027b1672507a6d584f795731c87e1d3b5c1de891438bd55e34b136a2d5cc' # voxtype-osd 'SKIP' # voxtype-osd.asc - '6c51add26a3329b66ff98ddeeeb9ccc6a6a2bff428027aa2a6627e526d743720' # voxtype-osd-gtk4 + '74fb0f6ad87feb0c1c9e06a8b28a7f7ecee101caef4248f77dcf613b5271238e' # voxtype-osd-gtk4 'SKIP' # voxtype-osd-gtk4.asc # Quickshell OSD launcher + audio-bridge sidecar (new in v0.7.5) - '0944cd78972bdabec0fff30aeef0470643ef0a661c174c1074452c26cee449c6' # voxtype-osd-quickshell + 'b809c5140e844a6add801d7e592775cd89af8cce73fa399b6a3aec15dfd09533' # voxtype-osd-quickshell 'SKIP' # voxtype-osd-quickshell.asc - '4e822d393a6a005296ae1fb31f1276fc242bb8454c80df3d443fa56c50e02a38' # voxtype-audio-bridge + '45776290e364194d83a8b89445166406c278e890507bf07ec52a5f0e8fa57720' # voxtype-audio-bridge 'SKIP' # voxtype-audio-bridge.asc # Config and support files - '0b91367d2088e8850a37b47a0cba0bd9107695fc81a00ab1933465ada2a16a2f' # config/default.toml + 'f4b2bccd56b31a6a50e1c0a8b6b72383dc1e636f9ccb8cb070442d58b7314579' # config/default.toml '531c3658e229619e56bb01659fb81f401767b85e1d6e2acd1ac67ee3414a168c' # voxtype.service '65c95805d9b03ccc2fadb9d63a03ab79974b00091df8457ee8ef290ec6bd5b12' # voxtype.bash 'e5e63b3c7f48238cf719e4f2ef90c1f9c5c7e8cd25eaebc9f78bdd34b24b6605' # voxtype.zsh 'f720ddd24ee97c105b448323899c36bca7c63d00c2d42c4a3da70c3d157dccbb' # voxtype.fish '31123c45b4ff9cb5fd9e01083350fea6ccaf14969013fd48e4c95fdf89e6eb4b' # LICENSE - '11bf83d8cb2bfab0e978707fa80475d97be79723a6dc71adc3c357829ba9f347' # README.md + 'e5b2ec5da5eafe2ce8f7b84b81a889f2ff496ad6488d56b6b9b32b73d025ed53' # README.md # Desktop entry + launcher '32144a4a5210092b0aa909f6de7a43ebe8bbf82fa3dfb1f3519787512fdf8e4b' # voxtype-configure.desktop '044b1f7b52cc610ce57ba624111d882029b0ce4bc3e2c2c360f96d07f69e0e85' # voxtype-configure-launcher # Quickshell QML tree (new in v0.7.5) - '3f711c7cfca80773960e98a47361611497e584e56657052d675241daae84947e' # quickshell/shell.qml - '6e8b8aae6b64809ed5ca504a3171a4ddeadc91b1ed96eacbf67bbf056b2ffbdd' # quickshell/OsdSurface.qml + 'd3d0b0b24a3fc3e252623a6fad898c7d1d147fb8c2e49de90fbf452e95de63ab' # quickshell/shell.qml + '70a611fd4eabde43189aee0619899827de21e36695191648dc36470d4c333de6' # quickshell/OsdSurface.qml '4d6eef505ec161080ca92ae6a355ca00dcc7bb05d5b190534a1851fcbb726e55' # quickshell/EnginePicker.qml '54271a8f0e4b52f40505f32801f4c78399ac356ed1e80906ca8a068ecb7ee734' # quickshell/MeetingControls.qml 'aaa011682b92d8e25863a9ea34a469b897bac15ad939292d8144b012fa05b209' # quickshell/voxtype-shared/Theme.qml - 'd7d2fb11df25c3f30127a0d7c72693cdfb649b357888f4abd5b3fbbb2e7ce655' # quickshell/voxtype-shared/StateReader.qml + '74345f9d8b77de3f1d6d08a759e52506c606e8af3d04a97a5bfe7954d0f64604' # quickshell/voxtype-shared/StateReader.qml '2ad530f92f13fc7f1100e6f6c9910878c35d911e91dc60feb902c2542b619230' # quickshell/voxtype-shared/AudioBridge.qml + '45180488129f16a0568c217c743ce7e45831acb0cb287346d6da5b2fc097717b' # quickshell/voxtype-shared/StyleLoader.qml + 'd5e9a86946dc8dc2afabdcd00d79d973181e51b2e5e8aea2772960aaf1ff2283' # quickshell/voxtype-shared/RecipeRenderer.qml '41b894baa1487e47db5f9ec4baeea9c02b6a76d3c8198b4700a88e2cb7ec0b62' # quickshell/voxtype-shared/qmldir ) @@ -284,6 +288,10 @@ package() { "$pkgdir/usr/share/voxtype/quickshell/voxtype-shared/StateReader.qml" install -Dm644 "$srcdir/quickshell-voxtype-shared-AudioBridge-$pkgver.qml" \ "$pkgdir/usr/share/voxtype/quickshell/voxtype-shared/AudioBridge.qml" + install -Dm644 "$srcdir/quickshell-voxtype-shared-StyleLoader-$pkgver.qml" \ + "$pkgdir/usr/share/voxtype/quickshell/voxtype-shared/StyleLoader.qml" + install -Dm644 "$srcdir/quickshell-voxtype-shared-RecipeRenderer-$pkgver.qml" \ + "$pkgdir/usr/share/voxtype/quickshell/voxtype-shared/RecipeRenderer.qml" install -Dm644 "$srcdir/quickshell-voxtype-shared-qmldir-$pkgver" \ "$pkgdir/usr/share/voxtype/quickshell/voxtype-shared/qmldir" diff --git a/pkgbuilds/voxtype-bin/voxtype-bin.install b/pkgbuilds/voxtype-bin/voxtype-bin.install index 7b424464..a790fa3d 100644 --- a/pkgbuilds/voxtype-bin/voxtype-bin.install +++ b/pkgbuilds/voxtype-bin/voxtype-bin.install @@ -53,6 +53,14 @@ _install_active_binary() { } _set_default_backend() { + # aarch64 ships one generic CPU binary — no AVX2/AVX-512 split exists on + # that architecture, so there's nothing to detect. + if [ "$(uname -m)" = "aarch64" ]; then + _install_active_binary /usr/lib/voxtype/voxtype-cpu + echo "cpu" + return + fi + # Set the default CPU backend based on CPU capabilities if grep -q avx512f /proc/cpuinfo 2>/dev/null; then _install_active_binary /usr/lib/voxtype/voxtype-avx512 @@ -161,6 +169,14 @@ _set_onnx_cuda_symlink() { } _detect_gpu() { + # No GPU-accelerated binary variant ships for aarch64 yet — no mainstream + # ARM hardware has a supported Vulkan/CUDA/MIGraphX path (see + # voxtype#579) — so recommending `voxtype setup gpu --enable` here would + # send aarch64 users after a switch that doesn't exist. + if [ "$(uname -m)" = "aarch64" ]; then + return + fi + # Detect GPU for acceleration recommendation GPU_DETECTED="" GPU_VENDOR="" From 45b077387ef6e00edef162cacf52deb316dce7ca Mon Sep 17 00:00:00 2001 From: dhh <2741+dhh@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:33:12 +0000 Subject: [PATCH 11/63] chore: sync upstream releases --- pkgbuilds/link-studio/PKGBUILD | 4 ++-- pkgbuilds/mise-bin/PKGBUILD | 6 +++--- pkgbuilds/omakade/PKGBUILD | 4 ++-- pkgbuilds/openai-codex-desktop/PKGBUILD | 6 +++--- pkgbuilds/perplexity/PKGBUILD | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pkgbuilds/link-studio/PKGBUILD b/pkgbuilds/link-studio/PKGBUILD index 034b49cc..361f4403 100644 --- a/pkgbuilds/link-studio/PKGBUILD +++ b/pkgbuilds/link-studio/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Jake Weaver pkgname=link-studio -pkgver=1.0.2 +pkgver=1.0.4 pkgrel=1 pkgdesc="Native Linux controller for Insta360 Link webcams" arch=('x86_64') @@ -28,7 +28,7 @@ optdepends=( 'whisper-cpp: offline meeting transcription' ) source=("${pkgname}-${pkgver}.tar.gz::https://github.com/jweaver60/link-studio/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz") -sha256sums=('b0fc912d8b7924661b57e427280c9766cbb0f01b7a2b3f3cdb1885969f8cfdc6') +sha256sums=('f04156d34ecf5e858bd259dca824f970af53ff177185516fafff15766a0880e9') build() { cd "link_studio-${pkgver}" diff --git a/pkgbuilds/mise-bin/PKGBUILD b/pkgbuilds/mise-bin/PKGBUILD index dc92f591..9d59d455 100644 --- a/pkgbuilds/mise-bin/PKGBUILD +++ b/pkgbuilds/mise-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Jeff Dickey pkgname=mise-bin -pkgver=2026.8.15 +pkgver=2026.9.1 pkgrel=1 pkgdesc="dev tools, env vars, task runner" arch=('x86_64' 'aarch64') @@ -14,8 +14,8 @@ provides=('mise') conflicts=('mise') source_x86_64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-x64.tar.xz") source_aarch64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-arm64.tar.xz") -sha256sums_x86_64=('0ed4ee8bcf229718bead0d0aa83de865803f3fe25cc6c71a41e3a8bd57c40a33') -sha256sums_aarch64=('d5f9b7e6b36b5cf5aaab0dc4d7e29ee74224281b1a81a228bdc1cd1f3edf9d49') +sha256sums_x86_64=('bbc21f8fe9b2aa09f74250be15f9bf32e87562e1039d858ad91d7944f057c8e2') +sha256sums_aarch64=('7d0c28d44bd7e1c5444a7d867146afabe43592c728329bc0ed5ec59ca7106c48') package() { install -Dm755 "${srcdir}/mise/bin/mise" "${pkgdir}/usr/bin/mise" diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 0ebaf89f..946ed43c 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,5 +1,5 @@ pkgname=omakade -pkgver=1.2.3 +pkgver=1.4.0 pkgrel=1 pkgdesc='A beautiful, local-first game library for Omarchy' arch=('x86_64') @@ -9,7 +9,7 @@ depends=('glib2' 'libsecret' 'qt6-base' 'qt6-declarative' 'qt6-wayland' 'sdl3') makedepends=('cmake' 'ninja' 'pkgconf') options=('!debug') source=("$pkgname-$pkgver.tar.gz::https://github.com/tsouth89/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") -sha256sums=('dcbb57caf982317ae0fd75a1a97c48b80d2b02f92d65b38030876a172eb25606') +sha256sums=('a45021f2f894a6f6b7bf68c06942b2979046c6c398f7ce836c4436d57848abb9') build() { cmake -S "$pkgname-$pkgver" -B build -G Ninja \ diff --git a/pkgbuilds/openai-codex-desktop/PKGBUILD b/pkgbuilds/openai-codex-desktop/PKGBUILD index a2ca833f..bfd6ebf9 100644 --- a/pkgbuilds/openai-codex-desktop/PKGBUILD +++ b/pkgbuilds/openai-codex-desktop/PKGBUILD @@ -5,7 +5,7 @@ # the version and checksums below from that repository's package index. pkgname=openai-codex-desktop -pkgver=26.825.51511 +pkgver=26.901.20858 pkgrel=1 pkgdesc="Official ChatGPT desktop app with Codex" arch=('x86_64' 'aarch64') @@ -71,8 +71,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}") noextract=("${_deb_x86_64}" "${_deb_aarch64}") sha256sums=('b3a4503b5931f102444bc7015c3cf4e40266cf034e0d682bd2a407dc5b3ee58c') -sha256sums_x86_64=('3554b0022c6cfb513326f43fd11f718835a77086ac4d7ca2ff3ebbba2d4c7f45') -sha256sums_aarch64=('125e36522d43c75bd7958477846ba6b1cddf2eb19cefcb57dda80be1742f917f') +sha256sums_x86_64=('42a6477f22f4136d62321eda7b4697a79da1eb66d61dcb85ab0420860a1a5223') +sha256sums_aarch64=('a9b6cabc7a4f38e62e78b37b5950050d3a189d4502dc7ca6e1285a7899d87469') package() { cd "${srcdir}" diff --git a/pkgbuilds/perplexity/PKGBUILD b/pkgbuilds/perplexity/PKGBUILD index e8911034..e7fe1d19 100644 --- a/pkgbuilds/perplexity/PKGBUILD +++ b/pkgbuilds/perplexity/PKGBUILD @@ -5,7 +5,7 @@ # from that repository's package index. pkgname=perplexity -pkgver=26.8.4+build50522 +pkgver=26.9.1+build61614 pkgrel=1 pkgdesc="Official Perplexity desktop app" arch=('x86_64' 'aarch64') @@ -79,8 +79,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64//+/%2B}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64//+/%2B}") noextract=("${_deb_x86_64}" "${_deb_aarch64}") sha256sums=('b180ca6fbd268712a277a34d4215f176001c0031a4f6360686458623f47fad65') -sha256sums_x86_64=('47ef208aaee9395497cefe8fbc65beb2a5c37dd8bd974125e4c4a0e3be2fd103') -sha256sums_aarch64=('5ac7b5f597e03a888f92a3aaa643138f6cc78a50fd891bc7afabfe86e1e2b994') +sha256sums_x86_64=('2fc95f5500335e661af6901bbad5c95e8805cc9f43d9f0b979787c7249c15c33') +sha256sums_aarch64=('917562bad5ab319612c22fca49773cdee163b2e7c9f0c3dd5569fc4878f10237') package() { cd "${srcdir}" From 09e41d7d88557be30043068117e52c49f91082d7 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 13:41:54 -0400 Subject: [PATCH 12/63] Add hardened Flea package --- pkgbuilds/flea/.omarchy/package.json | 5 + pkgbuilds/flea/.omarchy/upstream.sh | 100 ++++++++++++++++++++ pkgbuilds/flea/PKGBUILD | 132 +++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 pkgbuilds/flea/.omarchy/package.json create mode 100755 pkgbuilds/flea/.omarchy/upstream.sh create mode 100644 pkgbuilds/flea/PKGBUILD diff --git a/pkgbuilds/flea/.omarchy/package.json b/pkgbuilds/flea/.omarchy/package.json new file mode 100644 index 00000000..bbe9ae19 --- /dev/null +++ b/pkgbuilds/flea/.omarchy/package.json @@ -0,0 +1,5 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h" +} diff --git a/pkgbuilds/flea/.omarchy/upstream.sh b/pkgbuilds/flea/.omarchy/upstream.sh new file mode 100755 index 00000000..5f512b26 --- /dev/null +++ b/pkgbuilds/flea/.omarchy/upstream.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# Flea publishes signed source tags but no release artifacts or checksum manifest. +# Check the small GitHub source tarball only when a newer stable release exists, +# validate its expected root, then report its hash alongside the fixed hashes of +# the four upstream security patches carried until a release includes them. +set -euo pipefail + +REPO='thisisgm/flea' +RELEASES_URL="https://api.github.com/repos/$REPO/releases?per_page=100" +PATCH_2BD7CC2='c610a9f44294b67341940203943c9c567003b65cc436d6013cd36754379183d8' +PATCH_27D19CA='e457ef17e26f70057b1184eeb938fccc5906f48a7c77f1df573d55d13a845425' +PATCH_23290CE='f8381456a3b5f39d341cc6610bf27beb8354892b17a1c8c6d7882db4e40824c3' +PATCH_B4B7EE4='46bdbe43f135a052c893b1987f8b0928736445616a84b0c65d2181ee74b21b25' + +current=$(awk -F= '/^pkgver=/ { print $2; exit }' PKGBUILD) +releases=$(curl -fsSL "$RELEASES_URL") +now=$(date +%s) +min_age=${MIN_RELEASE_AGE_SECONDS:-0} + +candidates=0 +best_version='' +best_tag='' +best_published_at='' + +while IFS=$'\t' read -r tag published_at; do + if [[ ! $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + printf '%s has an unusable stable release tag: %s\n' "$REPO" "${tag:-}" >&2 + exit 1 + fi + version=${BASH_REMATCH[1]} + + if [[ -z $published_at ]] || ! published_epoch=$(date --date="$published_at" +%s 2>/dev/null); then + printf '%s release %s has an invalid publication time\n' "$REPO" "$tag" >&2 + exit 1 + fi + candidates=$((candidates + 1)) + + if (( now - published_epoch < min_age )) && [[ ${BYPASS_MIN_RELEASE_AGE:-} != 1 ]]; then + continue + fi + + if [[ -z $best_version ]] || (( $(vercmp "$version" "$best_version") > 0 )); then + best_version=$version + best_tag=$tag + best_published_at=$published_at + fi +done < <(jq -r '.[] | select((.draft or .prerelease) | not) | [.tag_name // "", .published_at // ""] | @tsv' <<<"$releases") + +if (( candidates == 0 )); then + printf 'No stable releases found for %s\n' "$REPO" >&2 + exit 1 +fi + +if [[ -z $best_version ]] || (( $(vercmp "$best_version" "$current") <= 0 )); then + echo '{}' + exit 0 +fi + +tarball=$(mktemp) +trap 'rm -f "$tarball"' EXIT +curl -fsSL -o "$tarball" "https://github.com/$REPO/archive/refs/tags/$best_tag.tar.gz" + +expected_root="flea-$best_version" +served_roots=$(tar -tzf "$tarball" | cut -d/ -f1 | sort -u) +if [[ $served_roots != "$expected_root" ]]; then + printf 'Release %s contains root %s, expected %s\n' "$best_tag" "$served_roots" "$expected_root" >&2 + 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") + +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 -Fq '.custom_flags(O_NOFOLLOW)' <<<"$copyfile_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" \ + --arg source "$(sha256sum "$tarball" | cut -d' ' -f1)" \ + --arg patch_2bd7cc2 "$PATCH_2BD7CC2" \ + --arg patch_27d19ca "$PATCH_27D19CA" \ + --arg patch_23290ce "$PATCH_23290CE" \ + --arg patch_b4b7ee4 "$PATCH_B4B7EE4" \ + '{pkgver: $pkgver, published_at: $published_at, sha256sums: {any: [$source, $patch_2bd7cc2, $patch_27d19ca, $patch_23290ce, $patch_b4b7ee4]}}' diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD new file mode 100644 index 00000000..63c900fc --- /dev/null +++ b/pkgbuilds/flea/PKGBUILD @@ -0,0 +1,132 @@ +# Maintainer: GM + +pkgname=flea +pkgver=0.1.2 +pkgrel=1 +pkgdesc='Fast, keyboard-first file manager for Omarchy' +arch=('x86_64') +url='https://github.com/thisisgm/flea' +license=('MIT') + +depends=( + 'bubblewrap' + 'gcc-libs' + 'glib2' + 'glibc' + 'hicolor-icon-theme' + 'omarchy' + 'quickshell' + 'qt6-multimedia' + 'qt6-webengine' + 'shared-mime-info' + 'util-linux' + 'xdg-utils' +) +makedepends=('cargo') +optdepends=( + '7zip: 7z archive support' + 'dropbox-cli: Dropbox browsing and share links' + 'ffmpeg: media metadata previews' + 'imagemagick: image conversion' + 'libarchive: archive listing and extraction' + 'tailscale: Taildrop sharing' + 'wl-clipboard: copy Dropbox share links to the clipboard' +) +conflicts=('flea-git') +options=('!debug') + +_security_patches=( + '2bd7cc207c110ae3b3ea61a4d4e42336f1815111' + '27d19ca4a38ef4a0a920fb78441ce662a0101327' + '23290ce1917b0d7f0392dd09d04bf2a7504ec93a' + 'b4b7ee47244b52457eec2874e21a8656d8ace647' +) +source=( + "$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz" + "security-2bd7cc2.patch::$url/commit/${_security_patches[0]}.patch" + "security-27d19ca.patch::$url/commit/${_security_patches[1]}.patch" + "security-23290ce.patch::$url/commit/${_security_patches[2]}.patch" + "security-b4b7ee4.patch::$url/commit/${_security_patches[3]}.patch" +) +sha256sums=( + '3788a735be2cb6eef0c1770833c82bc6591e20b7681fe02172aaea21c2a2d8fb' + 'c610a9f44294b67341940203943c9c567003b65cc436d6013cd36754379183d8' + 'e457ef17e26f70057b1184eeb938fccc5906f48a7c77f1df573d55d13a845425' + 'f8381456a3b5f39d341cc6610bf27beb8354892b17a1c8c6d7882db4e40824c3' + '46bdbe43f135a052c893b1987f8b0928736445616a84b0c65d2181ee74b21b25' +) + +prepare() { + cd "$pkgname-$pkgver" + + security_patch_present() { + case "$1" in + 2bd7cc207c110ae3b3ea61a4d4e42336f1815111) + grep -Fq 'a.push("--".to_string());' src/backend/archive.rs && + grep -Fq 'let input = std::fs::canonicalize(input)' src/backend/archiveops.rs && + grep -Fq 'if op != "compress" && op != "extract"' \ + src/backend/run.rs src/backend/archivereq.rs + ;; + 27d19ca4a38ef4a0a920fb78441ce662a0101327) + grep -Fq 'the sandbox is unavailable: bwrap or prlimit is not on PATH' src/backend/archivework.rs && + grep -Fq 'if !sandbox::available()' src/backend/mediaprobe.rs && + grep -Fq 'if !sandbox::available()' src/backend/metareq.rs + ;; + 23290ce1917b0d7f0392dd09d04bf2a7504ec93a) + grep -Fq 'copyToClipboard.command = ["wl-copy", url]' ui/ShareLink.qml + ;; + b4b7ee47244b52457eec2874e21a8656d8ace647) + grep -Fq '.custom_flags(O_NOFOLLOW)' src/backend/copyfile.rs + ;; + *) + return 1 + ;; + esac + } + + local commit patch_file + for commit in "${_security_patches[@]}"; do + patch_file="$srcdir/security-${commit:0:7}.patch" + + if patch --batch --forward --dry-run -Np1 -i "$patch_file" >/dev/null 2>&1; then + patch --batch --forward -Np1 -i "$patch_file" + elif security_patch_present "$commit"; then + printf 'Security patch %s is already present upstream\n' "$commit" + else + printf 'Security patch %s no longer applies and is not present upstream\n' "$commit" >&2 + return 1 + fi + done +} + +build() { + cd "$pkgname-$pkgver" + + export CARGO_TARGET_DIR=target + cargo build --frozen --release +} + +check() { + cd "$pkgname-$pkgver" + + export CARGO_TARGET_DIR=target + cargo test --frozen --release + ./tests/js.sh + ./tests/keymap-gen.sh +} + +package() { + cd "$pkgname-$pkgver" + + install -Dm755 target/release/flea "$pkgdir/usr/bin/flea" + install -Dm644 packaging/com.thisisgm.flea.desktop \ + "$pkgdir/usr/share/applications/com.thisisgm.flea.desktop" + install -Dm644 packaging/com.thisisgm.flea.svg \ + "$pkgdir/usr/share/icons/hicolor/scalable/apps/com.thisisgm.flea.svg" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + + install -Dm644 ui/qmldir ui/*.qml -t "$pkgdir/usr/share/flea/ui" + install -Dm644 ui/js/*.js -t "$pkgdir/usr/share/flea/ui/js" + ln -s /usr/share/omarchy/shell/Commons "$pkgdir/usr/share/flea/ui/Commons" + ln -s /usr/share/omarchy/shell/Ui "$pkgdir/usr/share/flea/ui/Ui" +} From fc27de8a2eb780cceaf848bd361f6bc4cfe6a205 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 14:03:25 -0400 Subject: [PATCH 13/63] Add safe upstream sync for Strata --- pkgbuilds/strata/.omarchy/package.json | 3 +- pkgbuilds/strata/.omarchy/upstream.sh | 106 +++++++++++++++++++++++++ pkgbuilds/strata/PKGBUILD | 19 +++-- 3 files changed, 122 insertions(+), 6 deletions(-) create mode 100755 pkgbuilds/strata/.omarchy/upstream.sh diff --git a/pkgbuilds/strata/.omarchy/package.json b/pkgbuilds/strata/.omarchy/package.json index db153c3e..bbe9ae19 100644 --- a/pkgbuilds/strata/.omarchy/package.json +++ b/pkgbuilds/strata/.omarchy/package.json @@ -1,4 +1,5 @@ { "source": "local", - "release_ring": "fast" + "release_ring": "fast", + "min_release_age": "24h" } diff --git a/pkgbuilds/strata/.omarchy/upstream.sh b/pkgbuilds/strata/.omarchy/upstream.sh new file mode 100755 index 00000000..6c638d9b --- /dev/null +++ b/pkgbuilds/strata/.omarchy/upstream.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# Strata publishes annotated tags and GitHub source archives, but no checksum +# manifest. Download the small archive only when a newer stable release exists, +# then verify that its embedded commit matches the release tag before hashing it. +set -euo pipefail + +REPO='lgse/strata' +API_URL="https://api.github.com/repos/$REPO" + +current=$(awk -F= '/^pkgver=/ { print $2; exit }' PKGBUILD) +releases=$(curl -fsSL "$API_URL/releases?per_page=100") +now=$(date +%s) +min_age=${MIN_RELEASE_AGE_SECONDS:-0} + +candidates=0 +best_version='' +best_tag='' +best_published_at='' + +while IFS=$'\t' read -r tag published_at; do + if [[ ! $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + printf '%s has an unusable stable release tag: %s\n' "$REPO" "${tag:-}" >&2 + exit 1 + fi + version=${BASH_REMATCH[1]} + + if [[ -z $published_at ]] || ! published_epoch=$(date --date="$published_at" +%s 2>/dev/null); then + printf '%s release %s has an invalid publication time\n' "$REPO" "$tag" >&2 + exit 1 + fi + candidates=$((candidates + 1)) + + if ((now - published_epoch < min_age)) && [[ ${BYPASS_MIN_RELEASE_AGE:-} != 1 ]]; then + continue + fi + + if [[ -z $best_version ]] || (( $(vercmp "$version" "$best_version") > 0 )); then + best_version=$version + best_tag=$tag + best_published_at=$published_at + fi +done < <(jq -r '.[] | select((.draft or .prerelease) | not) | [.tag_name // "", .published_at // ""] | @tsv' <<<"$releases") + +if ((candidates == 0)); then + printf 'No stable releases found for %s\n' "$REPO" >&2 + exit 1 +fi + +if [[ -z $best_version ]] || (( $(vercmp "$best_version" "$current") <= 0 )); then + echo '{}' + exit 0 +fi + +tag_ref=$(curl -fsSL "$API_URL/git/ref/tags/$best_tag") +tag_type=$(jq -r '.object.type // empty' <<<"$tag_ref") +tag_object=$(jq -r '.object.sha // empty' <<<"$tag_ref") +if [[ ! $tag_object =~ ^[0-9a-f]{40}$ ]]; then + printf 'Release %s has an invalid tag object\n' "$best_tag" >&2 + exit 1 +fi + +case "$tag_type" in + commit) + expected_commit=$tag_object + ;; + tag) + tag_data=$(curl -fsSL "$API_URL/git/tags/$tag_object") + if [[ $(jq -r '.object.type // empty' <<<"$tag_data") != commit ]]; then + printf 'Release %s does not resolve to a commit\n' "$best_tag" >&2 + exit 1 + fi + expected_commit=$(jq -r '.object.sha // empty' <<<"$tag_data") + ;; + *) + printf 'Release %s has unsupported tag object type %s\n' "$best_tag" "${tag_type:-}" >&2 + exit 1 + ;; +esac + +if [[ ! $expected_commit =~ ^[0-9a-f]{40}$ ]]; then + printf 'Release %s resolves to an invalid commit\n' "$best_tag" >&2 + exit 1 +fi + +tarball=$(mktemp) +trap 'rm -f "$tarball"' EXIT +curl -fsSL -o "$tarball" "https://github.com/$REPO/archive/refs/tags/$best_tag.tar.gz" + +expected_root="strata-$best_version" +served_roots=$(tar -tzf "$tarball" | cut -d/ -f1 | sort -u) +if [[ $served_roots != "$expected_root" ]]; then + printf 'Release %s contains root %s, expected %s\n' "$best_tag" "$served_roots" "$expected_root" >&2 + exit 1 +fi + +if ! archive_commit=$(git get-tar-commit-id < <(gzip -dc "$tarball")) || + [[ $archive_commit != "$expected_commit" ]]; then + printf 'Release %s archive commit does not match its tag\n' "$best_tag" >&2 + exit 1 +fi + +jq -n \ + --arg pkgver "$best_version" \ + --arg published_at "$best_published_at" \ + --arg source "$(sha256sum "$tarball" | cut -d' ' -f1)" \ + '{pkgver: $pkgver, published_at: $published_at, sha256sums: {any: [$source]}}' diff --git a/pkgbuilds/strata/PKGBUILD b/pkgbuilds/strata/PKGBUILD index ee5087fd..976374f7 100644 --- a/pkgbuilds/strata/PKGBUILD +++ b/pkgbuilds/strata/PKGBUILD @@ -1,6 +1,6 @@ pkgname=strata pkgver=0.8.0 -pkgrel=1 +pkgrel=2 pkgdesc='Fast, keyboard-first file manager for modern Linux desktops' arch=('x86_64' 'aarch64') url='https://github.com/lgse/strata' @@ -28,7 +28,7 @@ depends=( 'util-linux' 'xdg-terminal-exec' ) -makedepends=('cargo' 'glib2-devel' 'pkgconf') +makedepends=('cargo' 'git' 'glib2-devel' 'pkgconf') optdepends=( 'gvfs-smb: browse SMB network shares' 'imagemagick: additional camera RAW preview support' @@ -37,13 +37,20 @@ optdepends=( conflicts=('strata-git') options=('!debug' '!lto') -_commit='61b4b0bc74a5b8ca262c7c080e9fea57bba65ff5' source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") sha256sums=('8a6179ad5673afd8036ff6640c1740f6fa676516a05346c180d67ef98c4a56f7') prepare() { cd "$pkgname-$pkgver" + local commit + if ! commit=$(git get-tar-commit-id < <(gzip -dc "$srcdir/$pkgname-$pkgver.tar.gz")) || + [[ ! $commit =~ ^[0-9a-f]{40}$ ]]; then + printf 'Could not recover the release commit from the source archive\n' >&2 + return 1 + fi + printf '%s\n' "$commit" >.build-commit + cargo fetch --locked --target "$CARCH-unknown-linux-gnu" } @@ -51,7 +58,8 @@ build() { cd "$pkgname-$pkgver" export CARGO_TARGET_DIR=target - export STRATA_BUILD_COMMIT="$_commit" + export STRATA_BUILD_COMMIT + STRATA_BUILD_COMMIT=$(<.build-commit) export STRATA_RELEASE_TAG="v$pkgver" export STRATA_BUILD_KIND=stable cargo build --frozen --release @@ -61,7 +69,8 @@ check() { cd "$pkgname-$pkgver" export CARGO_TARGET_DIR=target - export STRATA_BUILD_COMMIT="$_commit" + export STRATA_BUILD_COMMIT + STRATA_BUILD_COMMIT=$(<.build-commit) export STRATA_RELEASE_TAG="v$pkgver" export STRATA_BUILD_KIND=stable cargo test --frozen --release --all-targets --all-features From 89e6e4d1ccc94a1ee4e3f2f91962b8d5d1c21d96 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 14:20:07 -0400 Subject: [PATCH 14/63] Fix Flea tests in constrained builders --- pkgbuilds/flea/PKGBUILD | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index 63c900fc..79381511 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -2,7 +2,7 @@ pkgname=flea pkgver=0.1.2 -pkgrel=1 +pkgrel=2 pkgdesc='Fast, keyboard-first file manager for Omarchy' arch=('x86_64') url='https://github.com/thisisgm/flea' @@ -110,7 +110,38 @@ check() { cd "$pkgname-$pkgver" export CARGO_TARGET_DIR=target - cargo test --frozen --release + + local -a test_args=() + if ! /usr/bin/prlimit --cpu=30 --as=1073741824 \ + /usr/bin/bwrap --unshare-all --die-with-parent --new-session --clearenv \ + --ro-bind /usr /usr --ro-bind /etc /etc \ + --symlink usr/lib /lib --symlink usr/lib /lib64 \ + --symlink usr/bin /bin --symlink usr/bin /sbin \ + --proc /proc --dev /dev --tmpfs /tmp /usr/bin/true >/dev/null 2>&1; then + printf 'Skipping sandbox integration tests: this builder cannot create an unprivileged namespace\n' + test_args+=( + --skip backend::archiveops::tests::a_tool_that_exits_zero_writing_nothing_is_caught_by_the_predicate + --skip backend::archiveops::tests::an_empty_archive_extracts_to_an_empty_directory_and_that_is_success + --skip backend::archiveops::tests::only_the_archive_root_extracts_to_nothing_while_nested_directories_do_not + --skip backend::archiveops::tests::the_emptiness_check_reads_the_destination_and_the_index_separately + --skip backend::thumbs::tests::a_cancelled_job_never_runs + --skip backend::thumbs::tests::a_real_file_round_trips_to_a_stamped_cache_entry + --skip backend::thumbs::tests::cancel_all_empties_the_queue + --skip backend::thumbs::tests::the_queue_is_bounded_and_drops_the_oldest_rather_than_growing + ) + fi + + if [[ ! -x /usr/bin/evince-thumbnailer ]] || + [[ ! -x /usr/bin/ffmpegthumbnailer ]] || + [[ ! -x /usr/bin/glycin-thumbnailer ]]; then + printf 'Skipping thumbnailer fixture tests: this builder lacks their optional host programs\n' + test_args+=( + --skip backend::thumbspec::tests::a_type_two_files_declare_goes_to_the_first_in_path_order + --skip backend::thumbspec::tests::the_heaviest_shipped_field_parses + ) + fi + + cargo test --frozen --release -- "${test_args[@]}" ./tests/js.sh ./tests/keymap-gen.sh } From d7d79c20a194b45234e1fee964c26627c9d42ec8 Mon Sep 17 00:00:00 2001 From: dhh <2741+dhh@users.noreply.github.com> Date: Thu, 3 Sep 2026 21:18:13 +0000 Subject: [PATCH 15/63] chore: rebuild against updated dependencies --- pkgbuilds/quickshell-git/.omarchy/package.json | 2 +- pkgbuilds/quickshell-git/PKGBUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/quickshell-git/.omarchy/package.json b/pkgbuilds/quickshell-git/.omarchy/package.json index 4a7a06e4..2bb526a5 100644 --- a/pkgbuilds/quickshell-git/.omarchy/package.json +++ b/pkgbuilds/quickshell-git/.omarchy/package.json @@ -8,7 +8,7 @@ "qt6-wayland" ], "rebuilt_against": { - "qt6-base": "6.11.2-2", + "qt6-base": "6.11.2-3", "qt6-declarative": "6.11.2-1", "qt6-wayland": "6.11.2-1" } diff --git a/pkgbuilds/quickshell-git/PKGBUILD b/pkgbuilds/quickshell-git/PKGBUILD index 9338de2d..5d1dec64 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=2 +pkgrel=3 pkgdesc='Flexible toolkit for making desktop shells with QtQuick' arch=(x86_64 aarch64) url='https://git.outfoxxed.me/quickshell/quickshell' From 05a4119b430c4244c1c2cdd825939d5344c4cfcf Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 18:05:52 -0400 Subject: [PATCH 16/63] Update Flea to 0.1.3 --- pkgbuilds/flea/PKGBUILD | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgbuilds/flea/PKGBUILD b/pkgbuilds/flea/PKGBUILD index 79381511..c03a00a5 100644 --- a/pkgbuilds/flea/PKGBUILD +++ b/pkgbuilds/flea/PKGBUILD @@ -1,8 +1,8 @@ # Maintainer: GM pkgname=flea -pkgver=0.1.2 -pkgrel=2 +pkgver=0.1.3 +pkgrel=1 pkgdesc='Fast, keyboard-first file manager for Omarchy' arch=('x86_64') url='https://github.com/thisisgm/flea' @@ -49,7 +49,7 @@ source=( "security-b4b7ee4.patch::$url/commit/${_security_patches[3]}.patch" ) sha256sums=( - '3788a735be2cb6eef0c1770833c82bc6591e20b7681fe02172aaea21c2a2d8fb' + '3599ab76253c444dea027f1ebe85b32612d9a174e60eb8aca33ee21d9e2d6973' 'c610a9f44294b67341940203943c9c567003b65cc436d6013cd36754379183d8' 'e457ef17e26f70057b1184eeb938fccc5906f48a7c77f1df573d55d13a845425' 'f8381456a3b5f39d341cc6610bf27beb8354892b17a1c8c6d7882db4e40824c3' From d9705d0e2fc1223e0aac23f92f298ca3994c998c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 22:04:04 -0400 Subject: [PATCH 17/63] Own ARM-compatible recipes and sync upstream directly --- pkgbuilds/asdcontrol/PKGBUILD | 2 +- .../github-copilot-cli/.omarchy/package.json | 5 ++- .../github-copilot-cli/.omarchy/upstream.sh | 34 +++++++++++++++++++ pkgbuilds/github-copilot-cli/PKGBUILD | 7 ++-- .../hyprland-preview-share-picker/PKGBUILD | 2 +- pkgbuilds/omasnap/PKGBUILD | 2 +- pkgbuilds/qmk-hid/.omarchy/package.json | 3 +- pkgbuilds/qmk-hid/.omarchy/upstream.sh | 27 +++++++++++++++ pkgbuilds/qmk-hid/PKGBUILD | 8 ++--- pkgbuilds/symfony-cli/.omarchy/package.json | 5 ++- pkgbuilds/symfony-cli/.omarchy/upstream.sh | 30 ++++++++++++++++ pkgbuilds/symfony-cli/PKGBUILD | 24 +++++++------ pkgbuilds/tensaku/.omarchy/package.json | 3 +- pkgbuilds/tensaku/.omarchy/upstream.sh | 26 ++++++++++++++ pkgbuilds/tensaku/PKGBUILD | 2 +- pkgbuilds/tzupdate/.omarchy/package.json | 3 +- pkgbuilds/tzupdate/.omarchy/upstream.sh | 26 ++++++++++++++ pkgbuilds/tzupdate/PKGBUILD | 2 +- pkgbuilds/v4l2-relayd/.omarchy/package.json | 8 +---- pkgbuilds/v4l2-relayd/.omarchy/upstream.sh | 26 ++++++++++++++ pkgbuilds/v4l2-relayd/PKGBUILD | 2 +- 21 files changed, 204 insertions(+), 43 deletions(-) create mode 100755 pkgbuilds/github-copilot-cli/.omarchy/upstream.sh create mode 100755 pkgbuilds/qmk-hid/.omarchy/upstream.sh create mode 100755 pkgbuilds/symfony-cli/.omarchy/upstream.sh create mode 100755 pkgbuilds/tensaku/.omarchy/upstream.sh create mode 100755 pkgbuilds/tzupdate/.omarchy/upstream.sh create mode 100755 pkgbuilds/v4l2-relayd/.omarchy/upstream.sh diff --git a/pkgbuilds/asdcontrol/PKGBUILD b/pkgbuilds/asdcontrol/PKGBUILD index eb67e1c8..6ae49791 100644 --- a/pkgbuilds/asdcontrol/PKGBUILD +++ b/pkgbuilds/asdcontrol/PKGBUILD @@ -4,7 +4,7 @@ epoch=1 pkgver=0.6.0 pkgrel=2 pkgdesc="Control brightness on Apple Displays connected via USB-C" -arch=('x86_64') +arch=('x86_64' 'aarch64') url="https://github.com/omakasui/asdcontrol" license=('GPL2') options=('!debug') diff --git a/pkgbuilds/github-copilot-cli/.omarchy/package.json b/pkgbuilds/github-copilot-cli/.omarchy/package.json index 39cf2ffe..db153c3e 100644 --- a/pkgbuilds/github-copilot-cli/.omarchy/package.json +++ b/pkgbuilds/github-copilot-cli/.omarchy/package.json @@ -1,5 +1,4 @@ { - "source": "aur", - "release_ring": "fast", - "upstream_commit": "fc4db00ecaf6c2669422b32c023a16bf6eb4e6c7" + "source": "local", + "release_ring": "fast" } diff --git a/pkgbuilds/github-copilot-cli/.omarchy/upstream.sh b/pkgbuilds/github-copilot-cli/.omarchy/upstream.sh new file mode 100755 index 00000000..92db1d25 --- /dev/null +++ b/pkgbuilds/github-copilot-cli/.omarchy/upstream.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# GitHub Copilot CLI is published to npm. Track npm's stable `latest` tag, +# then hash both inputs the PKGBUILD downloads. AUR is deliberately not part +# of this update path: Omarchy owns the multi-architecture recipe. +set -euo pipefail + +REGISTRY_URL="https://registry.npmjs.org/@github%2Fcopilot" +CHANGELOG_BASE="https://raw.githubusercontent.com/github/copilot-cli" + +metadata=$(curl -fsSL "$REGISTRY_URL") +version=$(jq -r '."dist-tags".latest // ""' <<<"$metadata") +tarball=$(jq -r --arg version "$version" '.versions[$version].dist.tarball // ""' <<<"$metadata") + +if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || ! $tarball =~ ^https://registry\.npmjs\.org/ ]]; then + echo "npm returned an unusable Copilot release: version='$version' tarball='$tarball'" >&2 + exit 1 +fi + +current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +if [[ $(vercmp "$version" "$current") -le 0 ]]; then + echo '{}' + exit 0 +fi + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +curl -fsSL -o "$work/copilot.tgz" "$tarball" +curl -fsSL -o "$work/changelog.md" "$CHANGELOG_BASE/v${version}/changelog.md" + +jq -n \ + --arg pkgver "$version" \ + --arg package_sum "$(sha256sum "$work/copilot.tgz" | cut -d' ' -f1)" \ + --arg changelog_sum "$(sha256sum "$work/changelog.md" | cut -d' ' -f1)" \ + '{pkgver: $pkgver, sha256sums: {any: [$package_sum, $changelog_sum]}}' diff --git a/pkgbuilds/github-copilot-cli/PKGBUILD b/pkgbuilds/github-copilot-cli/PKGBUILD index dc61d75a..da9b7ac0 100644 --- a/pkgbuilds/github-copilot-cli/PKGBUILD +++ b/pkgbuilds/github-copilot-cli/PKGBUILD @@ -14,7 +14,7 @@ pkgdesc="GitHub Copilot CLI brings the power of Copilot coding agent directly to url="https://github.com/github/copilot-cli" _urlraw="https://raw.githubusercontent.com/github/copilot-cli/v${pkgver}" -arch=("x86_64") +arch=("x86_64" "aarch64") license=("LicenseRef-GitHub-Copilot") @@ -30,8 +30,8 @@ source=("https://registry.npmjs.org/${_npmmodule}/-/copilot-${pkgver}.tgz" "CHANGELOG-${pkgver}.md::${_urlraw}/changelog.md") noextract=("copilot-${pkgver}.tgz") -b2sums=('41c8306b58a142afe162bad9822b150babb0f3903fe7392aa7106fcb0ffce4483c0a2ebda8bb1d2036199deb52dc83d9a0e9408b8e11f7b0bc2a8f57c6841425' - '5d2dfc870356f83b9b890d351b21bc9339100b0bfdce405ee77751e934dbeab0f8656f5eee85119a06aa3b01dcf3c91224ce28c24ff318eb1b13fdb6082037cd') +sha256sums=('563cbfe9b873fa52e998376cfea499eb77a5a300fb6db48fcc85f8eb297da4b9' + '653b32793f5e5d29952c0e301649473c8097afc15270aa488efa08aa9399afcf') # Document: https://wiki.archlinux.org/title/Node.js_package_guidelines package() { @@ -124,4 +124,3 @@ package() { ln -sf "/usr/lib/node_modules/${_npmmodule}/LICENSE.md" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" } - diff --git a/pkgbuilds/hyprland-preview-share-picker/PKGBUILD b/pkgbuilds/hyprland-preview-share-picker/PKGBUILD index ab85617e..b3d80bef 100644 --- a/pkgbuilds/hyprland-preview-share-picker/PKGBUILD +++ b/pkgbuilds/hyprland-preview-share-picker/PKGBUILD @@ -4,7 +4,7 @@ pkgname="hyprland-preview-share-picker" pkgver=0.2.1 pkgrel=1 pkgdesc="An alternative share picker for hyprland with window and monitor previews" -arch=(x86_64) +arch=(x86_64 aarch64) url="https://github.com/WhySoBad/hyprland-preview-share-picker" license=(MIT) depends=('gtk4' 'gtk4-layer-shell' 'xdg-desktop-portal-hyprland' 'hyprland') diff --git a/pkgbuilds/omasnap/PKGBUILD b/pkgbuilds/omasnap/PKGBUILD index 8404121e..24b7e086 100644 --- a/pkgbuilds/omasnap/PKGBUILD +++ b/pkgbuilds/omasnap/PKGBUILD @@ -4,7 +4,7 @@ pkgname=omasnap pkgver=1.20.1 pkgrel=1 pkgdesc="Native Wayland screenshot and annotation overlay for Hyprland" -arch=('x86_64') +arch=('x86_64' 'aarch64') url="https://github.com/tobi/omasnap" license=('MIT' 'OFL-1.1') depends=( diff --git a/pkgbuilds/qmk-hid/.omarchy/package.json b/pkgbuilds/qmk-hid/.omarchy/package.json index d00eb135..2a9719d3 100644 --- a/pkgbuilds/qmk-hid/.omarchy/package.json +++ b/pkgbuilds/qmk-hid/.omarchy/package.json @@ -1,4 +1,3 @@ { - "source": "aur", - "upstream_commit": "a850f2d07e825f61cb01b87931d2ae5f1802dde1" + "source": "local" } diff --git a/pkgbuilds/qmk-hid/.omarchy/upstream.sh b/pkgbuilds/qmk-hid/.omarchy/upstream.sh new file mode 100755 index 00000000..3cb0167b --- /dev/null +++ b/pkgbuilds/qmk-hid/.omarchy/upstream.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# qmk_hid marks every GitHub Release as a prerelease, including the versions +# it considers current. Track its semver tags instead and hash the tagged +# source archive only when a newer version appears. +set -euo pipefail + +REPO="https://github.com/FrameworkComputer/qmk_hid.git" +best="" +while read -r tag; do + tag=${tag%\^\{\}} + [[ $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue + version=${BASH_REMATCH[1]} + if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then + best=$version + fi +done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') + +[[ -n $best ]] || { echo "No usable qmk_hid version tag found" >&2; exit 1; } +current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +if [[ $(vercmp "$best" "$current") -le 0 ]]; then + echo '{}' + exit 0 +fi + +sum=$(curl -fsSL "https://github.com/FrameworkComputer/qmk_hid/archive/refs/tags/v${best}.tar.gz" | sha256sum | cut -d' ' -f1) +jq -n --arg pkgver "$best" --arg sha256 "$sum" \ + '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/qmk-hid/PKGBUILD b/pkgbuilds/qmk-hid/PKGBUILD index c71f3af1..f2508dfb 100644 --- a/pkgbuilds/qmk-hid/PKGBUILD +++ b/pkgbuilds/qmk-hid/PKGBUILD @@ -2,16 +2,16 @@ pkgname=qmk-hid _srcname=qmk_hid -pkgver=0.1.11 -pkgrel=3 +pkgver=0.1.13 +pkgrel=1 pkgdesc="Commandline tool for interacting with QMK devices over HID" -arch=('x86_64') +arch=('x86_64' 'aarch64') url="https://github.com/FrameworkComputer/qmk_hid" license=('BSD') depends=('gcc-libs' 'systemd-libs' 'libcap') makedepends=('cargo') source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz") -b2sums=('e898d27cfe8350f4a8cd2938a3d066935093daf1292f41fc10b8f1937059f2b59e4ac34a987f26c8258a5d3b15a900059024fe3a18ed793f7e3b524939ab4c71') +sha256sums=('8a058395b8ba56ff54308a681223f11a6032c54df42b4d0223143ccf09d0c858') options=('!lto') prepare() { diff --git a/pkgbuilds/symfony-cli/.omarchy/package.json b/pkgbuilds/symfony-cli/.omarchy/package.json index 3dc28470..db153c3e 100644 --- a/pkgbuilds/symfony-cli/.omarchy/package.json +++ b/pkgbuilds/symfony-cli/.omarchy/package.json @@ -1,5 +1,4 @@ { - "source": "aur", - "release_ring": "fast", - "upstream_commit": "39667376addf3916b7bed3ecbe3ec6ed2af367e4" + "source": "local", + "release_ring": "fast" } diff --git a/pkgbuilds/symfony-cli/.omarchy/upstream.sh b/pkgbuilds/symfony-cli/.omarchy/upstream.sh new file mode 100755 index 00000000..c68049af --- /dev/null +++ b/pkgbuilds/symfony-cli/.omarchy/upstream.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Symfony publishes a checksum manifest with every stable GitHub release. +# The `latest` URL is its vendor-maintained stable feed, so no GitHub API or +# AUR state is involved in selecting and verifying the source tarball. +set -euo pipefail + +CHECKSUMS_URL="https://github.com/symfony-cli/symfony-cli/releases/latest/download/checksums.txt" +checksums=$(curl -fsSL "$CHECKSUMS_URL") + +best="" +best_sum="" +while read -r sum filename; do + [[ $filename =~ ^symfony-cli-([0-9]+\.[0-9]+\.[0-9]+)\.tar\.gz$ ]] || continue + version=${BASH_REMATCH[1]} + [[ $sum =~ ^[0-9a-f]{64}$ ]] || continue + if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then + best=$version + best_sum=$sum + fi +done <<<"$checksums" + +[[ -n $best ]] || { echo "Symfony's latest checksum manifest names no source tarball" >&2; exit 1; } +current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +if [[ $(vercmp "$best" "$current") -le 0 ]]; then + echo '{}' + exit 0 +fi + +jq -n --arg pkgver "$best" --arg sha256 "$best_sum" \ + '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/symfony-cli/PKGBUILD b/pkgbuilds/symfony-cli/PKGBUILD index 28cba8e9..5c09eb21 100644 --- a/pkgbuilds/symfony-cli/PKGBUILD +++ b/pkgbuilds/symfony-cli/PKGBUILD @@ -1,23 +1,27 @@ # Maintainer: Florian Moser pkgname=symfony-cli -pkgrel=8 +pkgrel=9 pkgver=5.20.0 pkgdesc="The Symfony client helps developers create and manage Symfony applications." url="https://symfony.com/" -arch=('x86_64') +arch=('x86_64' 'aarch64') license=('AGPL3') depends=('glibc') -makedepends=('git' 'go') +makedepends=('go') provides=('symfony-cli') conflicts=('symfony-cli') -source=( - "git+https://github.com/symfony-cli/symfony-cli#tag=v${pkgver}" -) -sha256sums=('SKIP') +source=("$pkgname-$pkgver.tar.gz::https://github.com/symfony-cli/symfony-cli/releases/download/v${pkgver}/symfony-cli-${pkgver}.tar.gz") +sha256sums=('11354f7206740dc8b31410bf7fbcb20debccb4a92935da84729fd7d754b2082f') +noextract=("$pkgname-$pkgver.tar.gz") + +prepare() { + mkdir "$pkgname-$pkgver" + bsdtar -xf "$pkgname-$pkgver.tar.gz" -C "$pkgname-$pkgver" +} build() { - cd "symfony-cli" + cd "$pkgname-$pkgver" DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") PKGVER=${pkgver} @@ -26,6 +30,6 @@ build() { go build -trimpath -ldflags '-s -w -X main.channel=stable -X main.buildDate='"${DATE}"' -X main.version='"${PKGVER}"'' } -package() { - install -D -m 755 "${srcdir}/symfony-cli/symfony-cli" "${pkgdir}/usr/bin/symfony" +package() { + install -D -m 755 "${srcdir}/${pkgname}-${pkgver}/symfony-cli" "${pkgdir}/usr/bin/symfony" } diff --git a/pkgbuilds/tensaku/.omarchy/package.json b/pkgbuilds/tensaku/.omarchy/package.json index 8995d643..2a9719d3 100644 --- a/pkgbuilds/tensaku/.omarchy/package.json +++ b/pkgbuilds/tensaku/.omarchy/package.json @@ -1,4 +1,3 @@ { - "source": "aur", - "upstream_commit": "c6fe8f2c6dc43a5c2f0c95df5656037b351dacb7" + "source": "local" } diff --git a/pkgbuilds/tensaku/.omarchy/upstream.sh b/pkgbuilds/tensaku/.omarchy/upstream.sh new file mode 100755 index 00000000..cf5d9be7 --- /dev/null +++ b/pkgbuilds/tensaku/.omarchy/upstream.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Tensaku publishes semver tags but no source checksum manifest. Select the +# newest tag using pacman's ordering and hash its source archive. +set -euo pipefail + +REPO="https://github.com/jondkinney/tensaku.git" +version="" +while read -r tag; do + tag=${tag%\^\{\}} + [[ $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue + candidate=${BASH_REMATCH[1]} + if [[ -z $version || $(vercmp "$candidate" "$version") -gt 0 ]]; then + version=$candidate + fi +done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') + +[[ -n $version ]] || { echo "No usable Tensaku version tag found" >&2; exit 1; } +current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +if [[ $(vercmp "$version" "$current") -le 0 ]]; then + echo '{}' + exit 0 +fi + +sum=$(curl -fsSL "https://github.com/jondkinney/tensaku/archive/refs/tags/v${version}.tar.gz" | sha256sum | cut -d' ' -f1) +jq -n --arg pkgver "$version" --arg sha256 "$sum" \ + '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/tensaku/PKGBUILD b/pkgbuilds/tensaku/PKGBUILD index 1f8bd234..cc49c792 100644 --- a/pkgbuilds/tensaku/PKGBUILD +++ b/pkgbuilds/tensaku/PKGBUILD @@ -9,7 +9,7 @@ pkgname=tensaku pkgver=0.28.0 pkgrel=1 pkgdesc='Modern screenshot annotation tool for Wayland' -arch=('x86_64') +arch=('x86_64' 'aarch64') url='https://github.com/jondkinney/tensaku' license=('MPL-2.0') depends=('gtk4' 'gtk4-layer-shell' 'libadwaita' 'libepoxy' 'fontconfig') diff --git a/pkgbuilds/tzupdate/.omarchy/package.json b/pkgbuilds/tzupdate/.omarchy/package.json index cf4a8c65..2a9719d3 100644 --- a/pkgbuilds/tzupdate/.omarchy/package.json +++ b/pkgbuilds/tzupdate/.omarchy/package.json @@ -1,4 +1,3 @@ { - "source": "aur", - "upstream_commit": "1b3c3bdf6289a348da09eed57d653d3f6dce1956" + "source": "local" } diff --git a/pkgbuilds/tzupdate/.omarchy/upstream.sh b/pkgbuilds/tzupdate/.omarchy/upstream.sh new file mode 100755 index 00000000..3ccaf442 --- /dev/null +++ b/pkgbuilds/tzupdate/.omarchy/upstream.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# tzupdate publishes version tags but no GitHub Releases. Track its semver +# tags and hash the tagged source archive when a new version appears. +set -euo pipefail + +REPO="https://github.com/cdown/tzupdate.git" +best="" +while read -r tag; do + tag=${tag%\^\{\}} + [[ $tag =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue + version=${BASH_REMATCH[1]} + if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then + best=$version + fi +done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') + +[[ -n $best ]] || { echo "No usable tzupdate version tag found" >&2; exit 1; } +current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +if [[ $(vercmp "$best" "$current") -le 0 ]]; then + echo '{}' + exit 0 +fi + +sum=$(curl -fsSL "https://github.com/cdown/tzupdate/archive/refs/tags/${best}.tar.gz" | sha256sum | cut -d' ' -f1) +jq -n --arg pkgver "$best" --arg sha256 "$sum" \ + '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/tzupdate/PKGBUILD b/pkgbuilds/tzupdate/PKGBUILD index a8d05c73..c9aeb3a2 100644 --- a/pkgbuilds/tzupdate/PKGBUILD +++ b/pkgbuilds/tzupdate/PKGBUILD @@ -5,7 +5,7 @@ pkgver=3.1.0 pkgrel=1 pkgdesc='Set the system timezone based on IP geolocation' url=https://github.com/cdown/tzupdate -arch=('x86_64') +arch=('x86_64' 'aarch64') options=(!lto) license=('MIT') makedepends=('cargo') diff --git a/pkgbuilds/v4l2-relayd/.omarchy/package.json b/pkgbuilds/v4l2-relayd/.omarchy/package.json index 4c423964..2a9719d3 100644 --- a/pkgbuilds/v4l2-relayd/.omarchy/package.json +++ b/pkgbuilds/v4l2-relayd/.omarchy/package.json @@ -1,9 +1,3 @@ { - "source": "aur", - "sync": false, - "pkgrel": { - "suffix": 2, - "offset": 1 - }, - "upstream_commit": "086fa284d7f5ad562ea6ba79b3cb4fd798e759d3" + "source": "local" } diff --git a/pkgbuilds/v4l2-relayd/.omarchy/upstream.sh b/pkgbuilds/v4l2-relayd/.omarchy/upstream.sh new file mode 100755 index 00000000..4899ac21 --- /dev/null +++ b/pkgbuilds/v4l2-relayd/.omarchy/upstream.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# v4l2-relayd uses GitLab tags named upstream/. Keep Omarchy's local +# patches and follow that authoritative tag stream directly. +set -euo pipefail + +REPO="https://gitlab.com/vicamo/v4l2-relayd.git" +best="" +while read -r tag; do + tag=${tag%\^\{\}} + [[ $tag =~ ^upstream/([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue + version=${BASH_REMATCH[1]} + if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then + best=$version + fi +done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') + +[[ -n $best ]] || { echo "No usable v4l2-relayd upstream tag found" >&2; exit 1; } +current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +if [[ $(vercmp "$best" "$current") -le 0 ]]; then + echo '{}' + exit 0 +fi + +sum=$(curl -fsSL "https://gitlab.com/vicamo/v4l2-relayd/-/archive/upstream/${best}/v4l2-relayd-upstream-${best}.tar.gz" | sha256sum | cut -d' ' -f1) +jq -n --arg pkgver "$best" --arg sha256 "$sum" \ + '{pkgver: $pkgver, sha256sums: {any: [$sha256, "SKIP", "SKIP"]}}' diff --git a/pkgbuilds/v4l2-relayd/PKGBUILD b/pkgbuilds/v4l2-relayd/PKGBUILD index b276a5f8..f4784e4b 100644 --- a/pkgbuilds/v4l2-relayd/PKGBUILD +++ b/pkgbuilds/v4l2-relayd/PKGBUILD @@ -4,7 +4,7 @@ pkgname=v4l2-relayd pkgver=0.2.0 pkgrel=2.2 pkgdesc="Plays gstreamer sources into v4l2loopback devices." -arch=('x86_64') +arch=('x86_64' 'aarch64') url="https://gitlab.com/vicamo/v4l2-relayd" license=('GPL') depends=( From b89770c1da217a0a9ed0defc142e16f53489b32c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 22:13:59 -0400 Subject: [PATCH 18/63] Expand declarative upstream providers --- README.md | 46 ++++- bin/sync-upstream | 106 +++++++++-- helpers/package-metadata.sh | 30 +++- helpers/upstream-github.sh | 166 +++++++++++++++++- .../github-copilot-cli/.omarchy/package.json | 11 +- .../github-copilot-cli/.omarchy/upstream.sh | 34 ---- pkgbuilds/qmk-hid/.omarchy/package.json | 9 +- pkgbuilds/qmk-hid/.omarchy/upstream.sh | 27 --- pkgbuilds/symfony-cli/.omarchy/package.json | 9 +- pkgbuilds/symfony-cli/.omarchy/upstream.sh | 30 ---- pkgbuilds/tensaku/.omarchy/package.json | 9 +- pkgbuilds/tensaku/.omarchy/upstream.sh | 26 --- pkgbuilds/tzupdate/.omarchy/package.json | 9 +- pkgbuilds/tzupdate/.omarchy/upstream.sh | 26 --- pkgbuilds/v4l2-relayd/.omarchy/package.json | 13 +- pkgbuilds/v4l2-relayd/.omarchy/upstream.sh | 26 --- pkgbuilds/v4l2-relayd/PKGBUILD | 4 +- 17 files changed, 372 insertions(+), 209 deletions(-) delete mode 100755 pkgbuilds/github-copilot-cli/.omarchy/upstream.sh delete mode 100755 pkgbuilds/qmk-hid/.omarchy/upstream.sh delete mode 100755 pkgbuilds/symfony-cli/.omarchy/upstream.sh delete mode 100755 pkgbuilds/tensaku/.omarchy/upstream.sh delete mode 100755 pkgbuilds/tzupdate/.omarchy/upstream.sh delete mode 100755 pkgbuilds/v4l2-relayd/.omarchy/upstream.sh diff --git a/README.md b/README.md index 34e76149..010adba3 100644 --- a/README.md +++ b/README.md @@ -322,9 +322,49 @@ recent releases are considered. The provider fails closed on anything it cannot read — an unusable tag, timestamp, or checksum stops the sync rather than being skipped. -A package may also declare `"min_release_age": "24h"` (`s`/`m`/`h`/`d` suffix or -bare seconds) to quarantine fresh releases until maintainers have had time to -pull a bad or compromised one. The newest release that has cleared the window +Projects that publish version tags but no checksum manifest can declare the +tag repository, the exact tag shape, and every source that should be hashed: + +```json +"upstream": { + "git_tags": "https://github.com/owner/project.git", + "tag_pattern": "v{pkgver}", + "sources": { + "any": ["https://github.com/owner/project/archive/refs/tags/{tag}.tar.gz"] + } +} +``` + +The newest matching tag is selected with pacman's `vercmp`; unrelated tags are +ignored. `tag_pattern` must contain exactly one `{pkgver}`. Source templates may +use `{tag}` and `{pkgver}`. Each expanded URL must be HTTPS and is downloaded +only when the discovered version is newer. A checked-in patch or other local +source can be included as `file:patch-name.patch`; it is hashed from the package +directory. Keys such as `any`, `x86_64`, and `aarch64` select the corresponding +`sha256sums` array. + +npm packages use the same source mapping, with `{npm_tarball}` available for +the tarball named by the selected dist-tag: + +```json +"upstream": { + "npm": "@scope/package", + "dist_tag": "latest", + "sources": { + "any": ["{npm_tarball}", "https://example.com/v{pkgver}/CHANGELOG.md"] + } +} +``` + +`dist_tag` defaults to `latest`. The registry's publication timestamp is +carried into the provider result, so `min_release_age` works for npm packages. +Exactly one of `github`, `git_tags`, or `npm` may appear in a declaration. + +A timestamped provider may also declare `"min_release_age": "24h"` +(`s`/`m`/`h`/`d` suffix or bare seconds) to quarantine fresh releases until +maintainers have had time to pull a bad or compromised one. GitHub Releases and +npm provide publication times; raw git tags do not, so combining `git_tags` +with this policy fails closed. The newest release that has cleared the window ships, so a fast release cadence cannot starve updates. The window is enforced centrally: whatever reports the release must prove its age via `published_at`, or the sync fails. A maintainer deliberately shipping inside the window runs diff --git a/bin/sync-upstream b/bin/sync-upstream index a0da5f3c..d05d5f88 100755 --- a/bin/sync-upstream +++ b/bin/sync-upstream @@ -18,11 +18,11 @@ Usage: $0 [PACKAGE...] Update packages that track an upstream vendor release feed instead of the AUR. -A package whose upstream ships tagged GitHub releases with a checksum manifest -opts in declaratively, via "upstream" in .omarchy/package.json (see -helpers/upstream-github.sh for the schema); no code needed. Anything with a -bespoke feed provides pkgbuilds//.omarchy/upstream.sh instead, a hook -that reports the newest upstream release as JSON on stdout: +Packages opt in declaratively through "upstream" in .omarchy/package.json. +Providers cover GitHub Releases with checksum manifests, semver-shaped git +tags whose source URLs can be hashed, and npm dist-tags. See README.md for the +schemas. Anything outside those conventions may provide +pkgbuilds//.omarchy/upstream.sh, a hook that reports JSON on stdout: { "pkgver": "1.2.3", @@ -332,27 +332,27 @@ sync_package() { return 0 fi - local github_repo has_upstream=false - github_repo=$(package_upstream_github_repo "$package_dir") + local provider has_upstream=false + provider=$(package_upstream_provider "$package_dir") if package_has_upstream_provider "$package_dir"; then has_upstream=true fi # A present-but-unusable declaration fails loudly; treating it like "no # upstream source" would silently drop the package from scheduled runs. - if [[ "$has_upstream" == true && -z "$github_repo" ]]; then - print_error "Package $package has an unusable upstream declaration (needs a github owner/repo)" + if [[ "$has_upstream" == true && -z "$provider" ]]; then + print_error "Package $package has an unusable or ambiguous upstream declaration" ((++FAILED)) return 0 fi - if [[ -n "$github_repo" && -f "$hook" ]]; then - print_error "Package $package declares both upstream.github and an upstream.sh hook; keep exactly one" + if [[ -n "$provider" && -f "$hook" ]]; then + print_error "Package $package declares both an upstream provider and an upstream.sh hook; keep exactly one" ((++FAILED)) return 0 fi - if [[ -z "$github_repo" && ! -f "$hook" ]]; then + if [[ -z "$provider" && ! -f "$hook" ]]; then if [[ "$SPECIFIC_MODE" == true ]]; then print_error "Package $package has neither an upstream declaration nor .omarchy/upstream.sh" ((++FAILED)) @@ -372,10 +372,15 @@ sync_package() { print_info "Checking $package for upstream releases..." - local release - if [[ -n "$github_repo" ]]; then - if ! release=$(github_upstream_release "$package_dir" "$min_age"); then - print_error "GitHub release provider failed for $package" + local release release_status=0 + if [[ -n "$provider" ]]; then + case "$provider" in + github) release=$(github_upstream_release "$package_dir" "$min_age") || release_status=$? ;; + git_tags) release=$(git_tags_upstream_release "$package_dir") || release_status=$? ;; + npm) release=$(npm_upstream_release "$package_dir") || release_status=$? ;; + esac + if [[ ${release_status:-0} -ne 0 ]]; then + print_error "$provider upstream provider failed for $package" ((++FAILED)) return 0 fi @@ -614,10 +619,79 @@ EOF echo '{"source": "local", "upstream": {"github": "example/tool"}}' > "$agepkg/.omarchy/package.json" vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? check "upstream without checksums/assets is rejected" "1" "$vst" + echo '{"source":"local","upstream":{"github":"example/tool","git_tags":"https://example/tool.git","checksums":"sums","assets":{"any":"tool"}}}' > "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "multiple provider types are rejected" "1" "$vst" cp "$pkg/.omarchy/package.json" "$agepkg/.omarchy/package.json" vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? check "the real declaration shape is accepted" "0" "$vst" + echo "Git-tag provider:" + local tagpkg="$TEMP_DIR/selftest-tags" tag_sum remote_sum local_sum + mkdir -p "$tagpkg/.omarchy" + printf 'pkgver=1.0.0\npkgrel=4\nsha256sums=("old" "old")\n' > "$tagpkg/PKGBUILD" + printf 'local fixture\n' > "$tagpkg/local.patch" + cat > "$tagpkg/.omarchy/package.json" <<'EOF' +{ + "source": "local", + "upstream": { + "git_tags": "https://example.test/tool.git", + "tag_pattern": "release/{pkgver}", + "sources": { + "any": ["https://downloads.example.test/tool-{pkgver}.tar.gz", "file:local.patch"] + } + } +} +EOF + git_tags_fetch_refs() { + printf '%s\n' \ + 'aaaa refs/tags/release/1.9.0' \ + 'bbbb refs/tags/release/1.10.0' \ + 'cccc refs/tags/not-a-release' + } + upstream_fetch_source() { printf 'remote fixture for %s\n' "$1" > "$2"; } + tag_sum=$(git_tags_upstream_release "$tagpkg") + remote_sum=$(printf 'remote fixture for %s\n' 'https://downloads.example.test/tool-1.10.0.tar.gz' | sha256sum | cut -d' ' -f1) + local_sum=$(sha256sum "$tagpkg/local.patch" | cut -d' ' -f1) + check "pacman ordering selects 1.10.0 over 1.9.0" "1.10.0" "$(jq -r '.pkgver' <<<"$tag_sum")" + check "remote source template is downloaded and hashed" "$remote_sum" "$(jq -r '.sha256sums.any[0]' <<<"$tag_sum")" + check "local source entry is hashed" "$local_sum" "$(jq -r '.sha256sums.any[1]' <<<"$tag_sum")" + vst=0; validate_package_metadata "$tagpkg" >/dev/null || vst=$? + check "git-tags declaration validates" "0" "$vst" + + echo "npm provider:" + local npmpkg="$TEMP_DIR/selftest-npm" npm_sum npm_tar_sum npm_notes_sum + mkdir -p "$npmpkg/.omarchy" + printf 'pkgver=1.0.0\npkgrel=1\nsha256sums=("old" "old")\n' > "$npmpkg/PKGBUILD" + cat > "$npmpkg/.omarchy/package.json" <<'EOF' +{ + "source": "local", + "upstream": { + "npm": "@example/tool", + "dist_tag": "latest", + "sources": { + "any": ["{npm_tarball}", "https://example.test/tool/{pkgver}/notes"] + } + } +} +EOF + npm_fetch_metadata() { + jq -n '{ + "dist-tags": {latest: "2.0.0"}, + versions: {"2.0.0": {dist: {tarball: "https://registry.npmjs.org/@example/tool/-/tool-2.0.0.tgz"}}}, + time: {"2.0.0": "2024-01-02T03:04:05.000Z"} + }' + } + npm_sum=$(npm_upstream_release "$npmpkg") + npm_tar_sum=$(printf 'remote fixture for %s\n' 'https://registry.npmjs.org/@example/tool/-/tool-2.0.0.tgz' | sha256sum | cut -d' ' -f1) + npm_notes_sum=$(printf 'remote fixture for %s\n' 'https://example.test/tool/2.0.0/notes' | sha256sum | cut -d' ' -f1) + check "npm dist-tag selects its version" "2.0.0" "$(jq -r '.pkgver' <<<"$npm_sum")" + check "npm tarball placeholder is hashed" "$npm_tar_sum" "$(jq -r '.sha256sums.any[0]' <<<"$npm_sum")" + check "npm pkgver template is hashed" "$npm_notes_sum" "$(jq -r '.sha256sums.any[1]' <<<"$npm_sum")" + check "npm publication time is preserved" "2024-01-02T03:04:05.000Z" "$(jq -r '.published_at' <<<"$npm_sum")" + vst=0; validate_package_metadata "$npmpkg" >/dev/null || vst=$? + check "npm declaration validates" "0" "$vst" + # End to end over the real mise-bin package: its checked-in metadata and # PKGBUILD, the full sync_package path (selection, validation, backstop, # rewrite, read-back verification), with only the two network fetches diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index c121b46a..b14c3da9 100644 --- a/helpers/package-metadata.sh +++ b/helpers/package-metadata.sh @@ -16,6 +16,8 @@ # { "source": "local", "channels": ["edge", "rc", "stable"] } # { "source": "local", "min_release_age": "24h" } # { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } } +# { "source": "local", "upstream": { "git_tags": "https://example/repo.git", "tag_pattern": "v{pkgver}", "sources": { "any": ["https://example/archive/{tag}.tar.gz"] } } } +# { "source": "local", "upstream": { "npm": "@scope/package", "sources": { "any": ["{npm_tarball}"] } } } # # bin/sync-aur also writes upstream_commit for AUR-backed packages, and # bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on. @@ -446,17 +448,33 @@ validate_package_metadata() { # `has` rather than `// {}`: jq's // treats false as absent, which would # let "upstream": false slip through as an empty declaration. if ! jq -e ' + def valid_sources: + type == "object" and length > 0 and (to_entries | all( + (.key | test("\\A[a-z0-9_]+\\z")) + and (.value | type == "array" and length > 0 and all(type == "string" and length > 0)) + )); if has("upstream") | not then true elif (.upstream | type) != "object" then false else .upstream | - ((.github // "") | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z")) - and ((.checksums // "") | type == "string" and length > 0) - and ((.assets // {}) | type == "object" and length > 0 and (to_entries | all( - (.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0) - ))) + ([has("github"), has("git_tags"), has("npm")] | map(select(.)) | length) == 1 + and if has("github") then + (.github | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z")) + and (.checksums | type == "string" and length > 0) + and (.assets | type == "object" and length > 0 and (to_entries | all( + (.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0) + ))) + elif has("git_tags") then + (.git_tags | type == "string" and test("\\Ahttps://[^[:space:]]+\\.git\\z")) + and (.tag_pattern | type == "string" and (split("{pkgver}") | length) == 2) + and (.sources | valid_sources) + else + (.npm | type == "string" and test("\\A(@[a-z0-9_.-]+/)?[a-z0-9_.-]+\\z")) + and ((.dist_tag // "latest") | type == "string" and test("\\A[a-z0-9_.-]+\\z")) + and (.sources | valid_sources) + end end ' "$metadata" >/dev/null; then - echo "invalid upstream for $(basename "$pkgdir"): needs github owner/repo, checksums asset name, and an assets arch->name map" + echo "invalid upstream for $(basename "$pkgdir"): configure exactly one valid github, git_tags, or npm provider" return 1 fi diff --git a/helpers/upstream-github.sh b/helpers/upstream-github.sh index e37e6ab4..14b14461 100644 --- a/helpers/upstream-github.sh +++ b/helpers/upstream-github.sh @@ -1,4 +1,4 @@ -# GitHub-releases upstream provider for bin/sync-upstream. +# Declarative upstream providers for bin/sync-upstream. # # A package whose upstream ships tagged GitHub releases with a checksum # manifest asset needs no upstream.sh hook: the whole feed is data, declared @@ -17,13 +17,20 @@ # "v", which is stripped for pkgver. Drafts and prereleases are ignored. The # provider emits the same JSON contract as an upstream.sh hook, so # bin/sync-upstream's validation and min_release_age backstop apply -# unchanged; a feed that fits no convention keeps a bespoke upstream.sh. +# unchanged. Git-tag and npm providers below cover projects without a release +# checksum manifest; a feed that fits no convention keeps a bespoke hook. -package_upstream_github_repo() { - local pkgdir="$1" - # `objects` drops a non-object upstream value (validation rejects those - # separately) instead of erroring the jq pipeline. - package_metadata_value "$pkgdir" '(.upstream? | objects | .github)' "" +# Return the single declarative provider selected by a package. An empty +# result means either no provider or an invalid/ambiguous declaration; the +# caller distinguishes those through package_has_upstream_provider(). +package_upstream_provider() { + local pkgdir="$1" metadata + metadata=$(metadata_file_for_dir "$pkgdir") + jq -r ' + (.upstream? | objects) as $u + | [$u | keys[] | select(. == "github" or . == "git_tags" or . == "npm")] + | if length == 1 then .[0] else "" end + ' "$metadata" } # Fetches sit behind functions so the self-test can replace them with fixture @@ -43,6 +50,151 @@ github_fetch_checksums() { curl -fsSL "https://github.com/$repo/releases/download/$tag/$asset" } +git_tags_fetch_refs() { + local repo="$1" + git ls-remote --tags "$repo" +} + +npm_fetch_metadata() { + local package="$1" encoded + encoded=$(jq -rn --arg package "$package" '$package | @uri') + curl -fsSL "https://registry.npmjs.org/$encoded" +} + +upstream_fetch_source() { + local url="$1" output="$2" + curl --proto '=https' --proto-redir '=https' -fsSL -o "$output" "$url" +} + +# Hash every URL template in upstream.sources and emit hook-contract JSON. +# Templates may use {pkgver}, {tag}, and (for npm) {npm_tarball}. Downloads +# happen only after discovery reports a version newer than the PKGBUILD. +upstream_hash_sources() { + local package_dir="$1" pkgver="$2" tag="${3:-}" npm_tarball="${4:-}" + local metadata sources work result arch template url file sum sums index=0 + metadata=$(metadata_file_for_dir "$package_dir") + sources=$(jq -c '.upstream.sources' "$metadata") + work=$(mktemp -d) + result=$(jq -n --arg pkgver "$pkgver" '{pkgver: $pkgver, sha256sums: {}}') + + while IFS= read -r arch; do + sums='[]' + while IFS= read -r template; do + if [[ "$template" == file:* ]]; then + file=${template#file:} + if [[ ! "$file" =~ ^[A-Za-z0-9._+-]+$ || ! -f "$package_dir/$file" ]]; then + echo "upstream source names an unsafe or missing local file: '$file'" >&2 + rm -rf "$work" + return 1 + fi + sum=$(sha256sum "$package_dir/$file" | cut -d' ' -f1) + sums=$(jq -c --arg sum "$sum" '. + [$sum]' <<<"$sums") + continue + fi + url=${template//\{pkgver\}/$pkgver} + url=${url//\{tag\}/$tag} + url=${url//\{npm_tarball\}/$npm_tarball} + if [[ ! "$url" =~ ^https://[^[:space:]{}]+$ ]]; then + echo "upstream source template produced an unsafe URL: '$url'" >&2 + rm -rf "$work" + return 1 + fi + file="$work/source-$((index += 1))" + if ! upstream_fetch_source "$url" "$file"; then + echo "could not fetch upstream source: $url" >&2 + rm -rf "$work" + return 1 + fi + sum=$(sha256sum "$file" | cut -d' ' -f1) + sums=$(jq -c --arg sum "$sum" '. + [$sum]' <<<"$sums") + done < <(jq -r --arg arch "$arch" '.[$arch][]' <<<"$sources") + result=$(jq -c --arg arch "$arch" --argjson sums "$sums" '.sha256sums[$arch] = $sums' <<<"$result") + done < <(jq -r 'keys[]' <<<"$sources") + + rm -rf "$work" + printf '%s\n' "$result" +} + +git_tags_upstream_release() { + local package_dir="$1" metadata repo pattern prefix suffix refs + metadata=$(metadata_file_for_dir "$package_dir") + repo=$(jq -r '.upstream.git_tags // ""' "$metadata") + pattern=$(jq -r '.upstream.tag_pattern // ""' "$metadata") + prefix=${pattern%%\{pkgver\}*} + suffix=${pattern#*\{pkgver\}} + + if [[ ! "$repo" =~ ^https://[^[:space:]]+\.git$ || "$pattern" != *'{pkgver}'* || "$suffix" == *'{pkgver}'* ]]; then + echo "invalid git_tags provider configuration" >&2 + return 1 + fi + if ! refs=$(git_tags_fetch_refs "$repo"); then + echo "could not fetch tags from $repo" >&2 + return 1 + fi + + local best_pkgver="" best_tag="" tag candidate + declare -A version_tags=() + while read -r tag; do + tag=${tag%\^\{\}} + [[ "$tag" == "$prefix"*"$suffix" ]] || continue + candidate=${tag#"$prefix"} + [[ -z "$suffix" ]] || candidate=${candidate%"$suffix"} + [[ "$candidate" =~ ^[A-Za-z0-9][A-Za-z0-9._+]*$ ]] || continue + if [[ -n "${version_tags[$candidate]:-}" && "${version_tags[$candidate]}" != "$tag" ]]; then + echo "multiple tags map to pkgver $candidate: ${version_tags[$candidate]} and $tag" >&2 + return 1 + fi + version_tags[$candidate]="$tag" + if [[ -z "$best_pkgver" || $(vercmp "$candidate" "$best_pkgver") -gt 0 ]]; then + best_pkgver="$candidate" + best_tag="$tag" + fi + done < <(sed -n 's#^.*refs/tags/##p' <<<"$refs") + + [[ -n "$best_pkgver" ]] || { echo "no usable tags found at $repo" >&2; return 1; } + local current_pkgver + current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'") + if [[ $(vercmp "$best_pkgver" "$current_pkgver") -le 0 ]]; then + echo '{}' + return 0 + fi + upstream_hash_sources "$package_dir" "$best_pkgver" "$best_tag" +} + +npm_upstream_release() { + local package_dir="$1" metadata package dist_tag npm_metadata pkgver tarball published_at release + metadata=$(metadata_file_for_dir "$package_dir") + package=$(jq -r '.upstream.npm // ""' "$metadata") + dist_tag=$(jq -r '.upstream.dist_tag // "latest"' "$metadata") + if [[ ! "$package" =~ ^(@[a-z0-9_.-]+/)?[a-z0-9_.-]+$ || ! "$dist_tag" =~ ^[a-z0-9_.-]+$ ]]; then + echo "invalid npm provider configuration" >&2 + return 1 + fi + if ! npm_metadata=$(npm_fetch_metadata "$package"); then + echo "could not fetch npm metadata for $package" >&2 + return 1 + fi + pkgver=$(jq -r --arg tag "$dist_tag" '."dist-tags"[$tag] // ""' <<<"$npm_metadata") + tarball=$(jq -r --arg version "$pkgver" '.versions[$version].dist.tarball // ""' <<<"$npm_metadata") + published_at=$(jq -r --arg version "$pkgver" '.time[$version] // ""' <<<"$npm_metadata") + if [[ ! "$pkgver" =~ ^[A-Za-z0-9][A-Za-z0-9._+]*$ || ! "$tarball" =~ ^https://registry\.npmjs\.org/ ]]; then + echo "npm returned an unusable $package release" >&2 + return 1 + fi + + local current_pkgver + current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'") + if [[ $(vercmp "$pkgver" "$current_pkgver") -le 0 ]]; then + echo '{}' + return 0 + fi + release=$(upstream_hash_sources "$package_dir" "$pkgver" "$pkgver" "$tarball") || return 1 + if [[ -n "$published_at" ]]; then + release=$(jq -c --arg published_at "$published_at" '.published_at = $published_at' <<<"$release") + fi + printf '%s\n' "$release" +} + # Emits the newest qualifying release as hook-contract JSON. min_release_age # is honored during selection (newest release older than the window wins, # even when a younger one exists) and BYPASS_MIN_RELEASE_AGE=1 lifts it. diff --git a/pkgbuilds/github-copilot-cli/.omarchy/package.json b/pkgbuilds/github-copilot-cli/.omarchy/package.json index db153c3e..29481cfd 100644 --- a/pkgbuilds/github-copilot-cli/.omarchy/package.json +++ b/pkgbuilds/github-copilot-cli/.omarchy/package.json @@ -1,4 +1,13 @@ { "source": "local", - "release_ring": "fast" + "release_ring": "fast", + "upstream": { + "npm": "@github/copilot", + "sources": { + "any": [ + "{npm_tarball}", + "https://raw.githubusercontent.com/github/copilot-cli/v{pkgver}/changelog.md" + ] + } + } } diff --git a/pkgbuilds/github-copilot-cli/.omarchy/upstream.sh b/pkgbuilds/github-copilot-cli/.omarchy/upstream.sh deleted file mode 100755 index 92db1d25..00000000 --- a/pkgbuilds/github-copilot-cli/.omarchy/upstream.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -# GitHub Copilot CLI is published to npm. Track npm's stable `latest` tag, -# then hash both inputs the PKGBUILD downloads. AUR is deliberately not part -# of this update path: Omarchy owns the multi-architecture recipe. -set -euo pipefail - -REGISTRY_URL="https://registry.npmjs.org/@github%2Fcopilot" -CHANGELOG_BASE="https://raw.githubusercontent.com/github/copilot-cli" - -metadata=$(curl -fsSL "$REGISTRY_URL") -version=$(jq -r '."dist-tags".latest // ""' <<<"$metadata") -tarball=$(jq -r --arg version "$version" '.versions[$version].dist.tarball // ""' <<<"$metadata") - -if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || ! $tarball =~ ^https://registry\.npmjs\.org/ ]]; then - echo "npm returned an unusable Copilot release: version='$version' tarball='$tarball'" >&2 - exit 1 -fi - -current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -if [[ $(vercmp "$version" "$current") -le 0 ]]; then - echo '{}' - exit 0 -fi - -work=$(mktemp -d) -trap 'rm -rf "$work"' EXIT -curl -fsSL -o "$work/copilot.tgz" "$tarball" -curl -fsSL -o "$work/changelog.md" "$CHANGELOG_BASE/v${version}/changelog.md" - -jq -n \ - --arg pkgver "$version" \ - --arg package_sum "$(sha256sum "$work/copilot.tgz" | cut -d' ' -f1)" \ - --arg changelog_sum "$(sha256sum "$work/changelog.md" | cut -d' ' -f1)" \ - '{pkgver: $pkgver, sha256sums: {any: [$package_sum, $changelog_sum]}}' diff --git a/pkgbuilds/qmk-hid/.omarchy/package.json b/pkgbuilds/qmk-hid/.omarchy/package.json index 2a9719d3..aa2fb82a 100644 --- a/pkgbuilds/qmk-hid/.omarchy/package.json +++ b/pkgbuilds/qmk-hid/.omarchy/package.json @@ -1,3 +1,10 @@ { - "source": "local" + "source": "local", + "upstream": { + "git_tags": "https://github.com/FrameworkComputer/qmk_hid.git", + "tag_pattern": "v{pkgver}", + "sources": { + "any": ["https://github.com/FrameworkComputer/qmk_hid/archive/refs/tags/{tag}.tar.gz"] + } + } } diff --git a/pkgbuilds/qmk-hid/.omarchy/upstream.sh b/pkgbuilds/qmk-hid/.omarchy/upstream.sh deleted file mode 100755 index 3cb0167b..00000000 --- a/pkgbuilds/qmk-hid/.omarchy/upstream.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# qmk_hid marks every GitHub Release as a prerelease, including the versions -# it considers current. Track its semver tags instead and hash the tagged -# source archive only when a newer version appears. -set -euo pipefail - -REPO="https://github.com/FrameworkComputer/qmk_hid.git" -best="" -while read -r tag; do - tag=${tag%\^\{\}} - [[ $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue - version=${BASH_REMATCH[1]} - if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then - best=$version - fi -done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') - -[[ -n $best ]] || { echo "No usable qmk_hid version tag found" >&2; exit 1; } -current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -if [[ $(vercmp "$best" "$current") -le 0 ]]; then - echo '{}' - exit 0 -fi - -sum=$(curl -fsSL "https://github.com/FrameworkComputer/qmk_hid/archive/refs/tags/v${best}.tar.gz" | sha256sum | cut -d' ' -f1) -jq -n --arg pkgver "$best" --arg sha256 "$sum" \ - '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/symfony-cli/.omarchy/package.json b/pkgbuilds/symfony-cli/.omarchy/package.json index db153c3e..b9e29500 100644 --- a/pkgbuilds/symfony-cli/.omarchy/package.json +++ b/pkgbuilds/symfony-cli/.omarchy/package.json @@ -1,4 +1,11 @@ { "source": "local", - "release_ring": "fast" + "release_ring": "fast", + "upstream": { + "github": "symfony-cli/symfony-cli", + "checksums": "checksums.txt", + "assets": { + "any": "symfony-cli-{pkgver}.tar.gz" + } + } } diff --git a/pkgbuilds/symfony-cli/.omarchy/upstream.sh b/pkgbuilds/symfony-cli/.omarchy/upstream.sh deleted file mode 100755 index c68049af..00000000 --- a/pkgbuilds/symfony-cli/.omarchy/upstream.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# Symfony publishes a checksum manifest with every stable GitHub release. -# The `latest` URL is its vendor-maintained stable feed, so no GitHub API or -# AUR state is involved in selecting and verifying the source tarball. -set -euo pipefail - -CHECKSUMS_URL="https://github.com/symfony-cli/symfony-cli/releases/latest/download/checksums.txt" -checksums=$(curl -fsSL "$CHECKSUMS_URL") - -best="" -best_sum="" -while read -r sum filename; do - [[ $filename =~ ^symfony-cli-([0-9]+\.[0-9]+\.[0-9]+)\.tar\.gz$ ]] || continue - version=${BASH_REMATCH[1]} - [[ $sum =~ ^[0-9a-f]{64}$ ]] || continue - if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then - best=$version - best_sum=$sum - fi -done <<<"$checksums" - -[[ -n $best ]] || { echo "Symfony's latest checksum manifest names no source tarball" >&2; exit 1; } -current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -if [[ $(vercmp "$best" "$current") -le 0 ]]; then - echo '{}' - exit 0 -fi - -jq -n --arg pkgver "$best" --arg sha256 "$best_sum" \ - '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/tensaku/.omarchy/package.json b/pkgbuilds/tensaku/.omarchy/package.json index 2a9719d3..eb35b0ba 100644 --- a/pkgbuilds/tensaku/.omarchy/package.json +++ b/pkgbuilds/tensaku/.omarchy/package.json @@ -1,3 +1,10 @@ { - "source": "local" + "source": "local", + "upstream": { + "git_tags": "https://github.com/jondkinney/tensaku.git", + "tag_pattern": "v{pkgver}", + "sources": { + "any": ["https://github.com/jondkinney/tensaku/archive/refs/tags/{tag}.tar.gz"] + } + } } diff --git a/pkgbuilds/tensaku/.omarchy/upstream.sh b/pkgbuilds/tensaku/.omarchy/upstream.sh deleted file mode 100755 index cf5d9be7..00000000 --- a/pkgbuilds/tensaku/.omarchy/upstream.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# Tensaku publishes semver tags but no source checksum manifest. Select the -# newest tag using pacman's ordering and hash its source archive. -set -euo pipefail - -REPO="https://github.com/jondkinney/tensaku.git" -version="" -while read -r tag; do - tag=${tag%\^\{\}} - [[ $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue - candidate=${BASH_REMATCH[1]} - if [[ -z $version || $(vercmp "$candidate" "$version") -gt 0 ]]; then - version=$candidate - fi -done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') - -[[ -n $version ]] || { echo "No usable Tensaku version tag found" >&2; exit 1; } -current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -if [[ $(vercmp "$version" "$current") -le 0 ]]; then - echo '{}' - exit 0 -fi - -sum=$(curl -fsSL "https://github.com/jondkinney/tensaku/archive/refs/tags/v${version}.tar.gz" | sha256sum | cut -d' ' -f1) -jq -n --arg pkgver "$version" --arg sha256 "$sum" \ - '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/tzupdate/.omarchy/package.json b/pkgbuilds/tzupdate/.omarchy/package.json index 2a9719d3..31bfd105 100644 --- a/pkgbuilds/tzupdate/.omarchy/package.json +++ b/pkgbuilds/tzupdate/.omarchy/package.json @@ -1,3 +1,10 @@ { - "source": "local" + "source": "local", + "upstream": { + "git_tags": "https://github.com/cdown/tzupdate.git", + "tag_pattern": "{pkgver}", + "sources": { + "any": ["https://github.com/cdown/tzupdate/archive/refs/tags/{tag}.tar.gz"] + } + } } diff --git a/pkgbuilds/tzupdate/.omarchy/upstream.sh b/pkgbuilds/tzupdate/.omarchy/upstream.sh deleted file mode 100755 index 3ccaf442..00000000 --- a/pkgbuilds/tzupdate/.omarchy/upstream.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# tzupdate publishes version tags but no GitHub Releases. Track its semver -# tags and hash the tagged source archive when a new version appears. -set -euo pipefail - -REPO="https://github.com/cdown/tzupdate.git" -best="" -while read -r tag; do - tag=${tag%\^\{\}} - [[ $tag =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue - version=${BASH_REMATCH[1]} - if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then - best=$version - fi -done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') - -[[ -n $best ]] || { echo "No usable tzupdate version tag found" >&2; exit 1; } -current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -if [[ $(vercmp "$best" "$current") -le 0 ]]; then - echo '{}' - exit 0 -fi - -sum=$(curl -fsSL "https://github.com/cdown/tzupdate/archive/refs/tags/${best}.tar.gz" | sha256sum | cut -d' ' -f1) -jq -n --arg pkgver "$best" --arg sha256 "$sum" \ - '{pkgver: $pkgver, sha256sums: {any: [$sha256]}}' diff --git a/pkgbuilds/v4l2-relayd/.omarchy/package.json b/pkgbuilds/v4l2-relayd/.omarchy/package.json index 2a9719d3..daea8850 100644 --- a/pkgbuilds/v4l2-relayd/.omarchy/package.json +++ b/pkgbuilds/v4l2-relayd/.omarchy/package.json @@ -1,3 +1,14 @@ { - "source": "local" + "source": "local", + "upstream": { + "git_tags": "https://gitlab.com/vicamo/v4l2-relayd.git", + "tag_pattern": "upstream/{pkgver}", + "sources": { + "any": [ + "https://gitlab.com/vicamo/v4l2-relayd/-/archive/upstream/{pkgver}/v4l2-relayd-upstream-{pkgver}.tar.gz", + "file:0001-reset-output-on-idle.patch", + "file:0002-escape-optional-splashsrc-expansion.patch" + ] + } + } } diff --git a/pkgbuilds/v4l2-relayd/.omarchy/upstream.sh b/pkgbuilds/v4l2-relayd/.omarchy/upstream.sh deleted file mode 100755 index 4899ac21..00000000 --- a/pkgbuilds/v4l2-relayd/.omarchy/upstream.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# v4l2-relayd uses GitLab tags named upstream/. Keep Omarchy's local -# patches and follow that authoritative tag stream directly. -set -euo pipefail - -REPO="https://gitlab.com/vicamo/v4l2-relayd.git" -best="" -while read -r tag; do - tag=${tag%\^\{\}} - [[ $tag =~ ^upstream/([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue - version=${BASH_REMATCH[1]} - if [[ -z $best || $(vercmp "$version" "$best") -gt 0 ]]; then - best=$version - fi -done < <(git ls-remote --tags "$REPO" | sed 's#^.*refs/tags/##') - -[[ -n $best ]] || { echo "No usable v4l2-relayd upstream tag found" >&2; exit 1; } -current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -if [[ $(vercmp "$best" "$current") -le 0 ]]; then - echo '{}' - exit 0 -fi - -sum=$(curl -fsSL "https://gitlab.com/vicamo/v4l2-relayd/-/archive/upstream/${best}/v4l2-relayd-upstream-${best}.tar.gz" | sha256sum | cut -d' ' -f1) -jq -n --arg pkgver "$best" --arg sha256 "$sum" \ - '{pkgver: $pkgver, sha256sums: {any: [$sha256, "SKIP", "SKIP"]}}' diff --git a/pkgbuilds/v4l2-relayd/PKGBUILD b/pkgbuilds/v4l2-relayd/PKGBUILD index f4784e4b..fbe9bbb9 100644 --- a/pkgbuilds/v4l2-relayd/PKGBUILD +++ b/pkgbuilds/v4l2-relayd/PKGBUILD @@ -19,8 +19,8 @@ source=("https://gitlab.com/vicamo/v4l2-relayd/-/archive/upstream/${pkgver}/v4l2 "0001-reset-output-on-idle.patch" "0002-escape-optional-splashsrc-expansion.patch") sha256sums=('0c063edf18dcc6edcdef46e695128cfc2b2d60964ea8538c7e79a2454310c53d' - 'SKIP' - 'SKIP') + '07722a8708ced48d2db9575f3eae5b1266868d259d260635e06e9a10baddec78' + '3cb89056af276eed7a45dfc96435e8e48558e4e11c96560360d46d631b1c0b6c') prepare() { cd "$srcdir/${pkgname}-upstream-${pkgver}" From 55bc67834b3c96871eb1f81b8205627356b40f40 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Thu, 3 Sep 2026 22:52:22 -0500 Subject: [PATCH 19/63] perplexity: stop shipping upstream's /lib, fail closed on strays 26.9.1 added /lib/systemd/system/perplexity-local-runtime-setup.service to the deb, and wholesale extraction made the package own /lib -- a symlink owned by filesystem -- so pacman refused every install and upgrade. The unit could never work here anyway: its setup script apt-installs Docker and the NVIDIA Container Toolkit and exits on any distro but Ubuntu, so the Arch equivalents ride optdepends instead. package() now allowlists what leaves the deb: opt/, usr/, and that one known unit path (deleted). Anything else stops the build rather than shipping the next filesystem conflict. Verified in a clean container: the published -1 reproduces the /lib conflict; -2 installs fresh and upgrades from 26.8.4 cleanly. --- pkgbuilds/perplexity/PKGBUILD | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pkgbuilds/perplexity/PKGBUILD b/pkgbuilds/perplexity/PKGBUILD index e7fe1d19..42a496e6 100644 --- a/pkgbuilds/perplexity/PKGBUILD +++ b/pkgbuilds/perplexity/PKGBUILD @@ -6,7 +6,7 @@ pkgname=perplexity pkgver=26.9.1+build61614 -pkgrel=1 +pkgrel=2 pkgdesc="Official Perplexity desktop app" arch=('x86_64' 'aarch64') url="https://www.perplexity.ai" @@ -56,6 +56,8 @@ depends=( optdepends=( 'apparmor: confine the app with the bundled user-namespace profile' 'libappindicator-gtk3: tray icon support' + 'docker: containerized local GPU runtime for on-device models' + 'nvidia-container-toolkit: containerized local GPU runtime for on-device models' ) makedepends=('libarchive') @@ -91,6 +93,23 @@ package() { bsdtar -xOf "${deb}" data.tar.xz | bsdtar --no-same-owner -xf - -C "${pkgdir}" + # 26.9.1 started shipping a systemd unit under /lib, and owning /lib -- a + # symlink owned by the filesystem package -- makes pacman refuse the whole + # transaction. The unit is also a no-op here: + # its setup script apt-installs Docker and the NVIDIA Container Toolkit and + # exits on any distro but Ubuntu (install those yourself; see optdepends). + # Delete it, but only it: anything else arriving outside the trees this + # PKGBUILD expects stops the build rather than shipping the next conflict. + local unexpected + unexpected=$(cd "${pkgdir}" && find . \( -type f -o -type l \) | grep -vE \ + '^\./(opt|usr)/|^\./lib/systemd/system/perplexity-local-runtime-setup\.service$' || true) + if [[ -n "${unexpected}" ]]; then + echo "Unexpected files outside opt/ and usr/ in the upstream deb:" >&2 + echo "${unexpected}" >&2 + return 1 + fi + rm -rf "${pkgdir}/lib" + # pacman never runs the deb's postinst, so the /usr/bin entry it would have # symlinked is a launcher here instead, and the menu entry goes through it so # the flags file applies there too. The scheme handler stays upstream's. From f620e9ee6bebe0b025b67156c764d51db60e4a97 Mon Sep 17 00:00:00 2001 From: Jessyka_boat <158219213+Onefailatatime@users.noreply.github.com> Date: Fri, 4 Sep 2026 03:29:12 -0700 Subject: [PATCH 20/63] Add slap-notes-bin Local-first block notes with a wiki-link graph and a built-in AI research agent. Electron over a Next.js server, x86_64 only, repackaged from the vendor tarball with a Wayland launcher. Not in the AUR. The package follows its GitHub release feed through the declarative github upstream provider, reading each release's SHA256SUMS, with a 24h min_release_age quarantine. Co-Authored-By: Claude Opus 5 Co-Authored-By: Claude Fable 5.1 --- .../slap-notes-bin/.omarchy/package.json | 12 +++ pkgbuilds/slap-notes-bin/PKGBUILD | 82 +++++++++++++++++++ .../slap-notes-bin/slap-notes-bin.install | 3 + .../slap-notes-bin/slap-notes-launcher.sh | 32 ++++++++ 4 files changed, 129 insertions(+) create mode 100644 pkgbuilds/slap-notes-bin/.omarchy/package.json create mode 100644 pkgbuilds/slap-notes-bin/PKGBUILD create mode 100755 pkgbuilds/slap-notes-bin/slap-notes-bin.install create mode 100755 pkgbuilds/slap-notes-bin/slap-notes-launcher.sh diff --git a/pkgbuilds/slap-notes-bin/.omarchy/package.json b/pkgbuilds/slap-notes-bin/.omarchy/package.json new file mode 100644 index 00000000..a77bdb3a --- /dev/null +++ b/pkgbuilds/slap-notes-bin/.omarchy/package.json @@ -0,0 +1,12 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h", + "upstream": { + "github": "Onefailatatime/slap-notes", + "checksums": "SHA256SUMS", + "assets": { + "x86_64": "slap-notes-{tag}-linux-x64.tar.zst" + } + } +} diff --git a/pkgbuilds/slap-notes-bin/PKGBUILD b/pkgbuilds/slap-notes-bin/PKGBUILD new file mode 100644 index 00000000..b19a7e72 --- /dev/null +++ b/pkgbuilds/slap-notes-bin/PKGBUILD @@ -0,0 +1,82 @@ +# Maintainer: FunnelFix + +# Slap Notes publishes a plain Electron tree rather than an AppImage, so this +# repackages the vendor tarball as-is. .omarchy/upstream.sh rewrites the version +# and checksum below from the release feed the app updates itself from. + +pkgname=slap-notes-bin +_appname=slap-notes +pkgver=0.2.3 +pkgrel=1 +pkgdesc="Local-first block notes with a wiki-link graph and a built-in AI research agent" +arch=('x86_64') +url="https://slapnotes.com" +license=('LicenseRef-proprietary') +depends=( + 'alsa-lib' + 'at-spi2-core' + 'cairo' + 'dbus' + 'expat' + 'gcc-libs' + 'glib2' + 'glibc' + 'gtk3' + 'hicolor-icon-theme' + 'libcups' + 'libdrm' + # Both are opened by name at runtime rather than linked, so nothing else in + # this list pulls them in: libnotify carries desktop notifications and + # libsecret backs Electron's encrypted storage. + 'libnotify' + 'libsecret' + 'libx11' + 'libxcb' + 'libxcomposite' + 'libxdamage' + 'libxext' + 'libxfixes' + 'libxkbcommon' + 'libxrandr' + 'mesa' + 'nspr' + 'nss' + 'pango' + 'systemd-libs' + 'xdg-utils' +) +provides=("${_appname}=${pkgver}") +conflicts=("${_appname}") +# Electron ships prebuilt binaries: stripping them breaks the bundle. +options=('!strip' '!debug') +install="${pkgname}.install" +_tarball="${_appname}-${pkgver}-linux-x64.tar.zst" +source=("${_appname}-launcher.sh") +source_x86_64=("${pkgname}-${pkgver}.tar.zst::https://github.com/Onefailatatime/slap-notes/releases/download/${pkgver}/${_tarball}") +sha256sums=('9d208c06717906b74c41874bdd3fdba0f0dc5b31d239ae00d19472e6eab2ea29') +sha256sums_x86_64=('56cb7606f41051ba10d088ed8e1ae85a17b20692f4b45c0c6a6e6cec6669a5e2') + +package() { + cd "${srcdir}/${_appname}-${pkgver}" + + install -dm755 "${pkgdir}/opt/${_appname}" + cp -a app/. "${pkgdir}/opt/${_appname}/" + chmod -R a+rX "${pkgdir}/opt/${_appname}" + + install -Dm755 "${srcdir}/${_appname}-launcher.sh" "${pkgdir}/usr/bin/${_appname}" + + # Upstream's entry points straight into /opt, which would step around the + # launcher and hand every desktop-started window to XWayland. + sed -e "s|^Exec=.*|Exec=${_appname} %U|" "${_appname}.desktop" \ + > "${srcdir}/${_appname}.desktop.arch" + install -Dm644 "${srcdir}/${_appname}.desktop.arch" \ + "${pkgdir}/usr/share/applications/${_appname}.desktop" + + local size + for size in 16 32 48 64 128 256 512 1024; do + install -Dm644 "icons/${size}.png" \ + "${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/${_appname}.png" + done + + install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" +} diff --git a/pkgbuilds/slap-notes-bin/slap-notes-bin.install b/pkgbuilds/slap-notes-bin/slap-notes-bin.install new file mode 100755 index 00000000..77fb3b20 --- /dev/null +++ b/pkgbuilds/slap-notes-bin/slap-notes-bin.install @@ -0,0 +1,3 @@ +post_install() { + echo "Extra Chromium flags go in ~/.config/slap-notes-flags.conf, one per line." +} diff --git a/pkgbuilds/slap-notes-bin/slap-notes-launcher.sh b/pkgbuilds/slap-notes-bin/slap-notes-launcher.sh new file mode 100755 index 00000000..ade5c08b --- /dev/null +++ b/pkgbuilds/slap-notes-bin/slap-notes-launcher.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -euo pipefail + +user_flags=() +config_home="${XDG_CONFIG_HOME:-}" +[[ -n "$config_home" || -z "${HOME:-}" ]] || config_home="$HOME/.config" +flags_file="${config_home:+$config_home/slap-notes-flags.conf}" + +if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then + while IFS= read -r line || [[ -n "$line" ]]; do + line="${line%%#*}" + [[ -n "${line//[[:space:]]/}" ]] || continue + read -r -a flags <<<"$line" + user_flags+=("${flags[@]}") + done <"$flags_file" +fi + +# Chromium's own Ozone detection falls back to XWayland often enough to matter, +# and the result is a blurry window on every scaled display. Ask for Wayland +# directly, unless the user has already picked a platform themselves. +platform_flags=() +if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then + platform_flags=(--ozone-platform=wayland) + + for flag in "${user_flags[@]}" "$@"; do + case "$flag" in + --ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; + esac + done +fi + +exec /opt/slap-notes/slap-notes "${platform_flags[@]}" "${user_flags[@]}" "$@" From 99234a4fbb61b46225b2e9e560e114fbfebe8a95 Mon Sep 17 00:00:00 2001 From: Astrid Gealer Date: Fri, 4 Sep 2026 12:09:12 +0100 Subject: [PATCH 21/63] Add schist-bin, the Schist image editor, to the fast ring (#293) Schist is a layered image editor with PSD, Affinity and camera raw support, developed by Infrawrench and packaged by its upstream author. The package re-wraps the pacman-format payloads Schist's release workflow publishes for x86_64 and aarch64, so the builder does no compiling, and both assets are pinned by SHA-256. Releases are tracked declaratively through the GitHub upstream provider, which gains a "digests": true mode here: a vendor that publishes no checksum manifest can have each asset's SHA-256 read from the digest GitHub's release API reports, so the sync never downloads the artifacts. Exactly one of "checksums" or "digests" must be set, and the provider enforces that itself because scheduled runs reach it without the metadata validator. Fresh releases wait 24 hours before the scheduled sync picks them up, as mise-bin already does. vulkan-driver is an optional dependency rather than a hard one: makepkg -s would otherwise satisfy the virtual package with nvidia-utils in the build container, and Omarchy installs a Vulkan driver per machine. Co-authored-by: David Heinemeier Hansson Co-authored-by: Claude Fable 5.1 --- README.md | 12 ++- bin/sync-upstream | 99 +++++++++++++++++++++- helpers/package-metadata.sh | 5 +- helpers/upstream-github.sh | 59 +++++++++---- pkgbuilds/schist-bin/.omarchy/package.json | 13 +++ pkgbuilds/schist-bin/PKGBUILD | 44 ++++++++++ 6 files changed, 208 insertions(+), 24 deletions(-) create mode 100644 pkgbuilds/schist-bin/.omarchy/package.json create mode 100644 pkgbuilds/schist-bin/PKGBUILD diff --git a/README.md b/README.md index 010adba3..15f55e17 100644 --- a/README.md +++ b/README.md @@ -302,8 +302,8 @@ Some vendors publish a release feed of their own that is faster and more precise than the AUR packaging of it. Those packages are `source: local` — Omarchy owns the PKGBUILD — and declare where releases come from in one of two ways. -A vendor shipping tagged GitHub releases with a checksum manifest asset is pure -data, declared as `upstream` in `.omarchy/package.json` with no code at all: +A vendor shipping tagged GitHub releases is pure data, declared as `upstream` +in `.omarchy/package.json` with no code at all: ```json "upstream": { @@ -316,6 +316,12 @@ data, declared as `upstream` in `.omarchy/package.json` with no code at all: } ``` +`checksums` names the manifest asset the vendor publishes. A vendor publishing +none sets `"digests": true` instead, and the checksums come from the SHA-256 +digest GitHub's release API reports for every asset — see +`pkgbuilds/schist-bin/.omarchy/package.json`. Either way the artifacts +themselves are never downloaded. + `{tag}` and `{pkgver}` interpolate into asset names; a leading `v` on the tag is stripped for `pkgver`; drafts and prereleases are ignored. Only the 100 most recent releases are considered. The provider fails closed on anything it cannot @@ -626,7 +632,7 @@ Minimal examples: Fields: - `source`: `aur` or `local`. A `local` package can still follow an upstream release, either declaratively via `upstream` or with an `.omarchy/upstream.sh` hook. -- `upstream`: optional for `local` packages whose vendor ships tagged GitHub releases with a checksum manifest asset. `{ "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "": "name-{tag}.tar.xz" } }` — see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`. +- `upstream`: optional for `local` packages whose vendor ships tagged GitHub releases. `{ "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "": "name-{tag}.tar.xz" } }`, or `"digests": true` in place of `checksums` to use the release API's per-asset digests — see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`. - `min_release_age`: optional quarantine for upstream releases (`"24h"`, `"2d"`, or bare seconds). The newest release older than the window ships; anything younger waits, and a release whose age cannot be proven fails the sync. Bypass deliberately with `BYPASS_MIN_RELEASE_AGE=1 bin/sync-upstream `. - `sync`: optional for AUR packages; defaults to `true`. Set `false` for AUR-origin packages that Omarchy maintains manually. - `aur`: optional AUR package name when it differs from the local package directory, usually for split packages. diff --git a/bin/sync-upstream b/bin/sync-upstream index d05d5f88..04c7ac62 100755 --- a/bin/sync-upstream +++ b/bin/sync-upstream @@ -19,8 +19,8 @@ Usage: $0 [PACKAGE...] Update packages that track an upstream vendor release feed instead of the AUR. Packages opt in declaratively through "upstream" in .omarchy/package.json. -Providers cover GitHub Releases with checksum manifests, semver-shaped git -tags whose source URLs can be hashed, and npm dist-tags. See README.md for the +Providers cover GitHub Releases with checksum manifests or API asset digests, +semver-shaped git tags whose source URLs can be hashed, and npm dist-tags. See README.md for the schemas. Anything outside those conventions may provide pkgbuilds//.omarchy/upstream.sh, a hook that reports JSON on stdout: @@ -466,8 +466,8 @@ sync_package() { # paths. Covers release selection (fallback past quarantined releases, # draft/prerelease filtering, bypass, unchanged version), failure paths # (unusable tags/timestamps, missing checksums), checksum template mapping -# for both architectures, the min_release_age backstop, the duration parser, -# and manifest validation. +# for both architectures, release API digests, the min_release_age backstop, +# the duration parser, and manifest validation. cmd_self_test() { local failures=0 @@ -567,6 +567,76 @@ EOF rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$? check "missing aarch64 checksum fails the sync" "1" "$rc" + # A vendor publishing no manifest: checksums come from the digests the + # release API reports per asset, with nothing fetched beyond the feed. + echo "Release API digests:" + local digpkg="$TEMP_DIR/selftest-digests" + mkdir -p "$digpkg/.omarchy" + printf 'pkgver=1.0.0\npkgrel=1\n' > "$digpkg/PKGBUILD" + cat > "$digpkg/.omarchy/package.json" <<'EOF' +{ + "source": "local", + "upstream": { + "github": "example/tool", + "digests": true, + "assets": { + "x86_64": "tool-{pkgver}-1-x86_64.pkg.tar.zst", + "aarch64": "tool-{pkgver}-1-aarch64.pkg.tar.zst" + } + } +} +EOF + FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" --arg a "$sum_a19" '[ + {tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [ + {name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: ("sha256:" + $x)}, + {name: "tool-1.9.0-1-aarch64.pkg.tar.zst", digest: ("sha256:" + $a)}, + {name: "tool-1.9.0-1-x86_64.rpm", digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000"} + ]} + ]') + FIXTURE_CHECKSUMS="manifest must not be consulted" + out=$(github_upstream_release "$digpkg" 0 2>/dev/null) || out="" + check "x86_64 checksum via the asset digest" "$sum_x19" "$(jq -r '.sha256sums.x86_64[0] // ""' <<<"$out")" + check "aarch64 checksum via the asset digest" "$sum_a19" "$(jq -r '.sha256sums.aarch64[0] // ""' <<<"$out")" + + FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" '[ + {tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [ + {name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: ("sha256:" + $x)}, + {name: "tool-1.9.0-1-aarch64.pkg.tar.zst"} + ]} + ]') + rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$? + check "asset without a digest fails the sync" "1" "$rc" + + FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" '[ + {tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [ + {name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: ("sha256:" + $x)}, + {name: "tool-1.9.0-2-aarch64.pkg.tar.zst", digest: ("sha256:" + $x)} + ]} + ]') + rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$? + check "asset re-cut under another release number fails the sync" "1" "$rc" + + FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" --arg a "$sum_a19" '[ + {tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [ + {name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: $x}, + {name: "tool-1.9.0-1-aarch64.pkg.tar.zst", digest: ("sha256:" + $a)} + ]} + ]') + rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$? + check "digest without the sha256: prefix fails the sync" "1" "$rc" + + # The provider enforces the declaration shape itself: scheduled runs reach + # it without validate_package_metadata. + jq '.upstream.checksums = "SHASUMS256.txt"' "$digpkg/.omarchy/package.json" > "$digpkg/both.json" + cp "$digpkg/.omarchy/package.json" "$digpkg/good.json" + cp "$digpkg/both.json" "$digpkg/.omarchy/package.json" + rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$? + check "provider rejects checksums and digests together" "1" "$rc" + jq '.upstream.digests = "true"' "$digpkg/good.json" > "$digpkg/.omarchy/package.json" + rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$? + check "provider rejects a non-boolean digests" "1" "$rc" + cp "$digpkg/good.json" "$digpkg/.omarchy/package.json" + echo "Quarantine backstop:" local rel st rel=$(jq -n --arg p "$old2d" '{pkgver: "1.9.0", published_at: $p, sha256sums: {}}') @@ -619,6 +689,27 @@ EOF echo '{"source": "local", "upstream": {"github": "example/tool"}}' > "$agepkg/.omarchy/package.json" vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? check "upstream without checksums/assets is rejected" "1" "$vst" + echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": "SHASUMS256.txt", "digests": true, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "upstream with both checksums and digests is rejected" "1" "$vst" + echo '{"source": "local", "upstream": {"github": "example/tool", "digests": "yes", "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "non-boolean digests is rejected" "1" "$vst" + echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": false, "digests": true, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "checksums: false alongside digests is rejected" "1" "$vst" + echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": "SUMS", "digests": null, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "digests: null is rejected" "1" "$vst" + echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": "SUMS", "digests": false, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "digests: false beside a checksums manifest is accepted" "0" "$vst" + echo '{"source": "local", "upstream": {"github": "example/tool", "digests": false, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "digests: false alone is rejected" "1" "$vst" + cp "$digpkg/.omarchy/package.json" "$agepkg/.omarchy/package.json" + vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? + check "the digests declaration shape is accepted" "0" "$vst" echo '{"source":"local","upstream":{"github":"example/tool","git_tags":"https://example/tool.git","checksums":"sums","assets":{"any":"tool"}}}' > "$agepkg/.omarchy/package.json" vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? check "multiple provider types are rejected" "1" "$vst" diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index b14c3da9..90961030 100644 --- a/helpers/package-metadata.sh +++ b/helpers/package-metadata.sh @@ -16,6 +16,7 @@ # { "source": "local", "channels": ["edge", "rc", "stable"] } # { "source": "local", "min_release_age": "24h" } # { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } } +# { "source": "local", "upstream": { "github": "owner/repo", "digests": true, "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } } # { "source": "local", "upstream": { "git_tags": "https://example/repo.git", "tag_pattern": "v{pkgver}", "sources": { "any": ["https://example/archive/{tag}.tar.gz"] } } } # { "source": "local", "upstream": { "npm": "@scope/package", "sources": { "any": ["{npm_tarball}"] } } } # @@ -459,7 +460,9 @@ validate_package_metadata() { ([has("github"), has("git_tags"), has("npm")] | map(select(.)) | length) == 1 and if has("github") then (.github | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z")) - and (.checksums | type == "string" and length > 0) + and (if has("checksums") then (.checksums | type == "string" and length > 0) else true end) + and (if has("digests") then (.digests | type == "boolean") else true end) + and (has("checksums") != (has("digests") and .digests == true)) and (.assets | type == "object" and length > 0 and (to_entries | all( (.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0) ))) diff --git a/helpers/upstream-github.sh b/helpers/upstream-github.sh index 14b14461..1b6e64ce 100644 --- a/helpers/upstream-github.sh +++ b/helpers/upstream-github.sh @@ -1,8 +1,7 @@ # Declarative upstream providers for bin/sync-upstream. # -# A package whose upstream ships tagged GitHub releases with a checksum -# manifest asset needs no upstream.sh hook: the whole feed is data, declared -# in .omarchy/package.json -- +# A package whose upstream ships tagged GitHub releases needs no upstream.sh +# hook: the whole feed is data, declared in .omarchy/package.json -- # # "upstream": { # "github": "jdx/mise", @@ -13,12 +12,16 @@ # } # } # +# "checksums" names the vendor's manifest asset. A vendor publishing none can +# set "digests": true instead, which reads the SHA-256 digest GitHub's release +# API reports for every asset, so the sync never downloads the artifacts. +# # {tag} and {pkgver} interpolate into asset names; tags may carry a leading # "v", which is stripped for pkgver. Drafts and prereleases are ignored. The # provider emits the same JSON contract as an upstream.sh hook, so # bin/sync-upstream's validation and min_release_age backstop apply -# unchanged. Git-tag and npm providers below cover projects without a release -# checksum manifest; a feed that fits no convention keeps a bespoke hook. +# unchanged. Git-tag and npm providers below cover projects without GitHub +# releases; a feed that fits no convention keeps a bespoke hook. # Return the single declarative provider selected by a package. An empty # result means either no provider or an invalid/ambiguous declaration; the @@ -203,7 +206,7 @@ npm_upstream_release() { # not silently choose from. github_upstream_release() { local package_dir="$1" min_age="${2:-0}" - local metadata repo checksums_name + local metadata repo checksums_name use_digests metadata=$(metadata_file_for_dir "$package_dir") repo=$(jq -r '(.upstream? | objects | .github) // ""' "$metadata") @@ -211,9 +214,20 @@ github_upstream_release() { echo "invalid upstream.github repository: '${repo:-}'" >&2 return 1 fi - checksums_name=$(jq -r '(.upstream? | objects | .checksums) // ""' "$metadata") - if [[ -z "$checksums_name" ]]; then - echo "upstream.checksums names the checksum manifest asset and is required" >&2 + # Enforced here as well as in validate_package_metadata: the scheduled sync + # reaches this provider without running the validator first. + checksums_name=$(jq -r '(.upstream? | objects | .checksums) | strings' "$metadata") + use_digests=$(jq -r '(.upstream? | objects | .digests) | if . == null then "false" elif type == "boolean" then tostring else "invalid" end' "$metadata") + if [[ "$use_digests" == "invalid" ]]; then + echo "upstream.digests must be true or false" >&2 + return 1 + fi + if [[ -n "$checksums_name" && "$use_digests" == "true" ]]; then + echo "upstream sets both checksums and digests; keep exactly one" >&2 + return 1 + fi + if [[ -z "$checksums_name" && "$use_digests" != "true" ]]; then + echo "upstream needs either checksums (a manifest asset name) or digests: true" >&2 return 1 fi local arches @@ -281,15 +295,16 @@ github_upstream_release() { return 0 fi - local checksums - if ! checksums=$(github_fetch_checksums "$repo" "$best_tag" "$checksums_name"); then + local checksums="" + if [[ "$use_digests" != "true" ]] \ + && ! checksums=$(github_fetch_checksums "$repo" "$best_tag" "$checksums_name"); then echo "could not fetch $checksums_name for $repo $best_tag" >&2 return 1 fi local jq_args=(--arg pkgver "$best_pkgver" --arg published_at "$best_published_at") local jq_filter='{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}' - local arch template filename checksum + local arch template filename checksum checksum_source for arch in "${arches[@]}"; do if [[ ! "$arch" =~ ^[a-z0-9_]+$ ]]; then echo "invalid architecture key in upstream.assets: '$arch'" >&2 @@ -298,11 +313,23 @@ github_upstream_release() { template=$(jq -r --arg arch "$arch" '.upstream.assets[$arch]' "$metadata") filename=${template//\{pkgver\}/$best_pkgver} filename=${filename//\{tag\}/$best_tag} - # Manifest lines are " ", with the name sometimes prefixed - # "./" (sha256sum of a local path) or "*" (binary-mode marker). - checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums") + if [[ "$use_digests" == "true" ]]; then + # Only a "sha256:" digest is stripped to its hex; any other shape + # falls through empty and fails the check below. + checksum=$(jq -r --arg tag "$best_tag" --arg name "$filename" ' + first(.[] | select(.tag_name == $tag)) | (.assets // [])[] + | select(.name == $name) | (.digest // "") + | if type == "string" and test("\\Asha256:[0-9a-f]{64}\\z") then ltrimstr("sha256:") else "" end + ' <<<"$releases") + checksum_source="the release API digest" + else + # Manifest lines are " ", with the name sometimes prefixed + # "./" (sha256sum of a local path) or "*" (binary-mode marker). + checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums") + checksum_source="$checksums_name" + fi if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then - echo "no valid checksum for $filename in $repo $best_tag $checksums_name" >&2 + echo "no valid checksum for $filename in $repo $best_tag $checksum_source" >&2 return 1 fi jq_args+=(--arg "sum_$arch" "$checksum") diff --git a/pkgbuilds/schist-bin/.omarchy/package.json b/pkgbuilds/schist-bin/.omarchy/package.json new file mode 100644 index 00000000..8d64039c --- /dev/null +++ b/pkgbuilds/schist-bin/.omarchy/package.json @@ -0,0 +1,13 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h", + "upstream": { + "github": "Infrawrench/schist", + "digests": true, + "assets": { + "x86_64": "schist-{pkgver}-1-x86_64.pkg.tar.zst", + "aarch64": "schist-{pkgver}-1-aarch64.pkg.tar.zst" + } + } +} diff --git a/pkgbuilds/schist-bin/PKGBUILD b/pkgbuilds/schist-bin/PKGBUILD new file mode 100644 index 00000000..a67a1191 --- /dev/null +++ b/pkgbuilds/schist-bin/PKGBUILD @@ -0,0 +1,44 @@ +# Maintainer: Infrawrench LLC +pkgname=schist-bin +pkgver=0.11.0 +pkgrel=1 +# Upstream's own package release, embedded in the asset name. It is +# packages.sh's "release=" and only moves when the packaging changes under +# a version that has already shipped; pkgrel above is this package's own. +# The asset templates in .omarchy/package.json carry the same number. +_relver=1 +pkgdesc="Layered image editor with PSD and Affinity support (binary release)" +arch=(x86_64 aarch64) +url="https://github.com/Infrawrench/schist" +license=(MIT) +# The list the payload's own .PKGINFO carries: fontconfig/wayland/ +# vulkan-icd-loader are dlopen'd, so namcap flags them "may not be needed" +# — they are. +depends=(fontconfig freetype2 hicolor-icon-theme libxcb libxkbcommon + libxkbcommon-x11 vulkan-icd-loader wayland) +# A Vulkan ICD is needed to draw, but as a hard dependency makepkg -s would +# resolve it to nvidia-utils in the build container; Omarchy installs the right +# driver per machine. libheif is dlopen'd; everything else opens without it. +optdepends=('vulkan-driver: GPU rendering (any Vulkan ICD, vulkan-swrast in software)' + 'libheif: HEIC import') +provides=(schist) +conflicts=(schist) +# The released binary ships byte-exact: it is already stripped by the +# release workflow, and its build id is what Sentry symbolication matches. +options=(!strip !debug) +# The release asset is already a pacman-format payload, assembled by +# packaging/linux/packages.sh from the CI build — this just re-wraps its +# usr/ tree, so the dependency lists here have to stay in step with that +# script. +source_x86_64=("$url/releases/download/v$pkgver/schist-$pkgver-$_relver-x86_64.pkg.tar.zst") +source_aarch64=("$url/releases/download/v$pkgver/schist-$pkgver-$_relver-aarch64.pkg.tar.zst") +sha256sums_x86_64=('0fb0a13e579a9bd74880bf49dd69690cc6a5f830db3423f1e448fef516c1bc5f') +sha256sums_aarch64=('1dedd50295ac251206dcc271ce59a918258975a7e87facde2dcee1b03fef265b') + +package() { + # makepkg has already extracted the payload into srcdir; its .PKGINFO + # and .MTREE are upstream's and stay behind, only usr/ is packaged. + cp -a "$srcdir/usr" "$pkgdir/" + # The payload keeps the licence under the upstream pkgname. + mv "$pkgdir/usr/share/licenses/schist" "$pkgdir/usr/share/licenses/$pkgname" +} From 1fa158942a001fcfa6c3e90d11c0802dab4100e7 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Fri, 4 Sep 2026 16:16:54 -0500 Subject: [PATCH 22/63] perplexity: make the stray-entry guard type-blind Review caught that the allowlist only listed files and symlinks, so a future deb shipping an empty top-level bin/, sbin/ or lib64/ -- each a filesystem-owned symlink here, the exact conflict class this guard exists to close -- would pass it, as would FIFOs and device nodes. Delete the one known unit, rmdir its emptied parents, and treat any remaining entry outside opt/ and usr/ as unexpected, whatever its type. This also stops silently rm -rf'ing future /lib content: anything new there now fails the build for a human to look at instead. Verified: clean build ships only etc/, opt/ and usr/; an injected empty bin/, a stray lib64/ file, and a FIFO each abort package() with the entry listed. Built via bin/build; installs clean in a fresh container. --- pkgbuilds/perplexity/PKGBUILD | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pkgbuilds/perplexity/PKGBUILD b/pkgbuilds/perplexity/PKGBUILD index 42a496e6..bbfb0586 100644 --- a/pkgbuilds/perplexity/PKGBUILD +++ b/pkgbuilds/perplexity/PKGBUILD @@ -95,20 +95,26 @@ package() { # 26.9.1 started shipping a systemd unit under /lib, and owning /lib -- a # symlink owned by the filesystem package -- makes pacman refuse the whole - # transaction. The unit is also a no-op here: - # its setup script apt-installs Docker and the NVIDIA Container Toolkit and - # exits on any distro but Ubuntu (install those yourself; see optdepends). - # Delete it, but only it: anything else arriving outside the trees this - # PKGBUILD expects stops the build rather than shipping the next conflict. + # transaction. The unit is also a no-op here: its setup script apt-installs + # Docker and the NVIDIA Container Toolkit and exits on any distro but Ubuntu + # (install those yourself; see optdepends). Delete it, and its parents once + # emptied; then anything else left outside opt/ and usr/ -- whatever its + # type, an empty bin/ or lib64/ included, since those are filesystem-owned + # symlinks too -- stops the build rather than shipping the next conflict. + ( + cd "${pkgdir}" + rm -f lib/systemd/system/perplexity-local-runtime-setup.service + rmdir -p lib/systemd/system 2>/dev/null || true + ) + local unexpected - unexpected=$(cd "${pkgdir}" && find . \( -type f -o -type l \) | grep -vE \ - '^\./(opt|usr)/|^\./lib/systemd/system/perplexity-local-runtime-setup\.service$' || true) + unexpected=$(cd "${pkgdir}" && find . -mindepth 1 \ + -path ./opt -prune -o -path ./usr -prune -o -print) if [[ -n "${unexpected}" ]]; then - echo "Unexpected files outside opt/ and usr/ in the upstream deb:" >&2 + echo "Unexpected entries outside opt/ and usr/ in the upstream deb:" >&2 echo "${unexpected}" >&2 return 1 fi - rm -rf "${pkgdir}/lib" # pacman never runs the deb's postinst, so the /usr/bin entry it would have # symlinked is a launcher here instead, and the menu entry goes through it so From fe409baf0005ffa1f0f733fed34cc1c884a5ae7e Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Tue, 1 Sep 2026 09:33:12 -0500 Subject: [PATCH 23/63] Add OpenClaw to the fast ring, following the npm registry Package OpenClaw 2026.9.1 as a local PKGBUILD based on the AUR one, tracking the npm registry's latest dist-tag through the repository's declarative npm upstream provider: upstream's release cadence outruns the AUR maintainer, and the dist-tag is the stable channel where a plain version-max would ship next cycle's betas. A 24h min_release_age quarantines fresh releases, which matters more than usual here because the npm tarball is not vendored: package() resolves ~330 transitive dependencies from the live registry without integrity pins. The pinned sha256 was verified against the registry by hand. The initial pin was taken inside its quarantine window through the documented BYPASS_MIN_RELEASE_AGE maintainer path, deliberately, and lands through this reviewed change as that path intends. The AUR post_upgrade restart attempt is replaced with printed guidance: it targeted a nonexistent openclaw.service, and the real openclaw-gateway.service is a systemd user unit a root pacman hook cannot reach (voxtype-bin sets the precedent). The builder ships npm 12, which refuses install-time lifecycle scripts unless the package is allow-listed, and for a local tarball the allow-list key is the tarball's own file: spec rather than the package name. Without it openclaw's postinstall never runs, the .openclaw-lifecycle-pending marker ships in the package, and every invocation dies trying to finish the lifecycle inside the root-owned /usr/lib/node_modules/openclaw. package() now passes --allow-scripts and fails the build if the marker survives. That postinstall also runs upstream's legacy-state migration against whatever home it sees, so the npm call gets a scratch HOME under $srcdir with the OPENCLAW_* location overrides unset: a maintainer's own ~/.openclaw is not the build's to prune. --- pkgbuilds/openclaw/.omarchy/package.json | 11 ++++ pkgbuilds/openclaw/PKGBUILD | 72 ++++++++++++++++++++++++ pkgbuilds/openclaw/openclaw.install | 17 ++++++ 3 files changed, 100 insertions(+) create mode 100644 pkgbuilds/openclaw/.omarchy/package.json create mode 100644 pkgbuilds/openclaw/PKGBUILD create mode 100644 pkgbuilds/openclaw/openclaw.install diff --git a/pkgbuilds/openclaw/.omarchy/package.json b/pkgbuilds/openclaw/.omarchy/package.json new file mode 100644 index 00000000..1c25fbcd --- /dev/null +++ b/pkgbuilds/openclaw/.omarchy/package.json @@ -0,0 +1,11 @@ +{ + "source": "local", + "release_ring": "fast", + "min_release_age": "24h", + "upstream": { + "npm": "openclaw", + "sources": { + "any": ["{npm_tarball}"] + } + } +} diff --git a/pkgbuilds/openclaw/PKGBUILD b/pkgbuilds/openclaw/PKGBUILD new file mode 100644 index 00000000..5b2f50c6 --- /dev/null +++ b/pkgbuilds/openclaw/PKGBUILD @@ -0,0 +1,72 @@ +# Based on the AUR openclaw package: +# Maintainer: Ralph Torres +# Contributor: Evert Vorster +# Contributor: Emir-Eins +# Contributor: Wuxxin +# Followed here straight from the npm registry (see .omarchy/package.json), since +# upstream's release cadence outruns the AUR. + +pkgname=openclaw +pkgver=2026.9.1 +pkgrel=1 +pkgdesc='Multi-channel AI gateway with extensible messaging integrations' +arch=(x86_64 aarch64) +url=https://github.com/openclaw/openclaw +license=(MIT) + +depends=('nodejs>=22') +makedepends=(npm) +optdepends=( + '1password-cli: 1password skill' + 'curl: weather, openai-whisper-api skills' + 'ffmpeg: video-frames skill' + 'github-cli: github, gh-issues skills' + 'jq: session-logs, trello skills' + 'python-openai-whisper: openai-whisper skill' + 'ripgrep: session-logs skill' + 'tmux: tmux skill' + 'uv: nano-pdf skill' + 'go: for installing skill tools not packaged for Arch' +) +source=($pkgname-$pkgver.tgz::https://registry.npmjs.org/$pkgname/-/$pkgname-$pkgver.tgz) +sha256sums=('1bfcac877d53f1e41b69d15c24e081895b2f07d6ff2ffdfe0bf8a7336ab00e59') +options=(!debug !strip) +install=$pkgname.install +noextract=($pkgname-$pkgver.tgz) + +package() { + export SHARP_IGNORE_GLOBAL_LIBVIPS=1 + # npm 12 blocks install-time lifecycle scripts unless the package is + # allow-listed, and for a local tarball the allow-list key is the tarball's + # own file: spec, not the package name (arborist matches node.resolved). + # Upstream's postinstall prunes stale dist files and clears the + # .openclaw-lifecycle-pending marker the tarball ships with; without it, + # every openclaw invocation tries to finish that lifecycle itself and dies + # with EACCES on the root-owned install. + # That postinstall also migrates legacy state under whatever home it sees + # (pruning plugin-runtime-deps in ~/.openclaw and friends), so it gets a + # scratch home: a maintainer's own OpenClaw is not the build's to touch. + mkdir -p "$srcdir"/home + env -u OPENCLAW_HOME -u OPENCLAW_STATE_DIR -u OPENCLAW_CONFIG_PATH HOME="$srcdir"/home \ + npm install --silent --global --cache "$srcdir"/npm-cache \ + --allow-scripts="file:$srcdir/$pkgname-$pkgver.tgz" \ + --prefix "$pkgdir"/usr "$srcdir"/$pkgname-$pkgver.tgz + if [[ -e "$pkgdir"/usr/lib/node_modules/$pkgname/.openclaw-lifecycle-pending ]]; then + error "openclaw's postinstall did not run; the package would fail on first use" + return 1 + fi + + cat > $pkgname <<'EOF' +#!/bin/sh +export SHARP_IGNORE_GLOBAL_LIBVIPS=1 +exec node /usr/lib/node_modules/openclaw/openclaw.mjs "$@" +EOF + install -Dm755 -t "$pkgdir"/usr/bin $pkgname + + cd "$pkgdir"/usr/lib/node_modules/$pkgname + install -Dm644 -t "$pkgdir"/usr/share/licenses/$pkgname LICENSE + install -Dm644 -t "$pkgdir"/usr/share/doc/$pkgname README.md CHANGELOG.md + for f in docs/* + do ln -s /usr/lib/node_modules/$pkgname/"$f" "$pkgdir"/usr/share/doc/$pkgname/ + done +} diff --git a/pkgbuilds/openclaw/openclaw.install b/pkgbuilds/openclaw/openclaw.install new file mode 100644 index 00000000..dccd8c6e --- /dev/null +++ b/pkgbuilds/openclaw/openclaw.install @@ -0,0 +1,17 @@ +post_install() { + printf "%s\n" \ + "==> Install gateway service: openclaw gateway install --force" \ + "==> Perform onboarding once: openclaw onboard" +} + +post_upgrade() { + # The gateway runs as a systemd user unit (openclaw-gateway.service in + # ~/.config/systemd/user), which a root pacman hook has no session bus + # to reach, so it cannot be restarted from here and keeps running the + # old code until the user restarts it. + printf "%s\n" \ + "==> Restart the gateway to apply updates:" \ + " openclaw gateway restart" \ + "==> Run doctor to migrate configs, check gateway health, etc.:" \ + " openclaw doctor" +} From 91843ab099ec83a40492bc3b626ba2c10dc8965c Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Wed, 2 Sep 2026 23:46:41 +1000 Subject: [PATCH 24/63] Make aarch64 a first-class architecture in the scheduled pipeline One list, PUBLISHED_ARCHES in helpers/paths.sh (default x86_64, overridable with OMARCHY_ARCHES), now drives everything the repository host schedules. check-versions compares PKGBUILDs against each architecture's channel databases and writes one queue per channel and architecture; auto-release works through the queues one architecture at a time, each with its own backoff, so a failing build on one never blocks the other; advance-channel --arch all re-runs an advance for every published architecture and omarchy-release uses it for start and ship, building the pinned pair once per architecture in its rc trigger; the train observes channels through the reference (first) architecture instead of a hard-coded x86_64. Queue and backoff files written under the old per-channel names are treated as x86_64 until consumed. Two things made an aarch64 builder image impossible to create: the keyring bootstrap fetched omarchy-keyring from the target architecture's own channel tree, which does not exist before that architecture has published anything, and the QEMU probe only knew the x86_64-host, aarch64-target case. The keyring (arch=any) now always comes from the x86_64 tree, and the probe compares host and target architectures and runs a container for the target platform. clean-repo grouped versions with a regex that only knew any, x86_64 and i686, so aarch64 packages would never have been pruned. --- README.md | 49 ++++++++++++--- bin/advance-channel | 43 ++++++++++++- bin/auto-release | 150 +++++++++++++++++++++++++++----------------- bin/build | 14 +++-- bin/check-versions | 48 ++++++++++---- bin/clean-repo | 5 +- bin/omarchy-release | 24 +++++-- bin/release | 6 +- bin/setup | 4 ++ bin/timers | 26 +++++--- build/Dockerfile | 9 ++- helpers/paths.sh | 56 +++++++++++++++++ 12 files changed, 330 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 15f55e17..ee876bac 100644 --- a/README.md +++ b/README.md @@ -30,18 +30,44 @@ The filesystem no longer encodes release policy. Instead: ## Prerequisites ### aarch64 Builds (Optional) -To build ARM64 packages on x86_64, enable QEMU emulation: +The repository host builds every architecture it publishes on the same +machine. A foreign architecture runs under QEMU user emulation, which +`bin/build` sets up on first use (and re-checks after each reboot by +actually running a container for the target platform): ```bash -# Run after each reboot -docker run --privileged --rm tonistiigi/binfmt --install arm64 - # Verify docker run --rm --platform linux/arm64 alpine:latest uname -m # Should output: aarch64 ``` -**Note**: aarch64 builds use QEMU and slower than native x86_64 builds. +**Note**: emulated builds are much slower than native ones. + +### Published architectures + +`helpers/paths.sh` names the architectures this repository publishes: + +```bash +PUBLISHED_ARCHES="${OMARCHY_ARCHES:-x86_64}" +``` + +That list drives the whole scheduled pipeline. `check-versions` compares +PKGBUILDs against each architecture's channel databases and writes one queue +file per channel and architecture (`.sync-needed--`); +`auto-release ` works through the queues one architecture at a +time, each with its own backoff (`.build-failed--`), so a +failing build on one architecture never holds up the other; and the release +train advances channels with `--arch all`, so a channel never moves for one +architecture and not another. The first entry is the reference architecture +the release train observes channels through. + +Adding an architecture is therefore one change to that list (or +`OMARCHY_ARCHES` in the host's build credentials): the next `check-versions` +tick queues everything the new architecture lacks, and the next +`auto-release` tick starts building it. The builder image bootstraps +`omarchy-keyring` from the x86_64 tree for every architecture, so the first +build of a new architecture does not depend on a repository that only it can +create. ## Quick Start @@ -737,10 +763,13 @@ bin/repo release --package my-package - Mirrors: mirror.omarchy.org, rackspace, pkgbuild.com ### aarch64 -- QEMU emulation required on x86_64 hosts (slower) -- Uses Arch Linux ARM repositories +- Built on the repository host like x86_64; under QEMU when the host is x86_64 +- Uses Arch Linux ARM repositories (one mirrorlist for every channel — Arch + Linux ARM publishes no dated snapshots to pin a channel's base to) - Additional repos: `[alarm]`, `[aur]` -- Same workflow, just add `--arch aarch64` +- Same workflow, just add `--arch aarch64`; the scheduled pipeline runs it + automatically once `aarch64` is in `PUBLISHED_ARCHES` +- Packages whose `arch=()` lacks `aarch64` are skipped, not failed ### Building for Both Architectures @@ -791,8 +820,8 @@ The repository includes GitHub workflows and systemd services for automated rele All four units run **every 5 minutes**, staggered by a minute each, so a push reaches the mirror in minutes rather than hours: -1. **check-versions** (`*:0/5`): Pulls latest from git, compares PKGBUILD versions to published versions, creates state files if builds are needed -2. **auto-release-edge** (`*:1/5`): If a state file exists, builds all edge packages that need updates +1. **check-versions** (`*:0/5`): Pulls latest from git, compares PKGBUILD versions to published versions for every published architecture, creates one state file per channel and architecture if builds are needed +2. **auto-release-edge** (`*:1/5`): For each published architecture with a state file, builds all edge packages that need updates 3. **auto-release-rc** (`*:2/5`): Builds fast-ring packages for rc, from the main checkout like the other two — natively in the rc image, not copied from another channel. The pinned release pair is built separately by `omarchy-release rc` in the `rc` branch worktree 4. **auto-release-stable** (`*:3/5`): If a state file exists, builds `release_ring=fast` packages for stable and replicates them to rc diff --git a/bin/advance-channel b/bin/advance-channel index 5581a8e2..da320da3 100755 --- a/bin/advance-channel +++ b/bin/advance-channel @@ -26,6 +26,32 @@ FAST_RING_ONLY=false BOOTSTRAP=false PACKAGES="" +# Kept for --arch all, which re-invokes this script per architecture with the +# other arguments unchanged (minus the --arch all pair itself). +ORIGINAL_ARGS=() +skip_next=false +prev="" +for arg in "$@"; do + if [[ "$skip_next" == true ]]; then + skip_next=false + prev="" + continue + fi + if [[ "$arg" == "--arch" ]]; then + prev="--arch" + continue + fi + if [[ "$prev" == "--arch" ]]; then + prev="" + if [[ "$arg" == "all" ]]; then + continue + fi + ORIGINAL_ARGS+=("--arch" "$arg") + continue + fi + ORIGINAL_ARGS+=("$arg") +done + usage() { echo "Usage: $0 --from --to [OPTIONS]" echo "" @@ -36,7 +62,8 @@ usage() { echo " or --bootstrap (one-time initial seed)" echo "" echo "Options:" - echo " --arch Target architecture (x86_64 or aarch64, default: x86_64)" + echo " --arch |all Target architecture (x86_64 or aarch64, default: x86_64);" + echo " all = every published architecture, one after another" echo " --package Advance only the named package(s)" echo " --fast-ring Restrict to fast-ring packages (stable -> rc parity copy)" echo " --bootstrap One-time stable -> rc seed before forward-only enforcement" @@ -65,6 +92,20 @@ while [[ $# -gt 0 ]]; do shift 2 ;; --arch) + if [[ "$2" == "all" ]]; then + # A release train advances every published architecture together; a + # channel that moved for one and not another would serve mismatched + # versions to users on the two. Re-run this script once per + # architecture with the same arguments, stopping at the first failure + # so the operator sees which architecture is stuck (the advance is + # idempotent, so re-running with --arch all resumes where it stopped). + shift 2 + for arch in $(published_arches); do + "$0" --arch "$arch" "${ORIGINAL_ARGS[@]}" || exit $? + done + exit 0 + fi + require_valid_arch "$2" ARCH="$2" update_arch_paths shift 2 diff --git a/bin/auto-release b/bin/auto-release index 4d6d0648..10a83804 100755 --- a/bin/auto-release +++ b/bin/auto-release @@ -1,6 +1,10 @@ #!/bin/bash # Run the release workflow for a channel when work is queued. -# Usage: auto-release +# Usage: auto-release [|all] +# +# With no architecture (what the timers pass), every published architecture +# is processed in turn, each against its own queue and its own backoff, so a +# failing aarch64 build never holds up x86_64 or the other way round. # # Safe to run on a tight schedule. Three guards make that true: # @@ -21,7 +25,7 @@ source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/lock-helpers.sh" MIRROR="${1:-}" -STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}" +ARCH_ARG="${2:-all}" # Backoff schedule: 10m, 20m, 40m, 80m, 160m, 320m, then hourly-ish forever # (capped at 6h, the cadence this system ran at before frequent timers). @@ -29,8 +33,9 @@ BACKOFF_BASE_SECONDS="${OMARCHY_BACKOFF_BASE:-600}" BACKOFF_MAX_SECONDS="${OMARCHY_BACKOFF_MAX:-21600}" if [[ -z "$MIRROR" ]]; then - print_error "Usage: $0 " + print_error "Usage: $0 [|all]" echo " mirror: edge, rc, or stable" + echo " arch: one of $VALID_ARCHES, or all (default) for every published architecture" exit 1 fi @@ -39,17 +44,13 @@ if [[ "$MIRROR" != "edge" && "$MIRROR" != "rc" && "$MIRROR" != "stable" ]]; then exit 1 fi -STATE_FILE="$STATE_DIR/.sync-needed-$MIRROR" -FAIL_FILE="$STATE_DIR/.build-failed-$MIRROR" - -# Nothing queued: stay quiet. At a 5-minute cadence this is most invocations, -# and a header for each would bury the runs that matter in the journal. -if [[ ! -f "$STATE_FILE" ]]; then - exit 0 +if [[ "$ARCH_ARG" == "all" ]]; then + ARCHES=$(published_arches) +else + require_valid_arch "$ARCH_ARG" + ARCHES="$ARCH_ARG" fi -print_header "Processing Sync for $MIRROR" - # The build inputs are this checkout's contents; its HEAD identifies them. current_fingerprint() { git -C "$BUILD_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown" @@ -65,57 +66,92 @@ backoff_seconds() { echo "$delay" } -FAIL_COUNT=0 -if [[ -f "$FAIL_FILE" ]]; then - # shellcheck disable=SC1090 - source "$FAIL_FILE" 2>/dev/null || true - FAIL_COUNT="${FAILURE_COUNT:-0}" - failed_at="${FAILURE_AT:-0}" - failed_fingerprint="${FAILURE_FINGERPRINT:-}" +# Returns 0 when this architecture's queue was processed (or was empty), 1 when +# the release failed. Backoff and "someone else holds the lock" are not +# failures: they are this tick deciding to do nothing. +release_arch() { + local arch="$1" + local state_file fail_file + state_file=$(sync_queue_file "$MIRROR" "$arch") + fail_file=$(sync_fail_file "$MIRROR" "$arch") - if [[ "$failed_fingerprint" != "$(current_fingerprint)" ]]; then - print_info "Repository changed since the last failure — clearing backoff and retrying" - rm -f "$FAIL_FILE" - FAIL_COUNT=0 - else - delay=$(backoff_seconds "$FAIL_COUNT") - now=$(date +%s) - retry_at=$((failed_at + delay)) - if ((now < retry_at)); then - print_warning "$MIRROR has failed $FAIL_COUNT time(s) on this tree — not retrying until $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")" - echo " Push a fix (any new commit clears this), or: rm $FAIL_FILE" - exit 0 - fi - print_info "Backoff elapsed — retrying $MIRROR (failure #$((FAIL_COUNT + 1)) if this fails)" + # A queue written under the pre-architecture name belongs to x86_64. + if [[ "$arch" == "x86_64" && ! -f "$state_file" && -f "$(legacy_sync_queue_file "$MIRROR")" ]]; then + state_file=$(legacy_sync_queue_file "$MIRROR") + fi + if [[ "$arch" == "x86_64" && ! -f "$fail_file" && -f "$(legacy_sync_fail_file "$MIRROR")" ]]; then + fail_file=$(legacy_sync_fail_file "$MIRROR") fi -fi -# Non-blocking: a build in progress means this tick has nothing to do. -if ! try_release_lock; then - holder=$(release_lock_holder) - print_info "A release is already running (${holder:-holder unknown}) — skipping this tick" - exit 0 -fi + # Nothing queued: stay quiet. At a 5-minute cadence this is most + # invocations, and a header for each would bury the runs that matter in + # the journal. + [[ -f "$state_file" ]] || return 0 -print_info "State file found: $STATE_FILE" -print_info "Starting release workflow for $MIRROR..." + print_header "Processing Sync for $MIRROR ($arch)" -if "$BUILD_ROOT/bin/repo" release --mirror "$MIRROR" --skip-prod-check; then - print_success "Release completed successfully for $MIRROR" - rm -f "$STATE_FILE" - rm -f "$FAIL_FILE" - print_success "State file removed: $STATE_FILE" -else - status=$? - FAIL_COUNT=$((FAIL_COUNT + 1)) - cat >"$FAIL_FILE" </dev/null || true + fail_count="${FAILURE_COUNT:-0}" + failed_at="${FAILURE_AT:-0}" + failed_fingerprint="${FAILURE_FINGERPRINT:-}" + + if [[ "$failed_fingerprint" != "$(current_fingerprint)" ]]; then + print_info "Repository changed since the last failure — clearing backoff and retrying" + rm -f "$fail_file" + fail_count=0 + else + delay=$(backoff_seconds "$fail_count") + now=$(date +%s) + retry_at=$((failed_at + delay)) + if ((now < retry_at)); then + print_warning "$MIRROR ($arch) has failed $fail_count time(s) on this tree — not retrying until $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")" + echo " Push a fix (any new commit clears this), or: rm $fail_file" + return 0 + fi + print_info "Backoff elapsed — retrying $MIRROR ($arch) (failure #$((fail_count + 1)) if this fails)" + fi + fi + + # Non-blocking: a build in progress means this tick has nothing to do. The + # lock is reentrant, so once this run holds it the remaining architectures + # run under the same acquisition. + if ! try_release_lock; then + local holder + holder=$(release_lock_holder) + print_info "A release is already running (${holder:-holder unknown}) — skipping this tick" + return 0 + fi + + print_info "State file found: $state_file" + print_info "Starting release workflow for $MIRROR ($arch)..." + + if "$BUILD_ROOT/bin/repo" release --mirror "$MIRROR" --arch "$arch" --skip-prod-check; then + print_success "Release completed successfully for $MIRROR ($arch)" + rm -f "$state_file" "$fail_file" + print_success "State file removed: $state_file" + return 0 + fi + + fail_count=$((fail_count + 1)) + cat >"$fail_file" </dev/null 2>&1; then - print_info "Setting up QEMU for ARM64 emulation..." +# A foreign target architecture runs under QEMU user emulation. Probe by +# actually running a container for the target platform: that is the only +# test that covers both "binfmt not registered" and "registered but broken". +HOST_ARCH=$(uname -m) +[[ "$HOST_ARCH" == "arm64" ]] && HOST_ARCH=aarch64 +if [[ "$HOST_ARCH" != "$ARCH" ]]; then + # shellcheck disable=SC2086 + if ! docker run --rm $(get_platform_arg "$ARCH") alpine:3.21 /bin/true >/dev/null 2>&1; then + print_info "Setting up QEMU for $ARCH emulation on this $HOST_ARCH host..." setup_qemu fi fi diff --git a/bin/check-versions b/bin/check-versions index fe765839..fda47d7a 100755 --- a/bin/check-versions +++ b/bin/check-versions @@ -1,6 +1,10 @@ #!/bin/bash # Check PKGBUILD versions against published repo versions -# Creates state files for edge and/or stable if any packages need building +# Creates a state file per channel and architecture when packages need building +# +# Usage: check-versions [--pull] [--arch ] +# --pull pull the repository first (the scheduled run does this) +# --arch check one architecture; default: every published one set -e @@ -10,11 +14,27 @@ source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/package-metadata.sh" source "$BUILD_ROOT/helpers/lock-helpers.sh" -STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}" -ARCH="${ARCH:-x86_64}" PULL=false +ARCHES="" -[[ "${1:-}" == "--pull" ]] && PULL=true +while [[ $# -gt 0 ]]; do + case $1 in + --pull) + PULL=true + shift + ;; + --arch) + require_valid_arch "$2" + ARCHES="$2" + shift 2 + ;; + *) + print_error "Unknown option: $1" + exit 1 + ;; + esac +done +[[ -n "$ARCHES" ]] || ARCHES=$(published_arches) mkdir -p "$STATE_DIR" @@ -147,11 +167,12 @@ check_package() { check_mirror() { local mirror="$1" - local state_file="$STATE_DIR/.sync-needed-$mirror" + local state_file + state_file=$(sync_queue_file "$mirror" "$ARCH") local needs_build=false local packages=() - print_info "Checking $mirror packages..." + print_info "Checking $mirror packages for $ARCH..." while IFS= read -r pkg; do local pkgdir="$PKGBUILDS_DIR/$pkg" @@ -168,18 +189,23 @@ check_mirror() { # this to name the packages in its start report, which is the difference # between "a build is running" and "your package is in this build". printf '%s\n' "${packages[@]}" >"$state_file" - print_success "${mirror^} needs building (${#packages[@]} packages)" + print_success "${mirror^} ($ARCH) needs building (${#packages[@]} packages)" print_info "Packages: ${packages[*]}" print_info "State file created: $state_file" else - print_info "${mirror^} is up to date" + print_info "${mirror^} ($ARCH) is up to date" fi echo "" } -check_mirror edge -check_mirror rc -check_mirror stable +# One pass per published architecture: the version comparison reads that +# architecture's channel databases, and each queue is its own file. +for ARCH in $ARCHES; do + update_arch_paths + check_mirror edge + check_mirror rc + check_mirror stable +done print_success "Version check complete!" diff --git a/bin/clean-repo b/bin/clean-repo index 493b85a6..5b020af1 100755 --- a/bin/clean-repo +++ b/bin/clean-repo @@ -33,7 +33,10 @@ clean_packages() { # Extract package name (remove version and architecture) # Format: name-version-release-arch.pkg.tar.* - local pkgname=$(echo "$pkg" | sed -E 's/-[0-9]+.*-(any|x86_64|i686)\.pkg\.tar\..*//') + # The version starts at the first "-" and neither pkgver nor pkgrel + # can contain a hyphen, so the architecture is whatever sits between the + # last hyphen and .pkg.tar — any, x86_64, aarch64, and whatever comes next. + local pkgname=$(echo "$pkg" | sed -E 's/-[0-9]+.*-[^-]+\.pkg\.tar\..*//') # Add to array if [[ -n "${packages[$pkgname]}" ]]; then diff --git a/bin/omarchy-release b/bin/omarchy-release index c64d2ed5..3fa88a3f 100755 --- a/bin/omarchy-release +++ b/bin/omarchy-release @@ -28,7 +28,13 @@ ISO_REPO="${OMARCHY_ISO_REPO:-omacom-io/omarchy-iso}" DEV_BRANCH="${OMARCHY_DEV_BRANCH:-quattro}" PKGS_DB_BASE="${OMARCHY_PKGS_DB_BASE:-https://pkgs.omarchy.org}" -RC_DB_URL="$PKGS_DB_BASE/rc/x86_64/omarchy.db.tar.zst" +# The train observes channels through the reference architecture (the first +# published one). The pair is pinned to one version for every architecture and +# built for all of them in one rc trigger, so one architecture's database is +# the state of the release; per-architecture drift is a build failure the +# scheduled pipeline reports, not something the train has to poll for. +OBSERVED_ARCH=$(reference_arch) +RC_DB_URL="$PKGS_DB_BASE/rc/$OBSERVED_ARCH/omarchy.db.tar.zst" SRCDEST_DIR="$BUILD_ROOT/.srcdest" MIRROR_CLONE="$SRCDEST_DIR/omarchy" # bare mirror (shared with bin/omarchy-pkgs) @@ -186,7 +192,7 @@ published_version() { # A unique query string busts the CDN cache: right after a sync the plain # URL can keep serving the previous db for a while, which reads as "not # published yet" to status, the wait loop, and ship's pre-checks. - if ! curl -sf "$PKGS_DB_BASE/$channel/x86_64/omarchy.db.tar.zst?$(date +%s%N)" -o "$tmp"; then + if ! curl -sf "$PKGS_DB_BASE/$channel/$OBSERVED_ARCH/omarchy.db.tar.zst?$(date +%s%N)" -o "$tmp"; then rm -f "$tmp" return 2 fi @@ -251,8 +257,12 @@ fi git -C /root/omarchy-pkgs-rc fetch origin rc git -C /root/omarchy-pkgs-rc reset --hard origin/rc cd /root/omarchy-pkgs-rc -OMARCHY_RC_PINS=1 OMARCHY_REPO_ROOT=/root/omarchy-pkgs/pkgs.omarchy.org \ - bin/repo release --mirror rc --package omarchy omarchy-settings --skip-prod-check +# The pair is built once per published architecture (helpers/paths.sh on the +# host decides which), so every architecture'"'"'s rc channel carries the pins. +for arch in $(BUILD_ROOT=/root/omarchy-pkgs-rc bash -c "source helpers/paths.sh; published_arches"); do + OMARCHY_RC_PINS=1 OMARCHY_REPO_ROOT=/root/omarchy-pkgs/pkgs.omarchy.org \ + bin/repo release --mirror rc --arch "$arch" --package omarchy omarchy-settings --skip-prod-check +done ' trigger_rc_build() { @@ -278,10 +288,12 @@ host_advance() { # host_advance [extra args...] # against this machine's (likely stale) local tree would be wrong. if ! resolve_repo_host "$REPO_HOST_OVERRIDE" >/dev/null && ! on_repo_host; then print_no_host_help "advance $from -> $to" \ - " cd /root/omarchy-pkgs && bin/repo advance --from $from --to $to --skip-prod-check $*" + " cd /root/omarchy-pkgs && bin/repo advance --from $from --to $to --arch all --skip-prod-check $*" return 1 fi - "$BUILD_ROOT/bin/repo" advance --from "$from" --to "$to" --skip-prod-check "$@" + # --arch all: the host advances every architecture it publishes, so a train + # never moves a channel for one architecture and not another. + "$BUILD_ROOT/bin/repo" advance --from "$from" --to "$to" --arch all --skip-prod-check "$@" } wait_for_published() { # wait_for_published [timeout-seconds] diff --git a/bin/release b/bin/release index d40fa0f8..193bfb52 100755 --- a/bin/release +++ b/bin/release @@ -114,7 +114,11 @@ BUILT_FILES="" # What the scheduled version check queued, when it was the one that asked for # this run. Absent for a manual run, which is fine — the report just omits it. QUEUED_PACKAGES="" -QUEUE_FILE="${OMARCHY_STATE_DIR:-/root/.state}/.sync-needed-$MIRROR" +QUEUE_FILE=$(sync_queue_file "$MIRROR" "$ARCH") +# A queue written under the pre-architecture name belongs to x86_64. +if [[ "$ARCH" == "x86_64" && ! -s "$QUEUE_FILE" && -s "$(legacy_sync_queue_file "$MIRROR")" ]]; then + QUEUE_FILE=$(legacy_sync_queue_file "$MIRROR") +fi [[ -s "$QUEUE_FILE" ]] && QUEUED_PACKAGES=$(grep -c '' "$QUEUE_FILE") if [[ "$DRY_RUN" != true ]]; then diff --git a/bin/setup b/bin/setup index 5467be64..014dd2f8 100755 --- a/bin/setup +++ b/bin/setup @@ -237,6 +237,10 @@ echo "" # --- release timers ---------------------------------------------------------- +print_info "Published architectures: $(published_arches | tr '\n' ' ')" +echo " (PUBLISHED_ARCHES in helpers/paths.sh, or OMARCHY_ARCHES in $CREDENTIALS)" +echo "" + TIMERS=(omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-rc omarchy-auto-release-stable) if [[ "$SKIP_TIMERS" == true ]]; then diff --git a/bin/timers b/bin/timers index 619a821e..1cb2f4b9 100755 --- a/bin/timers +++ b/bin/timers @@ -84,15 +84,18 @@ echo "" # --- queued work ------------------------------------------------------------- -print_info "Queued builds (state files in $STATE_DIR)" +print_info "Queued builds (state files in $STATE_DIR; architectures: $(published_arches | tr '\n' ' '))" queued=false for channel in edge rc stable; do - state_file="$STATE_DIR/.sync-needed-$channel" - if [[ -f "$state_file" ]]; then - queued=true - since=$(date -r "$state_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown") - printf ' • %-7s queued since %s\n' "$channel" "$since" - fi + for arch in $(published_arches); do + state_file=$(sync_queue_file "$channel" "$arch") + [[ "$arch" == "x86_64" && ! -f "$state_file" ]] && state_file=$(legacy_sync_queue_file "$channel") + if [[ -f "$state_file" ]]; then + queued=true + since=$(date -r "$state_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown") + printf ' • %-7s %-8s queued since %s\n' "$channel" "$arch" "$since" + fi + done done [[ "$queued" == false ]] && echo " (nothing queued — all channels up to date)" echo "" @@ -101,7 +104,9 @@ echo "" # say so plainly: it is queued but deliberately not being retried yet. paused=false for channel in edge rc stable; do - fail_file="$STATE_DIR/.build-failed-$channel" + for arch in $(published_arches); do + fail_file=$(sync_fail_file "$channel" "$arch") + [[ "$arch" == "x86_64" && ! -f "$fail_file" ]] && fail_file=$(legacy_sync_fail_file "$channel") [[ -f "$fail_file" ]] || continue if [[ "$paused" == false ]]; then print_error "Failing builds (backoff active)" @@ -122,13 +127,14 @@ for channel in edge rc stable; do else when="retries on the next tick" fi - printf ' ✗ %-7s %s consecutive failure(s), %s\n' "$channel" "$FAILURE_COUNT" "$when" + printf ' ✗ %-7s %-8s %s consecutive failure(s), %s\n' "$channel" "$arch" "$FAILURE_COUNT" "$when" printf ' last attempt %s on commit %s\n' \ "$(date -d "@$FAILURE_AT" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$FAILURE_AT")" \ "${FAILURE_FINGERPRINT:0:12}" + done done if [[ "$paused" == true ]]; then - echo " Any new commit clears the backoff; or: rm $STATE_DIR/.build-failed-" + echo " Any new commit clears the backoff; or: rm $STATE_DIR/.build-failed--" echo "" fi diff --git a/build/Dockerfile b/build/Dockerfile index fc1e66b7..5ef7bfd2 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -108,11 +108,16 @@ RUN ln -sf /usr/lib/os-release /etc/os-release && \ # Setup Omarchy keyring manually before adding repo (avoids keyserver trust issues) # Note: Repository is removed at the end since build scripts add it dynamically. # The keyring comes from this image's own channel (the bare /$arch path is a -# stale legacy layout); %s keeps pacman's $arch literal while MIRROR expands. +# stale legacy layout). It is always taken from the x86_64 tree, whatever the +# image's own architecture: omarchy-keyring is an arch=any package, and the +# x86_64 tree is the one that exists before a new architecture has published +# anything. Bootstrapping from the target's own tree would make the first +# aarch64 build depend on an aarch64 repository that only that build can +# create. ARG MIRROR=edge RUN pacman-key --recv-keys 40DFB630FF42BCFFB047046CF0134EE680CAC571 --keyserver keys.openpgp.org && \ pacman-key --lsign-key 40DFB630FF42BCFFB047046CF0134EE680CAC571 && \ - printf '\n[omarchy]\nSigLevel = Optional\nServer = https://pkgs.omarchy.org/%s/$arch\n' "${MIRROR}" >> /etc/pacman.conf && \ + printf '\n[omarchy]\nSigLevel = Optional\nServer = https://pkgs.omarchy.org/%s/x86_64\n' "${MIRROR}" >> /etc/pacman.conf && \ pacman -Sy --noconfirm && \ pacman -S --noconfirm omarchy-keyring && \ pacman-key --populate omarchy && \ diff --git a/helpers/paths.sh b/helpers/paths.sh index 42fb44bd..c78db22b 100644 --- a/helpers/paths.sh +++ b/helpers/paths.sh @@ -5,6 +5,62 @@ ARCH=${ARCH:-x86_64} MIRROR=${MIRROR:-edge} +# Architectures the tooling knows how to build. +VALID_ARCHES="x86_64 aarch64" + +# Architectures this repository PUBLISHES. The scheduled pipeline (version +# check, auto-release, channel advance with --arch all) runs once per entry, +# in this order; the first entry is the reference architecture that the +# release train observes channels through. Adding an architecture here is the +# enablement step: the next check-versions tick queues its packages and the +# next auto-release tick builds them. OMARCHY_ARCHES overrides it for a host +# or a one-off run. +PUBLISHED_ARCHES="${OMARCHY_ARCHES:-x86_64}" + +validate_arch() { + case " $VALID_ARCHES " in + *" $1 "*) return 0 ;; + *) return 1 ;; + esac +} + +require_valid_arch() { + if ! validate_arch "$1"; then + echo "Invalid architecture: $1 (must be one of: $VALID_ARCHES)" >&2 + exit 1 + fi +} + +published_arches() { + local arch + for arch in $PUBLISHED_ARCHES; do + require_valid_arch "$arch" + echo "$arch" + done +} + +reference_arch() { + published_arches | head -1 +} + +# Scheduled-pipeline state, one file per channel and architecture, so one +# architecture's queue or backoff never gates another's. +STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}" + +sync_queue_file() { # sync_queue_file + echo "$STATE_DIR/.sync-needed-$1-$2" +} + +sync_fail_file() { # sync_fail_file + echo "$STATE_DIR/.build-failed-$1-$2" +} + +# The pre-architecture names, .sync-needed- and .build-failed-, +# meant x86_64. A host upgraded mid-cycle may still hold one; readers treat +# it as the x86_64 file until it is consumed. +legacy_sync_queue_file() { echo "$STATE_DIR/.sync-needed-$1"; } +legacy_sync_fail_file() { echo "$STATE_DIR/.build-failed-$1"; } + # Valid package channels, in pipeline order: packages move edge -> rc -> stable VALID_MIRRORS="edge rc stable" From b677f50358ab2f2eda9971b08888fe92b50d6bdb Mon Sep 17 00:00:00 2001 From: Marcelo Alcantara Date: Thu, 3 Sep 2026 02:10:04 +1000 Subject: [PATCH 25/63] Pin PKGEXT to .pkg.tar.zst in the builder image Arch Linux ARM's makepkg.conf defaults PKGEXT to .pkg.tar.xz. build/sign.sh only signs *.pkg.tar.zst, and push-build, sync-rebuilds and the notifier parse the same suffix, so an aarch64 package built under the stacked pipeline came out as .xz and would have been skipped at signing. Seen on a plain x86_64 runner building aarch64 under QEMU (fork run 33642125077). Same fix as the PKGEXT line in #240. --- build/Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index 5ef7bfd2..258fec4e 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -142,8 +142,13 @@ RUN useradd -m -G wheel -s /bin/bash builder && \ chmod 700 /home/builder/.gnupg && \ chown -R builder:builder /home/builder +# Arch Linux ARM's makepkg.conf still defaults PKGEXT to .pkg.tar.xz; every +# tool downstream of the build (sign.sh, push-build, sync-rebuilds, the +# notifier) expects .pkg.tar.zst, so an aarch64 package would build and then +# be skipped at signing. Pin the extension so both architectures match. RUN sed -i 's/^#MAKEFLAGS=.*/MAKEFLAGS="-j$(nproc)"/' /etc/makepkg.conf && \ - sed -i 's/^COMPRESSZST=.*/COMPRESSZST=(zstd -c -z -q --threads=0 -)/' /etc/makepkg.conf + sed -i 's/^COMPRESSZST=.*/COMPRESSZST=(zstd -c -z -q --threads=0 -)/' /etc/makepkg.conf && \ + sed -i "s|^PKGEXT=.*|PKGEXT='.pkg.tar.zst'|" /etc/makepkg.conf # Pacman wrapper that auto-resolves package conflicts (e.g. rustup vs rust). # makepkg only passes --noconfirm to pacman, which defaults to 'N' on conflict From 76687fcc82eed3ee9ca59748e301d0b785d8f918 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 3 Sep 2026 23:46:57 -0400 Subject: [PATCH 26/63] Harden multi-architecture release pipeline --- .github/workflows/test.yml | 1 + README.md | 66 +++-- bin/advance-channel | 73 ++--- bin/auto-release | 15 +- bin/build | 9 +- bin/check-versions | 2 +- bin/clean-repo | 13 +- bin/omarchy-pkgs | 18 +- bin/omarchy-release | 125 +++++---- bin/remove-package | 15 +- bin/setup | 3 +- bin/sign | 15 +- bin/sync-rebuilds | 287 ++++++++++++++++---- bin/timers | 52 ++-- bin/update-repo | 16 +- build/Dockerfile | 4 +- build/build.sh | 32 +-- helpers/docker-helpers.sh | 20 +- helpers/package-metadata.sh | 52 +++- helpers/paths.sh | 4 +- systemd/omarchy-auto-release-edge.service | 2 +- systemd/omarchy-auto-release-rc.service | 2 +- systemd/omarchy-auto-release-stable.service | 2 +- 23 files changed, 557 insertions(+), 271 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4aaea2b0..b4b78b90 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,7 @@ jobs: set -euo pipefail pacman -Syu --noconfirm jq ./bin/sync-upstream self-test + ./bin/sync-rebuilds --self-test ./bin/omarchy-pkgs self-test ./bin/omarchy-release self-test ' diff --git a/README.md b/README.md index ee876bac..c5338a0a 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,25 @@ file per channel and architecture (`.sync-needed--`); `auto-release ` works through the queues one architecture at a time, each with its own backoff (`.build-failed--`), so a failing build on one architecture never holds up the other; and the release -train advances channels with `--arch all`, so a channel never moves for one -architecture and not another. The first entry is the reference architecture -the release train observes channels through. +train advances channels with `--arch all`: it takes one host-wide lock and +verifies every architecture's source database before moving any of them. The +first entry is the reference architecture the release train observes channels +through. A remote sync failure can still leave a promotion temporarily partial; +rerunning the same advance completes it safely. -Adding an architecture is therefore one change to that list (or -`OMARCHY_ARCHES` in the host's build credentials): the next `check-versions` -tick queues everything the new architecture lacks, and the next -`auto-release` tick starts building it. The builder image bootstraps +Adding an architecture to the scheduled pipeline is therefore one checked-in +change to that list: the next `check-versions` tick queues everything the new +architecture lacks, and the next `auto-release` tick starts building it. A +checked-in list also means the rebuild workflow and release host cannot drift +onto different architecture sets. For a one-off run, override it directly: + +```bash +OMARCHY_ARCHES=x86_64 bin/check-versions +OMARCHY_ARCHES=aarch64 bin/check-versions +OMARCHY_ARCHES="x86_64 aarch64" bin/check-versions +``` + +The builder image bootstraps `omarchy-keyring` from the x86_64 tree for every architecture, so the first build of a new architecture does not depend on a repository that only it can create. @@ -450,7 +461,12 @@ A package names those dependencies in `.omarchy/package.json`: { "source": "aur", "sync": false, "rebuild_on": ["qt6-base", "qt6-declarative", "qt6-wayland"] } ``` -`bin/sync-rebuilds` reads each named package's version from the official repositories and compares it to `rebuilt_against`, the record of what the checked-in pkgrel was last bumped for. pkgrel is bumped unless every name in `rebuild_on` is recorded and still matches, so a name the record does not carry reads as changed rather than going unexamined forever. Opting a package in therefore buys one rebuild: what its published build actually linked against is not knowable from here, and a record written without a rebuild would certify a build nobody checked. +`bin/sync-rebuilds` reads each named package's version from the official +repositories for every published architecture the package supports and compares +it to `rebuilt_against`. Records are kept per architecture because Arch and +Arch Linux ARM can carry different dependency versions. pkgrel is bumped once +when any recorded version moves; that one source revision is then rebuilt by +each architecture's normal queue. The bump is the point of the command, and it has to land in git rather than in the builder. A rebuild that reuses the published version string produces a package pacman will never offer anyone, so merely unlocking the build gate would ship nothing. Bumping pkgrel needs no other change: `bin/check-versions` and the builder both already rebuild when pkgrel moves. @@ -458,9 +474,12 @@ For an AUR-synced package the bump is expressed as the dotted Omarchy pkgrel suf The bumped version is checked against the published one as well as the checked-in one, and refused when pacman would not order it higher. The checked-in version is not the floor; what a user already has is, and a checkout that has fallen behind the repository can otherwise be bumped to something that loses to the package it means to replace. That check is skipped with a warning when the published database cannot be read. -Versions are read from the local pacman database, so this runs on Arch or in an Arch container against a synced database. Only `core`, `extra` and `multilib` count: a Qt release sitting in testing or kde-unstable is not what the builder will link against, and rebuilding for it would ship a package built against the wrong ABI. The workflow points that database at `mirror.omarchy.org`, the mirror the x86_64 builder itself uses, because a mirror running ahead of the builder would record a version the build never linked against and nothing re-fires once the record matches. - -aarch64 is not covered. Those builds resolve Qt from Arch Linux ARM, which can lag Arch, so one record cannot describe both architectures. Only x86_64 is published today, so nothing currently ships from the untracked side; if ARM publishing starts, `rebuilt_against` has to become per-architecture before this can be trusted there. +x86_64 versions are read from the local pacman database, so the workflow runs +in an Arch container pointed at `mirror.omarchy.org`, the same mirror as the +x86_64 builder. aarch64 versions are read directly from the live Arch Linux ARM +repository database, which is also what the ARM builder uses. Testing and +staging repositories do not count. A legacy flat `rebuilt_against` record is +read as x86_64 and is migrated naturally the next time a rebuild is needed. ### Other @@ -668,7 +687,7 @@ Fields: - `skip_build`: optional boolean; defaults to `false`. Set `true` to exclude a package from scheduled version checks and unscoped builds. The package can still be built explicitly with `bin/repo release --package `. - `pkgrel`: optional Omarchy pkgrel suffix for a version-pinned rebuild bump. This emits `.` instead of replacing AUR's pkgrel. `offset` can be used only when preserving monotonic upgrades from old absolute pkgrel bumps. The metadata is removed automatically when AUR sync changes `pkgver`; the current package version is read from the checked-in PKGBUILD, so the version is not duplicated in JSON. - `rebuild_on`: optional array of package names this package links against closely enough that it must be rebuilt when they change, independent of its own source. Read by `bin/sync-rebuilds`. -- `rebuilt_against`: written by `bin/sync-rebuilds`. Records the version of each `rebuild_on` package that the current pkgrel was bumped for. +- `rebuilt_against`: written by `bin/sync-rebuilds`. Maps each published architecture to the versions of its `rebuild_on` packages that the current pkgrel was bumped for. - `upstream_commit`: set by `bin/sync-aur` for AUR packages. Used by `bin/package-worktree` to recreate the exact raw AUR package that Omarchy last synced. ### Build Matrix @@ -764,8 +783,10 @@ bin/repo release --package my-package ### aarch64 - Built on the repository host like x86_64; under QEMU when the host is x86_64 -- Uses Arch Linux ARM repositories (one mirrorlist for every channel — Arch - Linux ARM publishes no dated snapshots to pin a channel's base to) +- On an ARM host, package builds and the signing/database utility containers + run natively; only an explicitly requested x86_64 package build is emulated +- Uses Arch Linux ARM repositories through the same HTTPS mirror for every + channel (Arch Linux ARM publishes no dated snapshots to pin a channel's base) - Additional repos: `[alarm]`, `[aur]` - Same workflow, just add `--arch aarch64`; the scheduled pipeline runs it automatically once `aarch64` is in `PUBLISHED_ARCHES` @@ -835,10 +856,11 @@ That cadence is only safe because of three guards: an operator expects. `check-versions` takes it too — its `git pull` would otherwise swap PKGBUILDs out from under a running build. - **Backoff on failure.** A failed release records the attempt in - `.build-failed-` and backs off exponentially — 10m, 20m, 40m, up to + `.build-failed--` and backs off exponentially — 10m, 20m, 40m, up to a 6h ceiling — instead of rebuilding the same broken tree every 5 minutes. **Any new commit clears the backoff immediately**, since a push is the most - likely fix. Clear it by hand with `rm /root/.state/.build-failed-`. + likely fix. Clear it by hand with + `rm /root/.state/.build-failed--`. - **Quiet when idle.** With nothing queued a tick exits without output, so the journal shows the runs that mattered rather than 288 no-ops a day. @@ -891,10 +913,14 @@ bin/repo timers --local # inspect this machine instead ``` State files are stored in `/root/.state/`: -- `.sync-needed-` — the packages queued for that channel, one per - line; the release run reads them to name what it is building -- `.build-failed-` — consecutive failure count, timestamp, and the - commit it failed on (drives the backoff; removing it forces a retry) +- `.sync-needed--` — the packages queued for that channel and + architecture, one per line; the release run reads them to name what it is + building +- `.build-failed--` — consecutive failure count, timestamp, and + the commit it failed on (drives the backoff; removing it forces a retry) + +Legacy files without the architecture suffix are consumed once as x86_64 +state, so upgrading the host does not lose an in-flight build. ### Schedule (America/New_York) diff --git a/bin/advance-channel b/bin/advance-channel index da320da3..1effbc64 100755 --- a/bin/advance-channel +++ b/bin/advance-channel @@ -25,31 +25,21 @@ SKIP_PROD_CHECK=false FAST_RING_ONLY=false BOOTSTRAP=false PACKAGES="" +ALL_ARCHES=false # Kept for --arch all, which re-invokes this script per architecture with the # other arguments unchanged (minus the --arch all pair itself). ORIGINAL_ARGS=() -skip_next=false -prev="" +arch_option=false for arg in "$@"; do - if [[ "$skip_next" == true ]]; then - skip_next=false - prev="" - continue + if [[ "$arch_option" == true ]]; then + [[ "$arg" != "all" ]] && ORIGINAL_ARGS+=("--arch" "$arg") + arch_option=false + elif [[ "$arg" == "--arch" ]]; then + arch_option=true + else + ORIGINAL_ARGS+=("$arg") fi - if [[ "$arg" == "--arch" ]]; then - prev="--arch" - continue - fi - if [[ "$prev" == "--arch" ]]; then - prev="" - if [[ "$arg" == "all" ]]; then - continue - fi - ORIGINAL_ARGS+=("--arch" "$arg") - continue - fi - ORIGINAL_ARGS+=("$arg") done usage() { @@ -93,21 +83,13 @@ while [[ $# -gt 0 ]]; do ;; --arch) if [[ "$2" == "all" ]]; then - # A release train advances every published architecture together; a - # channel that moved for one and not another would serve mismatched - # versions to users on the two. Re-run this script once per - # architecture with the same arguments, stopping at the first failure - # so the operator sees which architecture is stuck (the advance is - # idempotent, so re-running with --arch all resumes where it stopped). - shift 2 - for arch in $(published_arches); do - "$0" --arch "$arch" "${ORIGINAL_ARGS[@]}" || exit $? - done - exit 0 + ALL_ARCHES=true + ARCH=all + else + require_valid_arch "$2" + ARCH="$2" + update_arch_paths fi - require_valid_arch "$2" - ARCH="$2" - update_arch_paths shift 2 ;; --package) @@ -175,6 +157,31 @@ case "$FROM->$TO" in ;; esac +if [[ "$ALL_ARCHES" == true ]]; then + # Hold one lock across the whole operation so a timer cannot mutate a + # channel between architectures. Check every source database before moving + # the first one; a failed sync can still require an idempotent retry, but a + # missing architecture never creates a knowingly partial advance. + if [[ "$DRY_RUN" != true ]]; then + acquire_release_lock || exit 1 + fi + + ARCHES=$(published_arches) + for arch in $ARCHES; do + source_db="$REPO_ROOT/$FROM/$arch/omarchy.db.tar.zst" + if [[ ! -f "$source_db" ]]; then + print_error "Source database not found: $source_db" + echo "Nothing has been published to the $FROM channel for $arch." + exit 1 + fi + done + + for arch in $ARCHES; do + "$0" --arch "$arch" "${ORIGINAL_ARGS[@]}" || exit $? + done + exit 0 +fi + SOURCE_DIR="$REPO_ROOT/$FROM/$ARCH" TARGET_DIR="$REPO_ROOT/$TO/$ARCH" SOURCE_DB="$SOURCE_DIR/omarchy.db.tar.zst" diff --git a/bin/auto-release b/bin/auto-release index 10a83804..b7094bb9 100755 --- a/bin/auto-release +++ b/bin/auto-release @@ -131,17 +131,28 @@ release_arch() { if "$BUILD_ROOT/bin/repo" release --mirror "$MIRROR" --arch "$arch" --skip-prod-check; then print_success "Release completed successfully for $MIRROR ($arch)" - rm -f "$state_file" "$fail_file" + local completed_state=("$state_file" "$fail_file") + if [[ "$arch" == "x86_64" ]]; then + completed_state+=("$(legacy_sync_queue_file "$MIRROR")" "$(legacy_sync_fail_file "$MIRROR")") + fi + if ! rm -f "${completed_state[@]}"; then + print_error "Release succeeded, but its queue state could not be cleared" + return 1 + fi print_success "State file removed: $state_file" return 0 fi fail_count=$((fail_count + 1)) - cat >"$fail_file" <"$fail_file" </dev/null 2>&1; then + if ! docker run --rm "$(get_platform_arg "$ARCH")" alpine:3.21 /bin/true >/dev/null 2>&1; then print_info "Setting up QEMU for $ARCH emulation on this $HOST_ARCH host..." setup_qemu fi @@ -183,7 +184,7 @@ if [[ $KEEP_BUILD_WORKSPACE == "1" ]]; then print_info "Keeping existing build workspace..." else print_info "Cleaning build workspace..." - rm -rf "$BUILD_OUTPUT_DIR"/* + rm -rf "${BUILD_OUTPUT_DIR:?}"/* fi mkdir -p "$BUILD_OUTPUT_DIR" @@ -237,7 +238,7 @@ DOCKER_ARGS=( # Run the builder with assembled args PLATFORM_ARG=$(get_platform_arg "$ARCH") -docker run $PLATFORM_ARG "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh +docker run "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh BUILD_RESULT=$? diff --git a/bin/check-versions b/bin/check-versions index fda47d7a..9849701a 100755 --- a/bin/check-versions +++ b/bin/check-versions @@ -180,7 +180,7 @@ check_mirror() { needs_build=true packages+=("$pkg") fi - done < <(packages_for_unscoped_build "$mirror") + done < <(packages_for_unscoped_build "$mirror" "$ARCH") echo "" diff --git a/bin/clean-repo b/bin/clean-repo index 5b020af1..8543a8a7 100755 --- a/bin/clean-repo +++ b/bin/clean-repo @@ -31,12 +31,13 @@ clean_packages() { # Skip signature files [[ "$pkg" == *.sig ]] && continue - # Extract package name (remove version and architecture) - # Format: name-version-release-arch.pkg.tar.* - # The version starts at the first "-" and neither pkgver nor pkgrel - # can contain a hyphen, so the architecture is whatever sits between the - # last hyphen and .pkg.tar — any, x86_64, aarch64, and whatever comes next. - local pkgname=$(echo "$pkg" | sed -E 's/-[0-9]+.*-[^-]+\.pkg\.tar\..*//') + # Format: name-version-release-arch.pkg.tar.*. Work from the right because + # package names can themselves contain version-like pieces (qt6-5compat, + # nvidia-580xx-utils), while pkgver and pkgrel cannot contain hyphens. + local stem="${pkg%%.pkg.tar.*}" + stem="${stem%-*}" # architecture + stem="${stem%-*}" # pkgrel + local pkgname="${stem%-*}" # pkgver # Add to array if [[ -n "${packages[$pkgname]}" ]]; then diff --git a/bin/omarchy-pkgs b/bin/omarchy-pkgs index 58ff646b..0ca607b9 100755 --- a/bin/omarchy-pkgs +++ b/bin/omarchy-pkgs @@ -23,7 +23,7 @@ source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/host-helpers.sh" UPSTREAM_URL="${OMARCHY_UPSTREAM_URL:-https://github.com/basecamp/omarchy.git}" -EDGE_DB_URL="${OMARCHY_EDGE_DB_URL:-https://pkgs.omarchy.org/edge/x86_64/omarchy.db.tar.zst}" +EDGE_DB_URL="${OMARCHY_EDGE_DB_URL:-https://pkgs.omarchy.org/edge/$(reference_arch)/omarchy.db.tar.zst}" RELEASE_PACKAGES=(omarchy omarchy-settings) DEFAULT_RC_REF="quattro" SRCDEST_DIR="$BUILD_ROOT/.srcdest" @@ -327,13 +327,19 @@ regenerate_checksums() { trigger_build_host() { local host + queue_edge_builds() { + mkdir -p "$STATE_DIR" || return 1 + local arch + for arch in $(published_arches); do + touch "$(sync_queue_file edge "$arch")" || return 1 + done + } + # Explicit host configuration outranks the local-host inference (a # workstation that ran a full local release carries the db marker too). if ! resolve_repo_host "${REPO_HOST_OVERRIDE:-}" >/dev/null && on_repo_host; then print_info "Triggering edge build locally (this is the build host)..." - if mkdir -p "${OMARCHY_STATE_DIR:-/root/.state}" && - touch "${OMARCHY_STATE_DIR:-/root/.state}/.sync-needed-edge" && - systemctl start --no-block omarchy-auto-release-edge.service; then + if queue_edge_builds && systemctl start --no-block omarchy-auto-release-edge.service; then print_success "Edge build triggered" else print_warning "Could not start the edge release service — the 6-hourly timer will pick it up" @@ -343,11 +349,11 @@ trigger_build_host() { if ! host=$(resolve_repo_host "${REPO_HOST_OVERRIDE:-}"); then print_info "No repository host configured (set OMARCHY_REPO_HOST or $BUILD_ROOT/.repo-host — any ssh destination, e.g. root@ or an ssh-config alias)." print_info "The 6-hourly auto-release timer will pick this up, or trigger manually:" - echo " ssh 'git -C /root/omarchy-pkgs pull --ff-only && touch /root/.state/.sync-needed-edge && systemctl start omarchy-auto-release-edge.service'" + echo " ssh 'git -C /root/omarchy-pkgs pull --ff-only && systemctl start omarchy-check-versions.service omarchy-auto-release-edge.service'" return 0 fi print_info "Triggering edge build on $host..." - if ssh "$host" 'git -C /root/omarchy-pkgs pull --ff-only && mkdir -p /root/.state && touch /root/.state/.sync-needed-edge && systemctl start --no-block omarchy-auto-release-edge.service'; then + if ssh "$host" 'git -C /root/omarchy-pkgs pull --ff-only && cd /root/omarchy-pkgs && export BUILD_ROOT=/root/omarchy-pkgs && source helpers/paths.sh && mkdir -p "$STATE_DIR" && for arch in $(published_arches); do touch "$(sync_queue_file edge "$arch")"; done && systemctl start --no-block omarchy-auto-release-edge.service'; then print_success "Edge build triggered on $host" else print_warning "Could not trigger $host — the 6-hourly timer will pick it up" diff --git a/bin/omarchy-release b/bin/omarchy-release index 3fa88a3f..762bd80d 100755 --- a/bin/omarchy-release +++ b/bin/omarchy-release @@ -28,11 +28,9 @@ ISO_REPO="${OMARCHY_ISO_REPO:-omacom-io/omarchy-iso}" DEV_BRANCH="${OMARCHY_DEV_BRANCH:-quattro}" PKGS_DB_BASE="${OMARCHY_PKGS_DB_BASE:-https://pkgs.omarchy.org}" -# The train observes channels through the reference architecture (the first -# published one). The pair is pinned to one version for every architecture and -# built for all of them in one rc trigger, so one architecture's database is -# the state of the release; per-architecture drift is a build failure the -# scheduled pipeline reports, not something the train has to poll for. +# The first published architecture supplies the version-ordering floor when +# cutting pins. Readiness checks below still verify every published +# architecture before an RC or final release can move forward. OBSERVED_ARCH=$(reference_arch) RC_DB_URL="$PKGS_DB_BASE/rc/$OBSERVED_ARCH/omarchy.db.tar.zst" @@ -187,12 +185,12 @@ ensure_work_clone() { # Prints omarchy's published version in a channel; empty when absent, rc 2 when # the database cannot be read (callers must not mistake an outage for absence). published_version() { - local channel="$1" tmp descs + local channel="$1" arch="${2:-$OBSERVED_ARCH}" tmp descs tmp=$(mktemp) || return 2 # A unique query string busts the CDN cache: right after a sync the plain # URL can keep serving the previous db for a while, which reads as "not # published yet" to status, the wait loop, and ship's pre-checks. - if ! curl -sf "$PKGS_DB_BASE/$channel/$OBSERVED_ARCH/omarchy.db.tar.zst?$(date +%s%N)" -o "$tmp"; then + if ! curl -sf "$PKGS_DB_BASE/$channel/$arch/omarchy.db.tar.zst?$(date +%s%N)" -o "$tmp"; then rm -f "$tmp" return 2 fi @@ -213,6 +211,14 @@ published_version() { ' <<<"$descs" } +all_arches_at_version() { # all_arches_at_version + local channel="$1" want="$2" arch got + for arch in $(published_arches); do + got=$(published_version "$channel" "$arch" 2>/dev/null) || return 1 + [[ "${got%-*}" == "$want" ]] || return 1 + done +} + # The rc branch of THIS repo carries the current pins. Read them without # touching the working tree. rc_branch_pin() { # prints "pkgver commit", empty when no rc branch @@ -297,12 +303,16 @@ host_advance() { # host_advance [extra args...] } wait_for_published() { # wait_for_published [timeout-seconds] - local channel="$1" want="$2" timeout="${3:-3600}" waited=0 got - print_info "Waiting for $want to appear in the $channel channel (up to $((timeout / 60))m)..." + local channel="$1" want="$2" timeout="${3:-3600}" waited=0 arch got pending + print_info "Waiting for $want in $channel for: $(published_arches | tr '\n' ' ')" while ((waited < timeout)); do - got=$(published_version "$channel" 2>/dev/null) || got="" - if [[ "${got%-*}" == "$want" ]]; then - print_success "$channel now serves omarchy $got" + pending="" + for arch in $(published_arches); do + got=$(published_version "$channel" "$arch" 2>/dev/null) || got="" + [[ "${got%-*}" == "$want" ]] || pending+=" $arch=${got:-unreachable}" + done + if [[ -z "$pending" ]]; then + print_success "$channel now serves omarchy $want on every published architecture" return 0 fi sleep 60 @@ -310,7 +320,7 @@ wait_for_published() { # wait_for_published [timeout-seconds printf '.' >&2 done echo "" >&2 - print_warning "Timed out waiting for $want in $channel (currently: ${got:-unknown})" + print_warning "Timed out waiting for $want in $channel (pending:$pending)" print_info "The build may still be running — re-run this command to resume." return 1 } @@ -674,15 +684,13 @@ cmd_rc() { if [[ -n "$pin" ]]; then local pin_ver="${pin%% *}" pin_commit="${pin##* }" if [[ "$pin_commit" == "$head" && "$pin_ver" =~ ^${version//./\\.}rc[0-9]+$ ]]; then - local pub - pub=$(published_version rc 2>/dev/null) || pub="" - if [[ "${pub%-*}" == "$pin_ver" ]]; then - print_success "$pin_ver is already cut from this head and published to rc" + if all_arches_at_version rc "$pin_ver"; then + print_success "$pin_ver is already cut from this head and published to rc on every architecture" maybe_iso "$pin_ver" rc "$iso_mode" return 0 fi print_info "$pin_ver is pinned from this head but not published yet — re-triggering the build" - trigger_rc_build || true + trigger_rc_build || return 1 [[ "$wait" == true ]] && wait_for_published rc "$pin_ver" maybe_iso "$pin_ver" rc "$iso_mode" return 0 @@ -698,7 +706,7 @@ cmd_rc() { new_ver="${new_pin%% *}" print_success "Pinned $new_ver (rc branch pushed)" - trigger_rc_build || true + trigger_rc_build || return 1 if [[ "$wait" == true ]]; then wait_for_published rc "$new_ver" || return 1 fi @@ -720,9 +728,7 @@ cmd_ship() { exit 1 fi version=$(branch_to_version "$branch") - local stable_now - stable_now=$(published_version stable 2>/dev/null) || stable_now="" - if [[ "${stable_now%-*}" == "$version" ]] && + if all_arches_at_version stable "$version" && gh release view "v$version" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; then print_success "Nothing to ship — $version is tagged, released, and live on stable" exit 0 @@ -764,10 +770,8 @@ cmd_ship() { echo " omarchy-release rc" exit 1 fi - local pub - pub=$(published_version rc 2>/dev/null) || pub="" - if [[ "${pub%-*}" != "$pin_ver" ]]; then - print_error "$pin_ver is pinned but rc serves '${pub:-nothing}' — the candidate build hasn't published" + if ! all_arches_at_version rc "$pin_ver"; then + print_error "$pin_ver is pinned but is not published for every architecture" echo "Wait for it (or re-run: omarchy-release rc), then ship." exit 1 fi @@ -811,10 +815,9 @@ cmd_ship() { # 2. Final pins into rc. Resolve the tag we just established so the final # PKGBUILDs record both its provenance and its exact commit. - local rc_pub stable_pub - rc_pub=$(published_version rc 2>/dev/null) || rc_pub="" - if [[ "${rc_pub%-*}" == "$version" ]]; then - print_success "2/7 Final $version already published to rc" + local stable_pub + if all_arches_at_version rc "$version"; then + print_success "2/7 Final $version already published to rc on every architecture" else if [[ "$pin_ver" != "$version" ]]; then print_info "2/7 Pinning final $version from tag v$version..." @@ -822,22 +825,21 @@ cmd_ship() { else print_info "2/7 Final $version pinned — re-triggering build" fi - trigger_rc_build || true + trigger_rc_build || exit 1 wait_for_published rc "$version" || exit 1 fi # 3. Promote rc -> stable - stable_pub=$(published_version stable 2>/dev/null) || stable_pub="" - if [[ "${stable_pub%-*}" == "$version" ]]; then - print_success "3/7 Stable already serves $version" + if all_arches_at_version stable "$version"; then + print_success "3/7 Stable already serves $version on every architecture" else host_advance rc stable || exit 1 - stable_pub=$(published_version stable 2>/dev/null) || stable_pub="" - if [[ "${stable_pub%-*}" != "$version" ]]; then - print_error "Promotion ran but stable serves '${stable_pub:-nothing}' — investigate before continuing" + if ! all_arches_at_version stable "$version"; then + print_error "Promotion ran but stable does not serve $version on every architecture" exit 1 fi - print_success "3/7 Promoted to stable: omarchy $stable_pub" + stable_pub=$(published_version stable 2>/dev/null) || stable_pub="$version" + print_success "3/7 Promoted to stable: omarchy $stable_pub on every architecture" fi # 4. Final pins onto master (keeps edge overlap publishing and the repo record) @@ -920,7 +922,7 @@ next_step() { # prints "|" last=$(newest_release_branch 2>/dev/null) || last="" if [[ -n "$last" && "$STABLE_VER" != "" ]]; then last_ver=$(branch_to_version "$last") - if [[ "${STABLE_VER%-*}" != "$last_ver" ]] || + if ! all_arches_at_version stable "$last_ver" || { command -v gh >/dev/null && ! gh release view "v$last_ver" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; }; then echo "ship|$last_ver is tagged but not fully shipped — resume ship" return @@ -936,7 +938,7 @@ next_step() { # prints "|" echo "ship|Final $TRAIN_VER is pinned — finish shipping (re-runs are safe)" elif [[ "$pin_commit" != "$TRAIN_HEAD" ]]; then echo "rc|$TRAIN has commits newer than $pin_ver — cut the next candidate" - elif [[ "${RC_VER%-*}" != "$pin_ver" ]]; then + elif ! all_arches_at_version rc "$pin_ver"; then echo "rc|$pin_ver is pinned but not published — re-run rc to re-trigger/wait" else echo "ship|$pin_ver is published to rc — test it, then ship" @@ -1006,20 +1008,24 @@ cmd_doctor() { check "makepkg available (checksums)" command -v makepkg check "curl available" command -v curl check "upstream reachable ($UPSTREAM_URL)" git ls-remote "$UPSTREAM_URL" HEAD - local ch + local ch arch for ch in edge stable; do - if published_version "$ch" >/dev/null 2>&1; then - print_success "$ch channel db readable" + for arch in $(published_arches); do + if published_version "$ch" "$arch" >/dev/null 2>&1; then + print_success "$ch/$arch channel db readable" + else + print_error "$ch/$arch channel db readable" + failures=$((failures + 1)) + fi + done + done + for arch in $(published_arches); do + if published_version rc "$arch" >/dev/null 2>&1; then + print_success "rc/$arch channel db readable" else - print_error "$ch channel db readable" - failures=$((failures + 1)) + print_warning "rc/$arch channel db not readable — bootstrap it first: bin/repo bootstrap-rc (on the build host)" fi done - if published_version rc >/dev/null 2>&1; then - print_success "rc channel db readable" - else - print_warning "rc channel db not readable — bootstrap it first: bin/repo bootstrap-rc (on the build host)" - fi local host if host=$(repo_host); then check "build host ssh ($host)" ssh -o ConnectTimeout=10 "$host" true @@ -1066,6 +1072,25 @@ cmd_self_test() { expect "version_is_patch 5.0.0" "$(version_is_patch 5.0.0 && echo yes || echo no)" "no" expect "previous_patch_tag 4.0.2" "$(previous_patch_tag 4.0.2)" "v4.0.1" expect "previous_patch_tag 4.0.10" "$(previous_patch_tag 4.0.10)" "v4.0.9" + + # Keep release readiness fail-closed when only one architecture has reached + # the requested version. This replaces the network reader for this process; + # self-test exits immediately afterwards. + published_version() { + case "$2" in + x86_64) echo "${TEST_X86_VERSION:-4.0.2-1}" ;; + aarch64) echo "${TEST_ARM_VERSION:-4.0.2-1}" ;; + esac + } + PUBLISHED_ARCHES=x86_64 + expect "x86-only readiness" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "ready" + PUBLISHED_ARCHES=aarch64 + expect "ARM-only readiness" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "ready" + PUBLISHED_ARCHES="x86_64 aarch64" + TEST_ARM_VERSION=4.0.1-1 + expect "mixed versions block release" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "blocked" + TEST_ARM_VERSION=4.0.2-1 + expect "both architectures ready" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "ready" echo "" if ((failures == 0)); then print_success "Self-test passed" diff --git a/bin/remove-package b/bin/remove-package index 6c798377..eedcfb03 100755 --- a/bin/remove-package +++ b/bin/remove-package @@ -88,9 +88,12 @@ if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 0 fi -# Build/update the Docker image (always use x86_64 for removal - it's architecture independent) -# repo-remove is mirror-independent — always use the edge x86_64 image -build_docker_image "$BUILD_DIR" "x86_64" "edge" +# repo-remove is architecture-independent, so use a host-native edge image. +TOOL_ARCH=$(docker_native_arch) || { + print_error "Unsupported host architecture: $(uname -m)" + exit 1 +} +build_docker_image "$BUILD_DIR" "$TOOL_ARCH" "edge" acquire_release_lock || exit 1 @@ -99,13 +102,13 @@ print_info "Removing package..." # Ensure directory is writable by container user make_dir_writable "$REPO_DIR" -# Run the removal script in Docker (always use x86_64 image) -docker run --rm --platform linux/amd64 \ +# Run the removal script in the host-native image. +docker run --rm "$(get_platform_arg "$TOOL_ARCH")" \ -e ARCH="$ARCH" \ -e MIRROR="$MIRROR" \ -v "$REPO_ROOT:/pkgs.omarchy.org" \ -v "$BUILD_DIR:/build:ro" \ - omarchy-pkg-builder:latest-x86_64-edge /build/remove-package.sh "$PACKAGE_NAME" + "omarchy-pkg-builder:latest-$TOOL_ARCH-edge" /build/remove-package.sh "$PACKAGE_NAME" RESULT=$? diff --git a/bin/setup b/bin/setup index 014dd2f8..6727d96c 100755 --- a/bin/setup +++ b/bin/setup @@ -15,6 +15,7 @@ set -e BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/paths.sh" CHECK_ONLY=false SKIP_TIMERS=false @@ -238,7 +239,7 @@ echo "" # --- release timers ---------------------------------------------------------- print_info "Published architectures: $(published_arches | tr '\n' ' ')" -echo " (PUBLISHED_ARCHES in helpers/paths.sh, or OMARCHY_ARCHES in $CREDENTIALS)" +echo " (PUBLISHED_ARCHES in helpers/paths.sh; OMARCHY_ARCHES overrides a one-off command)" echo "" TIMERS=(omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-rc omarchy-auto-release-stable) diff --git a/bin/sign b/bin/sign index 70779e8c..c517b9ba 100755 --- a/bin/sign +++ b/bin/sign @@ -66,23 +66,28 @@ if [[ -z "$GPG_PASSPHRASE" ]]; then exit 1 fi -# Build/update the Docker image (always use x86_64 for signing - it's architecture independent) -build_docker_image "$BUILD_DIR" "x86_64" "$MIRROR" +# Signing is architecture-independent, so run its utility container natively +# on either an x86_64 or ARM host. +TOOL_ARCH=$(docker_native_arch) || { + print_error "Unsupported host architecture: $(uname -m)" + exit 1 +} +build_docker_image "$BUILD_DIR" "$TOOL_ARCH" "$MIRROR" print_info "Running package signing..." # Ensure output directory is writable by container user make_dir_writable "$BUILD_OUTPUT_DIR" -# Run the signing script in Docker (always use x86_64 image) -docker run --rm --platform linux/amd64 \ +# Run the signing script in the host-native image. +docker run --rm "$(get_platform_arg "$TOOL_ARCH")" \ -e ARCH="$ARCH" \ -e MIRROR="$MIRROR" \ -e GPG_PRIVATE_KEY="$GPG_PRIVATE_KEY" \ -e GPG_PASSPHRASE="$GPG_PASSPHRASE" \ -v "$BUILD_ROOT/build-output:/build-output" \ -v "$BUILD_DIR:/build:ro" \ - omarchy-pkg-builder:latest-x86_64-$MIRROR /build/sign.sh + "omarchy-pkg-builder:latest-$TOOL_ARCH-$MIRROR" /build/sign.sh SIGN_RESULT=$? diff --git a/bin/sync-rebuilds b/bin/sync-rebuilds index 7a0336b4..b9aa81d1 100755 --- a/bin/sync-rebuilds +++ b/bin/sync-rebuilds @@ -17,11 +17,14 @@ SELF_TEST=false # rebuilding for it would ship a package built against the wrong ABI. OFFICIAL_REPOS=" core extra multilib core-debug extra-debug " -# The published repository, used as the floor a bumped pkgrel has to clear. -# Only x86_64 is published today; aarch64 has no repository to compare against. +# The published repositories are the floor a bumped pkgrel has to clear. PUBLISHED_BASE_URL="${OMARCHY_PUBLISHED_BASE_URL:-https://pkgs.omarchy.org}" PUBLISHED_MIRRORS=(edge stable) -PUBLISHED_ARCH=x86_64 + +# Arch Linux ARM has no dated snapshots. This is the same live repository the +# aarch64 builder resolves; override it only when the builder mirror changes. +ALARM_BASE_URL="${OMARCHY_ALARM_BASE_URL:-https://fl.us.mirror.archlinuxarm.org/aarch64}" +ALARM_REPOS=(core extra alarm aur) usage() { cat </dev/null; then + print_error "Could not read the aarch64 $repo repository database" + return 1 + fi + if ! tar -tf "$db" >/dev/null 2>&1; then + print_error "Unreadable aarch64 $repo repository database" + return 1 + fi + + tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk ' + function emit() { + if (name != "" && version != "") { + print name "\t" version + if (base != "" && base != name) print base "\t" version + } + name=""; base=""; version="" + } + $0 == "%FILENAME%" { emit(); next } + $0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next } + $0 == "%BASE%" { getline; base=$0; next } + $0 == "%VERSION%" { getline; version=$0; next } + END { emit() } + ' >"$index" +} + +alarm_repo_version() { + local package="$1" repo version + for repo in "${ALARM_REPOS[@]}"; do + load_alarm_repo "$repo" || return 1 + version=$(awk -F '\t' -v package="$package" '$1 == package { print $2; exit }' "$TEMP_DIR/alarm-$repo.index") + if [[ -n "$version" ]]; then + echo "$version" + return 0 + fi + done +} + +repo_version() { # repo_version + case "$1" in + x86_64) native_repo_version "$2" ;; + aarch64) alarm_repo_version "$2" ;; + *) return 1 ;; + esac +} + declare -A PUBLISHED_VERSION=() PUBLISHED_LOADED=false -PUBLISHED_AVAILABLE=true remember_published() { local name="$1" @@ -131,34 +188,35 @@ load_published_versions() { [[ "$PUBLISHED_LOADED" == true ]] && return 0 PUBLISHED_LOADED=true - local mirror db name base version + local arch mirror db name base version - for mirror in "${PUBLISHED_MIRRORS[@]}"; do - db="$TEMP_DIR/published-$mirror.db.tar.zst" + for arch in $(published_arches); do + for mirror in "${PUBLISHED_MIRRORS[@]}"; do + db="$TEMP_DIR/published-$mirror-$arch.db.tar.zst" - if ! curl -fsSL --max-time 120 -o "$db" \ - "$PUBLISHED_BASE_URL/$mirror/$PUBLISHED_ARCH/omarchy.db.tar.zst" 2>/dev/null; then - print_warning "Could not read the published $mirror database; bumps are not checked against it this run" - PUBLISHED_AVAILABLE=false - continue - fi + if ! curl -fsSL --max-time 120 -o "$db" \ + "$PUBLISHED_BASE_URL/$mirror/$arch/omarchy.db.tar.zst" 2>/dev/null; then + print_warning "Could not read the published $mirror/$arch database; bumps are not checked against it this run" + continue + fi - while IFS=$'\t' read -r name base version; do - [[ -n "$name" && -n "$version" ]] && remember_published "$name" "$version" - [[ -n "$base" && -n "$version" ]] && remember_published "$base" "$version" - done < <( - tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk ' - function emit() { - if (name != "" && version != "") print name "\t" base "\t" version - name=""; base=""; version="" - } - $0 == "%FILENAME%" { emit(); next } - $0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next } - $0 == "%BASE%" { getline; base=$0; next } - $0 == "%VERSION%" { getline; version=$0; next } - END { emit() } - ' - ) + while IFS=$'\t' read -r name base version; do + [[ -n "$name" && -n "$version" ]] && remember_published "$name" "$version" + [[ -n "$base" && -n "$version" ]] && remember_published "$base" "$version" + done < <( + tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk ' + function emit() { + if (name != "" && version != "") print name "\t" base "\t" version + name=""; base=""; version="" + } + $0 == "%FILENAME%" { emit(); next } + $0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next } + $0 == "%BASE%" { getline; base=$0; next } + $0 == "%VERSION%" { getline; version=$0; next } + END { emit() } + ' + ) + done done } @@ -252,7 +310,17 @@ record_triggers() { local package_dir="$1" local current="$2" - write_metadata "$package_dir" '.rebuilt_against = $current' --argjson current "$current" + # A flat record is the legacy x86_64 shape. Preserve records for + # architectures outside this run, then replace the ones just rebuilt. + write_metadata "$package_dir" ' + (.rebuilt_against // {}) as $old | + (if ($old | length) == 0 then {} + elif ($old | to_entries | all(.value | type == "string")) + then {x86_64: $old} + else $old + end) as $by_arch | + .rebuilt_against = ($by_arch * $current) + ' --argjson current "$current" } # Metadata that does not parse would otherwise drop its package out of the run @@ -300,21 +368,41 @@ sync_package() { print_info "Checking $package against ${triggers[*]}..." - local current="{}" trigger version - for trigger in "${triggers[@]}"; do - version=$(repo_version "$trigger") - if [[ -z "$version" ]]; then - print_error " $trigger is in no official repository; leaving $package alone" - ((++FAILED)) - return 0 - fi - if ! current=$(jq -c --arg name "$trigger" --arg version "$version" '.[$name] = $version' <<<"$current"); then - print_error " Could not record $trigger $version for $package" + local current="{}" arch arch_current trigger version considered=0 + for arch in $(published_arches); do + package_supports_arch "$package_dir" "$arch" || continue + considered=$((considered + 1)) + arch_current="{}" + for trigger in "${triggers[@]}"; do + if ! version=$(repo_version "$arch" "$trigger"); then + print_error " Could not read $arch repository versions; leaving $package alone" + ((++FAILED)) + return 0 + fi + if [[ -z "$version" ]]; then + print_error " $trigger is in no $arch repository; leaving $package alone" + ((++FAILED)) + return 0 + fi + if ! arch_current=$(jq -c --arg name "$trigger" --arg version "$version" '.[$name] = $version' <<<"$arch_current"); then + print_error " Could not record $arch/$trigger $version for $package" + ((++FAILED)) + return 0 + fi + done + if ! current=$(jq -c --arg arch "$arch" --argjson versions "$arch_current" '.[$arch] = $versions' <<<"$current"); then + print_error " Could not record $arch trigger versions for $package" ((++FAILED)) return 0 fi done + if ((considered == 0)); then + print_info " Skipping: not built for any published architecture" + ((++SKIPPED)) + return 0 + fi + local recorded if ! recorded=$(package_metadata_value "$package_dir" '.rebuilt_against' ""); then print_error " Could not read .omarchy/package.json for $package" @@ -323,13 +411,20 @@ sync_package() { fi [[ -n "$recorded" && "$recorded" != "null" ]] || recorded="{}" + # Before architecture-specific records existed, rebuilt_against described + # x86_64. Read it that way without forcing a metadata-only migration. + if jq -e 'to_entries | all(.value | type == "string")' >/dev/null <<<"$recorded"; then + recorded=$(jq -c '{x86_64: .}' <<<"$recorded") + fi + # Walk the declared triggers rather than the record, so a name the record does # not carry reads as changed instead of going unexamined forever. local moved if ! moved=$(jq -r --argjson recorded "$recorded" ' - to_entries - | map(select($recorded[.key] != .value) - | "\(.key) \($recorded[.key] // "unrecorded") -> \(.value)") + [to_entries[] as $arch + | $arch.value | to_entries[] as $trigger + | select($recorded[$arch.key][$trigger.key] != $trigger.value) + | "\($arch.key)/\($trigger.key) \($recorded[$arch.key][$trigger.key] // "unrecorded") -> \($trigger.value)"] | join(", ") ' <<<"$current"); then print_error " Could not compare recorded trigger versions for $package" @@ -338,7 +433,7 @@ sync_package() { fi if [[ -z "$moved" ]]; then - print_info " Already rebuilt against $(jq -r 'to_entries | map("\(.key) \(.value)") | join(", ")' <<<"$current")" + print_info " Already rebuilt against every published architecture" ((++SKIPPED)) return 0 fi @@ -466,11 +561,11 @@ selftest_root() { } selftest_package() { - local root="$1" name="$2" pkgrel="$3" metadata="$4" pkgver="${5:-1.0}" + local root="$1" name="$2" pkgrel="$3" metadata="$4" pkgver="${5:-1.0}" arches="${6:-x86_64}" local dir="$root/pkgbuilds/$name" mkdir -p "$dir/.omarchy" - printf 'pkgname=%s\npkgver=%s\npkgrel=%s\narch=(x86_64)\n' "$name" "$pkgver" "$pkgrel" > "$dir/PKGBUILD" + printf 'pkgname=%s\npkgver=%s\npkgrel=%s\narch=(%s)\n' "$name" "$pkgver" "$pkgrel" "$arches" > "$dir/PKGBUILD" printf '%s\n' "$metadata" > "$dir/.omarchy/package.json" } @@ -514,19 +609,51 @@ selftest_published() { cat > "$root/stub/curl" <<'STUB' #!/bin/bash out="" +url="" while [[ $# -gt 0 ]]; do case "$1" in -o) out="$2"; shift 2 ;; - *) shift ;; + *) url="$1"; shift ;; esac done -db="$(dirname "$0")/omarchy.db.tar.zst" +if [[ "$url" == */aarch64/* ]]; then + repo="${url%/*}" + repo="${repo##*/}" + db="$(dirname "$0")/alarm-$repo.db" +else + db="$(dirname "$0")/omarchy.db.tar.zst" +fi [[ -f "$db" && -n "$out" ]] || exit 22 cp "$db" "$out" STUB chmod +x "$root/stub/curl" } +selftest_alarm() { + local root="$1" + shift + local repo staging="$root/stub/alarm-db" entry name version + + for repo in core extra; do + rm -rf "$staging" + mkdir -p "$staging" + if [[ "$repo" == "core" ]]; then + mkdir -p "$staging/unrelated-1-1" + printf '%%FILENAME%%\nunrelated-1-1-aarch64.pkg.tar.zst\n\n%%NAME%%\nunrelated\n\n%%BASE%%\nunrelated\n\n%%VERSION%%\n1-1\n' \ + > "$staging/unrelated-1-1/desc" + else + for entry in "$@"; do + name="${entry%=*}" + version="${entry#*=}" + mkdir -p "$staging/$name-$version" + printf '%%FILENAME%%\n%s-%s-aarch64.pkg.tar.zst\n\n%%NAME%%\n%s\n\n%%BASE%%\n%s\n\n%%VERSION%%\n%s\n' \ + "$name" "$version" "$name" "$name" "$version" > "$staging/$name-$version/desc" + done + fi + tar -czf "$root/stub/alarm-$repo.db" -C "$staging" . + done +} + cmd_self_test() { local failures=0 local root @@ -545,7 +672,8 @@ cmd_self_test() { local root="$1" shift local status=0 - PATH="$root/stub:$PATH" "$root/bin/sync-rebuilds" "$@" > "$root/output" 2>&1 || status=$? + OMARCHY_ARCHES="${SELFTEST_ARCHES:-x86_64}" \ + PATH="$root/stub:$PATH" "$root/bin/sync-rebuilds" "$@" > "$root/output" 2>&1 || status=$? echo "$status" } @@ -563,7 +691,7 @@ cmd_self_test() { check "run succeeds" 0 "$(run_case "$root")" check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-partial")" check "unrecorded trigger now recorded" "2-2" \ - "$(jq -r '.rebuilt_against["dep-b"]' "$root/pkgbuilds/t-partial/.omarchy/package.json")" + "$(jq -r '.rebuilt_against.x86_64["dep-b"]' "$root/pkgbuilds/t-partial/.omarchy/package.json")" echo "Opting a package in buys a rebuild rather than a bare record:" root=$(selftest_root fresh) @@ -573,7 +701,7 @@ cmd_self_test() { check "run succeeds" 0 "$(run_case "$root")" check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-fresh")" check "trigger recorded" "1-1" \ - "$(jq -r '.rebuilt_against["dep-a"]' "$root/pkgbuilds/t-fresh/.omarchy/package.json")" + "$(jq -r '.rebuilt_against.x86_64["dep-a"]' "$root/pkgbuilds/t-fresh/.omarchy/package.json")" echo "An unchanged package is left alone:" root=$(selftest_root current) @@ -612,6 +740,47 @@ cmd_self_test() { check "suffix recorded for the next AUR sync" 1 \ "$(jq -r '.pkgrel.suffix' "$root/pkgbuilds/t-aur/.omarchy/package.json")" + echo "A dependency is tracked independently for both published architectures:" + root=$(selftest_root multiarch) + selftest_package "$root" t-multi 1 '{"source":"local","rebuild_on":["dep-a"],"rebuilt_against":{"dep-a":"1-1"}}' 1.0 'x86_64 aarch64' + selftest_pacman "$root" dep-a=1-1 + selftest_published "$root" + selftest_alarm "$root" dep-a=2-1 + SELFTEST_ARCHES="x86_64 aarch64" + check "run succeeds" 0 "$(run_case "$root")" + unset SELFTEST_ARCHES + check "pkgrel bumped once" 2 "$(pkgrel_of "$root/pkgbuilds/t-multi")" + check "x86_64 trigger recorded" "1-1" \ + "$(jq -r '.rebuilt_against.x86_64["dep-a"]' "$root/pkgbuilds/t-multi/.omarchy/package.json")" + check "aarch64 trigger recorded" "2-1" \ + "$(jq -r '.rebuilt_against.aarch64["dep-a"]' "$root/pkgbuilds/t-multi/.omarchy/package.json")" + + echo "An ARM-only run records only the ARM dependency state:" + root=$(selftest_root arm-only) + selftest_package "$root" t-arm 1 '{"source":"local","rebuild_on":["dep-a"]}' 1.0 'x86_64 aarch64' + selftest_pacman "$root" + selftest_published "$root" + selftest_alarm "$root" dep-a=2-1 + SELFTEST_ARCHES=aarch64 + check "run succeeds" 0 "$(run_case "$root")" + unset SELFTEST_ARCHES + check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-arm")" + check "ARM trigger recorded" "2-1" \ + "$(jq -r '.rebuilt_against.aarch64["dep-a"]' "$root/pkgbuilds/t-arm/.omarchy/package.json")" + check "x86_64 was not consulted" "false" \ + "$(jq -r '.rebuilt_against | has("x86_64")' "$root/pkgbuilds/t-arm/.omarchy/package.json")" + + echo "An x86-only package ignores ARM during a dual-architecture run:" + root=$(selftest_root x86-package) + selftest_package "$root" t-x86 1 '{"source":"local","rebuild_on":["dep-a"],"rebuilt_against":{"dep-a":"1-1"}}' + selftest_pacman "$root" dep-a=1-1 + selftest_published "$root" + selftest_alarm "$root" dep-a=2-1 + SELFTEST_ARCHES="x86_64 aarch64" + check "run succeeds" 0 "$(run_case "$root")" + unset SELFTEST_ARCHES + check "pkgrel untouched" 1 "$(pkgrel_of "$root/pkgbuilds/t-x86")" + echo "" if [[ "$failures" -eq 0 ]]; then print_success "Self-test passed" @@ -626,9 +795,9 @@ if [[ "$SELF_TEST" == true ]]; then exit $? fi -for tool in pacman vercmp jq curl; do +for tool in pacman vercmp jq curl tar; do if ! command -v "$tool" >/dev/null 2>&1; then - print_error "$tool not found: reading trigger versions and ordering pkgrels both need pacman" + print_error "$tool not found: rebuild trigger sync cannot run" exit 1 fi done diff --git a/bin/timers b/bin/timers index 1cb2f4b9..4b39a292 100755 --- a/bin/timers +++ b/bin/timers @@ -105,32 +105,32 @@ echo "" paused=false for channel in edge rc stable; do for arch in $(published_arches); do - fail_file=$(sync_fail_file "$channel" "$arch") - [[ "$arch" == "x86_64" && ! -f "$fail_file" ]] && fail_file=$(legacy_sync_fail_file "$channel") - [[ -f "$fail_file" ]] || continue - if [[ "$paused" == false ]]; then - print_error "Failing builds (backoff active)" - paused=true - fi - FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT="" - # shellcheck disable=SC1090 - source "$fail_file" 2>/dev/null || true - delay=600 - for ((i = 1; i < FAILURE_COUNT; i++)); do - delay=$((delay * 2)) - ((delay >= 21600)) && { delay=21600; break; } - done - retry_at=$((FAILURE_AT + delay)) - now=$(date +%s) - if ((now < retry_at)); then - when="retries at $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")" - else - when="retries on the next tick" - fi - printf ' ✗ %-7s %-8s %s consecutive failure(s), %s\n' "$channel" "$arch" "$FAILURE_COUNT" "$when" - printf ' last attempt %s on commit %s\n' \ - "$(date -d "@$FAILURE_AT" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$FAILURE_AT")" \ - "${FAILURE_FINGERPRINT:0:12}" + fail_file=$(sync_fail_file "$channel" "$arch") + [[ "$arch" == "x86_64" && ! -f "$fail_file" ]] && fail_file=$(legacy_sync_fail_file "$channel") + [[ -f "$fail_file" ]] || continue + if [[ "$paused" == false ]]; then + print_error "Failing builds (backoff active)" + paused=true + fi + FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT="" + # shellcheck disable=SC1090 + source "$fail_file" 2>/dev/null || true + delay=600 + for ((i = 1; i < FAILURE_COUNT; i++)); do + delay=$((delay * 2)) + ((delay >= 21600)) && { delay=21600; break; } + done + retry_at=$((FAILURE_AT + delay)) + now=$(date +%s) + if ((now < retry_at)); then + when="retries at $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")" + else + when="retries on the next tick" + fi + printf ' ✗ %-7s %-8s %s consecutive failure(s), %s\n' "$channel" "$arch" "$FAILURE_COUNT" "$when" + printf ' last attempt %s on commit %s\n' \ + "$(date -d "@$FAILURE_AT" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$FAILURE_AT")" \ + "${FAILURE_FINGERPRINT:0:12}" done done if [[ "$paused" == true ]]; then diff --git a/bin/update-repo b/bin/update-repo index 34d9f512..894e1972 100755 --- a/bin/update-repo +++ b/bin/update-repo @@ -28,17 +28,21 @@ update_database() { # Make output directory writable for container make_dir_writable "$REPO_DIR" - # repo-add is architecture- and mirror-independent, so always use the edge - # x86_64 image. This also lets bootstrap-rc build the rc database before the - # rc channel exists remotely (an rc image can only build after it does). - build_docker_image "$BUILD_DIR" "x86_64" "edge" + # repo-add is architecture- and mirror-independent. Use the host-native edge + # image, which also lets bootstrap-rc run before that channel exists remotely. + local tool_arch + tool_arch=$(docker_native_arch) || { + print_error "Unsupported host architecture: $(uname -m)" + exit 1 + } + build_docker_image "$BUILD_DIR" "$tool_arch" "edge" - docker run --rm --platform linux/amd64 \ + docker run --rm "$(get_platform_arg "$tool_arch")" \ -e ARCH="$ARCH" \ -e MIRROR="$MIRROR" \ -v "$REPO_ROOT:/output" \ -v "$BUILD_DIR:/build:ro" \ - omarchy-pkg-builder:latest-x86_64-edge /build/update-repo.sh + "omarchy-pkg-builder:latest-$tool_arch-edge" /build/update-repo.sh } # Main execution diff --git a/build/Dockerfile b/build/Dockerfile index 258fec4e..af1bb5e9 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -45,9 +45,7 @@ RUN if [ "${TARGETARCH}" = "amd64" ]; then \ printf 'Server = https://mirror.omarchy.org/$repo/os/$arch\n' > /etc/pacman.d/mirrorlist; \ fi; \ else \ - curl -L "https://raw.githubusercontent.com/archlinuxarm/PKGBUILDs/master/core/pacman-mirrorlist/mirrorlist" 2>/dev/null | \ - sed -E 's/^\s*#\s*Server\s*=/Server =/g' > /etc/pacman.d/mirrorlist && \ - sed -i 's/\$arch/aarch64/g' /etc/pacman.d/mirrorlist; \ + printf 'Server = https://fl.us.mirror.archlinuxarm.org/aarch64/$repo\n' > /etc/pacman.d/mirrorlist; \ fi # Bootstrap keyrings (required before pacstrap can verify packages) diff --git a/build/build.sh b/build/build.sh index e0f69acb..8534ee9c 100755 --- a/build/build.sh +++ b/build/build.sh @@ -186,26 +186,9 @@ get_local_version() { # Returns 0 (success) if should build, 1 if should skip should_build_for_arch() { local pkg="$1" - local current_arch="$ARCH" - local pkgdir=$(find_package_dir "$pkg") - local pkgbuild="$pkgdir/PKGBUILD" - - [[ ! -f "$pkgbuild" ]] && return 1 - - # Check PKGBUILD arch=() array - local pkgbuild_archs=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${arch[@]}"') - - # If arch=('any'), build for all architectures - if [[ "$pkgbuild_archs" == "any" ]]; then - return 0 - fi - - # Check if current arch is in PKGBUILD arch=() - if echo "$pkgbuild_archs" | grep -qw "$current_arch"; then - return 0 # Build - else - return 1 # Skip - fi + local pkgdir + pkgdir=$(find_package_dir "$pkg") + [[ -n "$pkgdir" ]] && package_supports_arch "$pkgdir" "$ARCH" } # For VCS packages, makepkg recalculates pkgver() before the build. If the @@ -488,7 +471,7 @@ check_needs_build() { # Collect packages that should be built for the selected mirror collect_packages() { - packages_for_unscoped_build "$MIRROR" + packages_for_unscoped_build "$MIRROR" "$ARCH" } # Main execution @@ -540,13 +523,6 @@ if [[ -n "$PACKAGES" ]]; then else # Build all packages that need updates from the relevant directories while IFS= read -r pkg; do - # Check if package should be built for this architecture - if ! should_build_for_arch "$pkg"; then - echo " - $pkg - not built for $ARCH" - SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg" - continue - fi - if check_needs_build "$pkg"; then PACKAGES_TO_BUILD+=("$pkg") else diff --git a/helpers/docker-helpers.sh b/helpers/docker-helpers.sh index 4a269041..c108340e 100644 --- a/helpers/docker-helpers.sh +++ b/helpers/docker-helpers.sh @@ -13,13 +13,21 @@ check_docker() { fi } +docker_native_arch() { + case "$(uname -m)" in + x86_64) echo x86_64 ;; + aarch64 | arm64) echo aarch64 ;; + *) return 1 ;; + esac +} + setup_qemu() { - # Setup QEMU for building ARM64 packages on x86_64 hosts + # Register emulators for builds whose target differs from the host. if ! docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes >/dev/null 2>&1; then - print_error "Failed to setup QEMU for ARM64 emulation" + print_error "Failed to set up QEMU emulation" exit 1 fi - print_success "QEMU ARM64 emulation enabled" + print_success "QEMU emulation enabled" } build_docker_image() { @@ -53,9 +61,9 @@ build_docker_image() { get_platform_arg() { local arch="$1" case "$arch" in - x86_64) echo "--platform linux/amd64" ;; - aarch64) echo "--platform linux/arm64" ;; - *) echo "" ;; + x86_64) echo "--platform=linux/amd64" ;; + aarch64) echo "--platform=linux/arm64" ;; + *) return 1 ;; esac } diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index 90961030..7382bbf2 100644 --- a/helpers/package-metadata.sh +++ b/helpers/package-metadata.sh @@ -143,6 +143,31 @@ package_has_pkgbuild() { [[ -f "$pkgdir/PKGBUILD" ]] } +# The architectures declared by a PKGBUILD. Set CARCH while reading it so a +# conditional arch=() assignment is evaluated for the architecture we are +# actually checking, even when the repository host is a different one. +package_arches() { + local pkgdir="$1" + local arch="${2:-${ARCH:-x86_64}}" + + (cd "$pkgdir" && env -u OMARCHY_SRC CARCH="$arch" bash -c ' + source PKGBUILD >/dev/null 2>&1 + printf "%s\n" "${arch[*]}" + ') +} + +package_supports_arch() { + local pkgdir="$1" + local target="${2:-${ARCH:-x86_64}}" + local arches + + arches=$(package_arches "$pkgdir" "$target") || return 1 + case " $arches " in + *" any "* | *" $target "*) return 0 ;; + *) return 1 ;; + esac +} + # Channel membership: where a package may be published. Packages without a # `channels` key are members of every channel (they flow edge -> rc -> stable). package_has_channels() { @@ -301,9 +326,12 @@ packages_for_mirror() { packages_for_unscoped_build() { local mirror="$1" + local arch="${2:-${ARCH:-x86_64}}" package_dirs | while IFS= read -r pkgdir; do - if package_builds_for_mirror "$pkgdir" "$mirror" && ! package_build_skipped "$pkgdir"; then + if package_builds_for_mirror "$pkgdir" "$mirror" && + ! package_build_skipped "$pkgdir" && + package_supports_arch "$pkgdir" "$arch"; then basename "$pkgdir" fi done @@ -502,12 +530,28 @@ validate_package_metadata() { return 1 fi - if ! jq -e '(.rebuilt_against // {}) | type == "object" and (to_entries | all(.value | type == "string" and length > 0))' "$metadata" >/dev/null; then - echo "invalid rebuilt_against for $(basename "$pkgdir"): must be an object mapping package names to versions" + if ! jq -e ' + def version_map: + type == "object" and (to_entries | all(.value | type == "string" and length > 0)); + (.rebuilt_against // {}) as $record | + ($record | version_map) or + (($record | type) == "object" + and ((($record | keys) - ["x86_64", "aarch64"]) | length == 0) + and ($record | to_entries | all(.value | version_map))) + ' "$metadata" >/dev/null; then + echo "invalid rebuilt_against for $(basename "$pkgdir"): must map architectures to package-version maps" return 1 fi - if ! jq -e '((.rebuilt_against // {}) | keys) - (.rebuild_on // []) | length == 0' "$metadata" >/dev/null; then + if ! jq -e ' + (.rebuild_on // []) as $triggers | + (.rebuilt_against // {}) as $record | + if ($record | to_entries | all(.value | type == "string")) then + ((($record | keys) - $triggers) | length == 0) + else + ($record | to_entries | all((((.value | keys) - $triggers) | length) == 0)) + end + ' "$metadata" >/dev/null; then echo "invalid rebuilt_against for $(basename "$pkgdir"): records a package that rebuild_on does not name" return 1 fi diff --git a/helpers/paths.sh b/helpers/paths.sh index c78db22b..5d6191c3 100644 --- a/helpers/paths.sh +++ b/helpers/paths.sh @@ -13,8 +13,8 @@ VALID_ARCHES="x86_64 aarch64" # in this order; the first entry is the reference architecture that the # release train observes channels through. Adding an architecture here is the # enablement step: the next check-versions tick queues its packages and the -# next auto-release tick builds them. OMARCHY_ARCHES overrides it for a host -# or a one-off run. +# next auto-release tick builds them. OMARCHY_ARCHES overrides it for a +# one-off run. PUBLISHED_ARCHES="${OMARCHY_ARCHES:-x86_64}" validate_arch() { diff --git a/systemd/omarchy-auto-release-edge.service b/systemd/omarchy-auto-release-edge.service index 7658dfb7..3537ed95 100644 --- a/systemd/omarchy-auto-release-edge.service +++ b/systemd/omarchy-auto-release-edge.service @@ -7,7 +7,7 @@ Wants=network-online.target Type=oneshot ExecStart=/bin/bash -c 'source /root/.omarchy/build-credentials && /root/omarchy-pkgs/bin/auto-release edge' Environment=OMARCHY_STATE_DIR=/root/.state -TimeoutStartSec=7200 +TimeoutStartSec=43200 [Install] WantedBy=multi-user.target diff --git a/systemd/omarchy-auto-release-rc.service b/systemd/omarchy-auto-release-rc.service index 0078b3d1..c57f5f73 100644 --- a/systemd/omarchy-auto-release-rc.service +++ b/systemd/omarchy-auto-release-rc.service @@ -12,7 +12,7 @@ Type=oneshot # branch's worktree with OMARCHY_RC_PINS=1. ExecStart=/bin/bash -c 'source /root/.omarchy/build-credentials && /root/omarchy-pkgs/bin/auto-release rc' Environment=OMARCHY_STATE_DIR=/root/.state -TimeoutStartSec=7200 +TimeoutStartSec=43200 [Install] WantedBy=multi-user.target diff --git a/systemd/omarchy-auto-release-stable.service b/systemd/omarchy-auto-release-stable.service index e1979e3a..110bad3b 100644 --- a/systemd/omarchy-auto-release-stable.service +++ b/systemd/omarchy-auto-release-stable.service @@ -7,7 +7,7 @@ Wants=network-online.target Type=oneshot ExecStart=/bin/bash -c 'source /root/.omarchy/build-credentials && /root/omarchy-pkgs/bin/auto-release stable' Environment=OMARCHY_STATE_DIR=/root/.state -TimeoutStartSec=7200 +TimeoutStartSec=43200 [Install] WantedBy=multi-user.target From 9e9acb071a6c553a572a45ba0c3cd0c766392d80 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Fri, 4 Sep 2026 14:17:55 -0400 Subject: [PATCH 27/63] Support rootless Podman builds --- bin/build | 31 ++++++++---- bin/clean-docker | 20 +++++--- bin/omarchy-release | 11 +++-- bin/remove-package | 12 +++-- bin/setup | 76 ++++++++++++++++++++--------- bin/sign | 12 +++-- bin/update-repo | 14 +++--- helpers/docker-helpers.sh | 100 +++++++++++++++++++++++++++++++------- 8 files changed, 198 insertions(+), 78 deletions(-) diff --git a/bin/build b/bin/build index 2d32294b..7e6ae58d 100755 --- a/bin/build +++ b/bin/build @@ -145,7 +145,7 @@ if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then fi if [[ "$DRY_RUN" == true ]]; then - print_warning "DRY RUN MODE - build plan only; no Docker or makepkg will run" + print_warning "DRY RUN MODE - build plan only; no container or makepkg will run" ARCH="$ARCH" \ MIRROR="$MIRROR" \ PACKAGES="$PACKAGES" \ @@ -163,8 +163,8 @@ fi # Create directories if they don't exist mkdir -p "$BUILD_OUTPUT_DIR" "$REPO_DIR" "$SRC_DIR" -# Check Docker is available -check_docker +# Check the selected container engine is available +check_engine # A foreign target architecture runs under QEMU user emulation. Probe by # actually running a container for the target platform: that is the only @@ -172,9 +172,11 @@ check_docker HOST_ARCH=$(uname -m) [[ "$HOST_ARCH" == "arm64" ]] && HOST_ARCH=aarch64 if [[ "$HOST_ARCH" != "$ARCH" ]]; then - if ! docker run --rm "$(get_platform_arg "$ARCH")" alpine:3.21 /bin/true >/dev/null 2>&1; then + PROBE_IMAGE="alpine:3.21" + [[ "$CONTAINER_ENGINE" == "podman" ]] && PROBE_IMAGE="docker.io/library/alpine:3.21" + if ! "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$ARCH")" "$PROBE_IMAGE" /bin/true >/dev/null 2>&1; then print_info "Setting up QEMU for $ARCH emulation on this $HOST_ARCH host..." - setup_qemu + setup_qemu "$ARCH" fi fi @@ -201,7 +203,7 @@ fi # flag is that every job runs the same bytes. IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR" if [[ $SKIP_BUILDER_IMAGE == "1" ]]; then - if ! docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then + if ! "$CONTAINER_ENGINE" image inspect "$IMAGE_TAG" >/dev/null 2>&1; then print_error "Prepared builder image is unavailable: $IMAGE_TAG" exit 1 fi @@ -216,9 +218,12 @@ print_info "Running package build..." mkdir -p "$BUILD_OUTPUT_DIR" mkdir -p "$REPO_DIR" -# Ensure output directories are writable by container user -make_dir_writable "$BUILD_OUTPUT_DIR" -make_dir_writable "$REPO_DIR" +# Rootful Docker writes as the image uid, so retain its existing permission +# workaround. Rootless Podman uses keep-id and must leave ownership/modes alone. +if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + make_dir_writable "$BUILD_OUTPUT_DIR" + make_dir_writable "$REPO_DIR" +fi # Build Docker arguments DOCKER_ARGS=( @@ -235,10 +240,16 @@ DOCKER_ARGS=( -v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro" ) +# Podman-created images can leave WORKDIR owned by a remapped uid. Mount the +# existing host-user-owned workspace so the builder can write there. +if [[ "$CONTAINER_ENGINE" == "podman" ]]; then + DOCKER_ARGS+=(-v "$SRC_DIR:/src") +fi + # Run the builder with assembled args PLATFORM_ARG=$(get_platform_arg "$ARCH") -docker run "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh +"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh BUILD_RESULT=$? diff --git a/bin/clean-docker b/bin/clean-docker index 4a5fc945..93c9a659 100755 --- a/bin/clean-docker +++ b/bin/clean-docker @@ -1,21 +1,27 @@ #!/bin/bash -# Clean Docker builder images and cache +# Clean builder images and cache for the selected container engine # Forces a fresh image build on next run set -e BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/docker-helpers.sh" -print_header "Cleaning Docker Builder Images" +check_engine +print_header "Cleaning Container Builder Images" # Remove all omarchy-pkg-builder images print_info "Removing omarchy-pkg-builder images..." -docker images omarchy-pkg-builder -q | xargs -r docker rmi -f 2>/dev/null || true +"$CONTAINER_ENGINE" images omarchy-pkg-builder -q | xargs -r "$CONTAINER_ENGINE" rmi -f 2>/dev/null || true -# Clear buildx cache for these builds -print_info "Clearing buildx cache..." -docker buildx prune -a -f +# Clear this engine's build cache. +print_info "Clearing build cache..." +if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + "$CONTAINER_ENGINE" buildx prune -a -f +else + "$CONTAINER_ENGINE" image prune --build-cache -f +fi -print_success "Docker builder cache cleared" +print_success "Container builder cache cleared" print_info "Next build will create fresh images" diff --git a/bin/omarchy-release b/bin/omarchy-release index 762bd80d..12e689bd 100755 --- a/bin/omarchy-release +++ b/bin/omarchy-release @@ -18,6 +18,7 @@ set -e BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/docker-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/host-helpers.sh" @@ -374,8 +375,8 @@ iso_checkout() { # prints a usable omarchy-iso checkout, cloning to tmp if neede build_iso() { # build_iso [--rc] local version="$1" rc_flag="${2:-}" dir dir=$(iso_checkout) || return 1 - if ! command -v docker >/dev/null || ! docker info >/dev/null 2>&1; then - print_warning "Docker unavailable — cannot build the ISO here. Run on a Docker machine:" + if [[ -z "$CONTAINER_ENGINE" ]] || ! "$CONTAINER_ENGINE" info >/dev/null 2>&1; then + print_warning "No container engine is available — cannot build the ISO here. Run on a Docker or Podman machine:" echo " cd $dir && bin/omarchy-iso-release ${rc_flag:+$rc_flag }$version" return 1 fi @@ -1034,10 +1035,10 @@ cmd_doctor() { else print_warning "no build host configured — set OMARCHY_REPO_HOST, --host, or write an ssh destination (root@ or an ssh-config alias) to $BUILD_ROOT/.repo-host; until then builds trigger on the 6h timer only" fi - if command -v docker >/dev/null && docker info >/dev/null 2>&1; then - print_success "docker available (ISO builds possible here)" + if [[ -n "$CONTAINER_ENGINE" ]] && "$CONTAINER_ENGINE" info >/dev/null 2>&1; then + print_success "$CONTAINER_ENGINE available (ISO builds possible here)" else - print_warning "docker unavailable — ISO builds will print instructions instead" + print_warning "container engine unavailable — ISO builds will print instructions instead" fi echo "" if ((failures == 0)); then diff --git a/bin/remove-package b/bin/remove-package index eedcfb03..4700402b 100755 --- a/bin/remove-package +++ b/bin/remove-package @@ -62,8 +62,8 @@ if [[ ! -d "$REPO_DIR" ]]; then exit 1 fi -# Check Docker is available -check_docker +# Check the selected container engine is available +check_engine # Find package files to confirm before running Docker cd "$REPO_DIR" @@ -99,11 +99,13 @@ acquire_release_lock || exit 1 print_info "Removing package..." -# Ensure directory is writable by container user -make_dir_writable "$REPO_DIR" +# Rootless Podman uses keep-id and leaves host ownership/modes intact. +if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + make_dir_writable "$REPO_DIR" +fi # Run the removal script in the host-native image. -docker run --rm "$(get_platform_arg "$TOOL_ARCH")" \ +"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$TOOL_ARCH")" \ -e ARCH="$ARCH" \ -e MIRROR="$MIRROR" \ -v "$REPO_ROOT:/pkgs.omarchy.org" \ diff --git a/bin/setup b/bin/setup index 6727d96c..69dfe0e9 100755 --- a/bin/setup +++ b/bin/setup @@ -15,6 +15,7 @@ set -e BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" +source "$BUILD_ROOT/helpers/docker-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" CHECK_ONLY=false @@ -62,10 +63,12 @@ if command -v apt-get >/dev/null 2>&1; then DISTRO="debian" PKG_BSDTAR="libarchive-tools" PKG_DOCKER="docker.io" + PKG_PODMAN="podman" elif command -v pacman >/dev/null 2>&1; then DISTRO="arch" PKG_BSDTAR="libarchive" PKG_DOCKER="docker" + PKG_PODMAN="podman" else print_error "Unsupported distribution — need apt-get or pacman" exit 1 @@ -73,18 +76,36 @@ fi print_info "Distribution: $DISTRO" +# A fresh repository host still defaults to Docker. An explicit Podman choice, +# or a working Podman selected by the shared helper, is left alone. +if [[ -z "$CONTAINER_ENGINE" ]]; then + CONTAINER_ENGINE=docker + export CONTAINER_ENGINE +fi +if ! container_engine_supported; then + print_error "Unsupported CONTAINER_ENGINE: $CONTAINER_ENGINE (use docker or podman)" + exit 1 +fi +if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + PKG_ENGINE="$PKG_DOCKER" + ENGINE_NAME="Docker" +else + PKG_ENGINE="$PKG_PODMAN" + ENGINE_NAME="Podman" +fi + if [[ "$CHECK_ONLY" != true && $EUID -ne 0 ]]; then print_error "Run as root (installing packages and systemd units)" exit 1 fi -# Docker and the release timers are both systemd units. Say so plainly rather -# than failing later on a missing command — a container is the usual way to end -# up here, and it cannot be a repository host. +# The release timers are systemd units. Say so plainly rather than failing +# later on a missing command — a container is the usual way to end up here, +# and it cannot be a repository host. if [[ "$CHECK_ONLY" != true ]] && ! command -v systemctl >/dev/null 2>&1; then print_error "systemctl not found — the repository host must run systemd" echo "" - echo "Docker and the release timers are systemd units. This looks like a" + echo "The release timers are systemd units. This looks like a" echo "container; run setup on the host itself." exit 1 fi @@ -140,24 +161,27 @@ else fi echo "" -# --- docker ------------------------------------------------------------------ +# --- container engine -------------------------------------------------------- -# Docker is left alone when it already works. A host may well be running a +# The selected engine is left alone when it already works. A host may be running a # version from Docker's own repository rather than the distribution's, and # replacing that underneath a working builder would be a poor trade for # tidiness. -print_info "Checking Docker..." +print_info "Checking $ENGINE_NAME..." -if command -v docker >/dev/null 2>&1; then - print_step "docker present: $(docker --version 2>/dev/null | head -1)" - if docker info >/dev/null 2>&1; then - print_success "Docker is installed and running — leaving it alone" +if command -v "$CONTAINER_ENGINE" >/dev/null 2>&1; then + print_step "$CONTAINER_ENGINE present: $("$CONTAINER_ENGINE" --version 2>/dev/null | head -1)" + if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then + print_success "$ENGINE_NAME is installed and available — leaving it alone" elif [[ "$CHECK_ONLY" == true ]]; then - print_warning "Docker is installed but not running; would start it" + print_warning "$ENGINE_NAME is installed but unavailable" + elif [[ "$CONTAINER_ENGINE" == "podman" ]]; then + print_error "Podman is installed but unavailable to the current user" + exit 1 else print_info "Docker is installed but not running — starting it" systemctl enable --now docker.service - if docker info >/dev/null 2>&1; then + if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then print_success "Docker started" else print_error "Docker is installed but still not responding" @@ -166,16 +190,24 @@ if command -v docker >/dev/null 2>&1; then fi fi elif [[ "$CHECK_ONLY" == true ]]; then - print_warning "Would install $PKG_DOCKER and enable it" -else - print_info "Installing $PKG_DOCKER..." - install_packages "$PKG_DOCKER" - systemctl enable --now docker.service - if docker info >/dev/null 2>&1; then - print_success "Docker installed and running" + if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + print_warning "Would install $PKG_ENGINE and enable it" else - print_error "Docker installed but not responding" - echo " Check 'systemctl status docker' — builds cannot run without it." + print_warning "Would install $PKG_ENGINE" + fi +else + print_info "Installing $PKG_ENGINE..." + install_packages "$PKG_ENGINE" + if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + systemctl enable --now docker.service + fi + if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then + print_success "$ENGINE_NAME installed and available" + else + print_error "$ENGINE_NAME installed but not responding" + if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + echo " Check 'systemctl status docker' — builds cannot run without it." + fi exit 1 fi fi diff --git a/bin/sign b/bin/sign index c517b9ba..e1e99cf3 100755 --- a/bin/sign +++ b/bin/sign @@ -52,8 +52,8 @@ if [[ ! -d "$BUILD_OUTPUT_DIR" ]]; then exit 1 fi -# Check Docker is available -check_docker +# Check the selected container engine is available +check_engine # Check GPG credentials are in environment if [[ -z "$GPG_PRIVATE_KEY" ]]; then @@ -76,11 +76,13 @@ build_docker_image "$BUILD_DIR" "$TOOL_ARCH" "$MIRROR" print_info "Running package signing..." -# Ensure output directory is writable by container user -make_dir_writable "$BUILD_OUTPUT_DIR" +# Rootless Podman uses keep-id and leaves host ownership/modes intact. +if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + make_dir_writable "$BUILD_OUTPUT_DIR" +fi # Run the signing script in the host-native image. -docker run --rm "$(get_platform_arg "$TOOL_ARCH")" \ +"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$TOOL_ARCH")" \ -e ARCH="$ARCH" \ -e MIRROR="$MIRROR" \ -e GPG_PRIVATE_KEY="$GPG_PRIVATE_KEY" \ diff --git a/bin/update-repo b/bin/update-repo index 894e1972..2fda5493 100755 --- a/bin/update-repo +++ b/bin/update-repo @@ -11,12 +11,11 @@ source "$BUILD_ROOT/helpers/docker-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/lock-helpers.sh" -# Function to update repository database using Docker +# Function to update repository database using the selected container engine update_database() { - print_info "Updating repository database in Docker container..." + print_info "Updating repository database in a container..." - # Check Docker is available - check_docker + check_engine # Ensure output directory exists if [[ ! -d "$REPO_DIR" ]]; then @@ -25,8 +24,9 @@ update_database() { exit 1 fi - # Make output directory writable for container - make_dir_writable "$REPO_DIR" + if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + make_dir_writable "$REPO_DIR" + fi # repo-add is architecture- and mirror-independent. Use the host-native edge # image, which also lets bootstrap-rc run before that channel exists remotely. @@ -37,7 +37,7 @@ update_database() { } build_docker_image "$BUILD_DIR" "$tool_arch" "edge" - docker run --rm "$(get_platform_arg "$tool_arch")" \ + "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$tool_arch")" \ -e ARCH="$ARCH" \ -e MIRROR="$MIRROR" \ -v "$REPO_ROOT:/output" \ diff --git a/helpers/docker-helpers.sh b/helpers/docker-helpers.sh index c108340e..3ec61974 100644 --- a/helpers/docker-helpers.sh +++ b/helpers/docker-helpers.sh @@ -1,14 +1,57 @@ -# Docker helper functions for Omarchy package build system +# Container engine helpers for Omarchy package build system -check_docker() { - if ! command -v docker &>/dev/null; then - print_error "Docker is not installed" +container_engine_supported() { + [[ "$CONTAINER_ENGINE" == "docker" || "$CONTAINER_ENGINE" == "podman" ]] +} + +if [[ -z "${CONTAINER_ENGINE:-}" ]]; then + for engine in docker podman; do + if command -v "$engine" >/dev/null 2>&1 && "$engine" info >/dev/null 2>&1; then + CONTAINER_ENGINE="$engine" + break + fi + done +fi +export CONTAINER_ENGINE + +# Rootless Podman otherwise maps the image's builder uid 1000 to a subordinate +# host uid. keep-id makes files written through bind mounts belong to the user +# who invoked the build. +CONTAINER_RUN_ARGS=() +if [[ "$CONTAINER_ENGINE" == "podman" ]]; then + CONTAINER_RUN_ARGS+=("--userns=keep-id:uid=1000,gid=1000") +fi + +check_engine() { + if [[ -n "$CONTAINER_ENGINE" ]] && ! container_engine_supported; then + print_error "Unsupported CONTAINER_ENGINE: $CONTAINER_ENGINE (use docker or podman)" exit 1 fi - if ! docker info &>/dev/null; then - print_error "Docker daemon is not running" - print_warning "Start Docker with: sudo systemctl start docker" + if [[ -z "$CONTAINER_ENGINE" ]]; then + print_error "No working container engine found (tried Docker, then Podman)" + if command -v docker >/dev/null 2>&1; then + print_warning "Docker is installed but unavailable. Start it with: sudo systemctl start docker" + elif command -v podman >/dev/null 2>&1; then + print_warning "Podman is installed but 'podman info' failed" + else + print_warning "Install Docker or Podman" + fi + exit 1 + fi + + if ! command -v "$CONTAINER_ENGINE" >/dev/null 2>&1; then + print_error "$CONTAINER_ENGINE is not installed" + exit 1 + fi + + if ! "$CONTAINER_ENGINE" info >/dev/null 2>&1; then + if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + print_error "Docker daemon is not running or is not accessible" + print_warning "Start Docker with: sudo systemctl start docker" + else + print_error "Podman is not available to the current user" + fi exit 1 fi } @@ -22,8 +65,22 @@ docker_native_arch() { } setup_qemu() { + local target_arch="${1:-aarch64}" + + if [[ "$CONTAINER_ENGINE" == "podman" ]]; then + local registration="/proc/sys/fs/binfmt_misc/qemu-$target_arch" + if [[ -r "$registration" ]] && grep -q '^flags:.*F' "$registration"; then + print_success "QEMU $target_arch emulation is registered" + return 0 + fi + + print_error "Rootless Podman requires host QEMU binfmt registration with the F flag" + print_info "Install it with: sudo pacman -S qemu-user-static qemu-user-static-binfmt" + exit 1 + fi + # Register emulators for builds whose target differs from the host. - if ! docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes >/dev/null 2>&1; then + if ! "$CONTAINER_ENGINE" run --rm --privileged docker.io/multiarch/qemu-user-static --reset -p yes --credential yes >/dev/null 2>&1; then print_error "Failed to set up QEMU emulation" exit 1 fi @@ -47,15 +104,24 @@ build_docker_image() { ;; esac - print_info "Building Docker image for $arch ($platform) using $mirror mirror..." - - docker buildx build \ - --platform "$platform" \ - --build-arg MIRROR="$mirror" \ - --load \ - -t "$image_tag" \ - -f "$build_dir/Dockerfile" \ - "$build_dir" + print_info "Building container image for $arch ($platform) using $mirror mirror..." + + if [[ "$CONTAINER_ENGINE" == "docker" ]]; then + "$CONTAINER_ENGINE" buildx build \ + --platform "$platform" \ + --build-arg MIRROR="$mirror" \ + --load \ + -t "$image_tag" \ + -f "$build_dir/Dockerfile" \ + "$build_dir" + else + "$CONTAINER_ENGINE" build \ + --platform "$platform" \ + --build-arg MIRROR="$mirror" \ + -t "$image_tag" \ + -f "$build_dir/Dockerfile" \ + "$build_dir" + fi } get_platform_arg() { From 0b67dbab8ee081a85c424685b61114806cc68918 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Fri, 4 Sep 2026 23:40:31 -0400 Subject: [PATCH 28/63] Harden emulated ARM builds --- README.md | 8 ++++--- bin/build | 18 ++++++++++++-- build/build.sh | 50 +++++++++++++++++++++++++++------------ helpers/docker-helpers.sh | 18 +++++++++++--- 4 files changed, 71 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index c5338a0a..c34f0047 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,14 @@ The filesystem no longer encodes release policy. Instead: The repository host builds every architecture it publishes on the same machine. A foreign architecture runs under QEMU user emulation, which -`bin/build` sets up on first use (and re-checks after each reboot by -actually running a container for the target platform): +`bin/build` checks by actually running a container for the target platform. +Rootful Docker registers QEMU on first use. Rootless Podman uses the host's +registration and prints the one-time Arch setup commands when it is missing or +lacks the credential flag required by `sudo` inside the builder: ```bash # Verify -docker run --rm --platform linux/arm64 alpine:latest uname -m +podman run --rm --platform linux/arm64 docker.io/library/alpine:latest uname -m # Should output: aarch64 ``` diff --git a/bin/build b/bin/build index 7e6ae58d..c2d001c9 100755 --- a/bin/build +++ b/bin/build @@ -174,10 +174,24 @@ HOST_ARCH=$(uname -m) if [[ "$HOST_ARCH" != "$ARCH" ]]; then PROBE_IMAGE="alpine:3.21" [[ "$CONTAINER_ENGINE" == "podman" ]] && PROBE_IMAGE="docker.io/library/alpine:3.21" - if ! "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$ARCH")" "$PROBE_IMAGE" /bin/true >/dev/null 2>&1; then - print_info "Setting up QEMU for $ARCH emulation on this $HOST_ARCH host..." + + # Rootless Podman cannot repair host binfmt state itself. Validate the flags + # before the basic probe, because an F-only registration can start an ARM + # container but silently breaks sudo inside it. + if [[ "$CONTAINER_ENGINE" == "podman" ]]; then setup_qemu "$ARCH" fi + + if ! "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$ARCH")" "$PROBE_IMAGE" /bin/true >/dev/null 2>&1; then + if [[ "$CONTAINER_ENGINE" == "podman" ]]; then + print_error "QEMU $ARCH is registered, but the container probe failed" + print_info "Refresh the registration with: sudo systemctl restart systemd-binfmt" + exit 1 + else + print_info "Setting up QEMU for $ARCH emulation on this $HOST_ARCH host..." + setup_qemu "$ARCH" + fi + fi fi # Clean build-output directory to start fresh, unless the caller seeded it diff --git a/build/build.sh b/build/build.sh index 8534ee9c..9357ebd4 100755 --- a/build/build.sh +++ b/build/build.sh @@ -5,6 +5,10 @@ # Setup directories ARCH=${ARCH:-x86_64} +# ARCH selects the repository target for this script, but make and Kbuild also +# interpret an exported ARCH themselves (Linux calls this target "arm64"). +# Keep the shell variable local to the orchestrator so PKGBUILDs see CARCH only. +export -n ARCH MIRROR=${MIRROR:-edge} DRY_RUN=${DRY_RUN:-false} PKGBUILDS_DIR=${PKGBUILDS_DIR:-/pkgbuilds} @@ -60,14 +64,11 @@ if [[ "$DRY_RUN" != true ]]; then # Configure Omarchy repositories for dependency resolution echo "==> Configuring Omarchy repositories for dependency resolution..." - # Always add omarchy-build repo (for incremental builds) - # Packages in build-output are unsigned, so use SigLevel = Never - sudo tee -a /etc/pacman.conf > /dev/null < omarchy-build (priority 1): $BUILD_OUTPUT_DIR" # Initialize empty build database if it doesn't exist @@ -90,12 +91,7 @@ EOF # Add omarchy repo if it has a database (stable packages) if [[ -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" ]] || [[ -f "$FINAL_OUTPUT_DIR/omarchy.db" ]]; then - sudo tee -a /etc/pacman.conf > /dev/null < omarchy (priority 2): $FINAL_OUTPUT_DIR" fi @@ -336,8 +332,14 @@ build_package() { # Ensure output directory exists mkdir -p "$BUILD_OUTPUT_DIR" + local dependency_pkg_file="" for pkg_file in *.pkg.tar.*; do - [[ -f "$pkg_file" ]] && cp "$pkg_file" "$BUILD_OUTPUT_DIR/" + [[ -f "$pkg_file" && "$pkg_file" != *.sig ]] || continue + cp "$pkg_file" "$BUILD_OUTPUT_DIR/" + + if [[ "$(bsdtar -xOf "$pkg_file" .PKGINFO 2>/dev/null | sed -n 's/^pkgname = //p')" == "$pkg" ]]; then + dependency_pkg_file="$BUILD_OUTPUT_DIR/$pkg_file" + fi done cd "$BUILD_OUTPUT_DIR" @@ -353,6 +355,24 @@ build_package() { cd /src/$pkg + # A lower-priority official repository may contain an older package with + # the same name. Install the exact artifact we just built before building + # its consumers, so pacman cannot select that older provider instead. + if [[ "${INSTALL_PACKAGES[$pkg]:-}" == "1" ]]; then + if [[ -z "$dependency_pkg_file" ]]; then + echo " Could not find the built $pkg package to install as a dependency" + FAILED_PACKAGES="$FAILED_PACKAGES $pkg" + return 1 + fi + + echo " Installing freshly built $pkg for dependent packages..." + if ! sudo /usr/local/bin/pacman-for-makepkg -U --needed --noconfirm "$dependency_pkg_file"; then + echo " Failed to install freshly built dependency $pkg" + FAILED_PACKAGES="$FAILED_PACKAGES $pkg" + return 1 + fi + fi + echo " Successfully built $pkg" SUCCESSFUL_PACKAGES="$SUCCESSFUL_PACKAGES $pkg" return 0 diff --git a/helpers/docker-helpers.sh b/helpers/docker-helpers.sh index 3ec61974..cc18222a 100644 --- a/helpers/docker-helpers.sh +++ b/helpers/docker-helpers.sh @@ -69,13 +69,25 @@ setup_qemu() { if [[ "$CONTAINER_ENGINE" == "podman" ]]; then local registration="/proc/sys/fs/binfmt_misc/qemu-$target_arch" - if [[ -r "$registration" ]] && grep -q '^flags:.*F' "$registration"; then + local packaged_registration="/usr/lib/binfmt.d/qemu-$target_arch-static.conf" + local flags="" + + if [[ -r "$registration" ]]; then + flags=$(sed -n 's/^flags: //p' "$registration") + fi + + if [[ "$flags" == *F* && "$flags" == *C* ]]; then print_success "QEMU $target_arch emulation is registered" return 0 fi - print_error "Rootless Podman requires host QEMU binfmt registration with the F flag" - print_info "Install it with: sudo pacman -S qemu-user-static qemu-user-static-binfmt" + print_error "Rootless Podman requires QEMU binfmt registration with the F and C flags" + print_info "F keeps the emulator available inside containers; C lets container sudo preserve credentials." + print_info "Configure it once with:" + echo " sudo pacman -S --needed qemu-user-static qemu-user-static-binfmt" + echo " sudo mkdir -p /etc/binfmt.d" + echo " sed 's/:FP$/:FPC/' $packaged_registration | sudo tee /etc/binfmt.d/qemu-$target_arch-static.conf >/dev/null" + echo " sudo systemctl restart systemd-binfmt" exit 1 fi From 520dd5c3bb4272ee9a16c6684fbf7779a570248c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Fri, 4 Sep 2026 23:40:31 -0400 Subject: [PATCH 29/63] Repair aarch64 package coverage --- .../.omarchy/patches/x86-only.patch | 11 ++ pkgbuilds/dropbox-cli/PKGBUILD | 5 +- .../hyprland-guiutils/.omarchy/package.json | 21 ++++ pkgbuilds/hyprland-guiutils/PKGBUILD | 40 +++++++ pkgbuilds/hyprland/.omarchy/package.json | 21 ++++ pkgbuilds/hyprland/PKGBUILD | 105 ++++++++++++++++++ pkgbuilds/hyprtoolkit/.omarchy/package.json | 21 ++++ pkgbuilds/hyprtoolkit/PKGBUILD | 50 +++++++++ .../.omarchy/patches/arm-gradle.patch | 48 ++++++++ pkgbuilds/limine-mkinitcpio-hook/PKGBUILD | 16 ++- .../.omarchy/patches/arm-gradle.patch | 46 ++++++++ pkgbuilds/limine-snapper-sync/PKGBUILD | 16 ++- .../.omarchy/patches/arm-dependencies.patch | 57 ++++++++++ pkgbuilds/sunshine/PKGBUILD | 22 +++- .../.omarchy/patches/arm-kernel-headers.patch | 11 ++ pkgbuilds/xpadneo-dkms/PKGBUILD | 6 +- 16 files changed, 478 insertions(+), 18 deletions(-) create mode 100644 pkgbuilds/dropbox-cli/.omarchy/patches/x86-only.patch create mode 100644 pkgbuilds/hyprland-guiutils/.omarchy/package.json create mode 100644 pkgbuilds/hyprland-guiutils/PKGBUILD create mode 100644 pkgbuilds/hyprland/.omarchy/package.json create mode 100644 pkgbuilds/hyprland/PKGBUILD create mode 100644 pkgbuilds/hyprtoolkit/.omarchy/package.json create mode 100644 pkgbuilds/hyprtoolkit/PKGBUILD create mode 100644 pkgbuilds/limine-mkinitcpio-hook/.omarchy/patches/arm-gradle.patch create mode 100644 pkgbuilds/limine-snapper-sync/.omarchy/patches/arm-gradle.patch create mode 100644 pkgbuilds/sunshine/.omarchy/patches/arm-dependencies.patch create mode 100644 pkgbuilds/xpadneo-dkms/.omarchy/patches/arm-kernel-headers.patch diff --git a/pkgbuilds/dropbox-cli/.omarchy/patches/x86-only.patch b/pkgbuilds/dropbox-cli/.omarchy/patches/x86-only.patch new file mode 100644 index 00000000..2448ae46 --- /dev/null +++ b/pkgbuilds/dropbox-cli/.omarchy/patches/x86-only.patch @@ -0,0 +1,11 @@ +--- a/PKGBUILD ++++ b/PKGBUILD +@@ -11,7 +11,7 @@ pkgname=dropbox-cli + pkgver=2024.04.17 + pkgrel=2 + pkgdesc="Command line interface for Dropbox" +-arch=("any") ++arch=("x86_64") + url="https://www.dropbox.com" + license=("GPL-3.0-or-later") + makedepends=("gdk-pixbuf2") diff --git a/pkgbuilds/dropbox-cli/PKGBUILD b/pkgbuilds/dropbox-cli/PKGBUILD index 7cc84b95..96f1777b 100644 --- a/pkgbuilds/dropbox-cli/PKGBUILD +++ b/pkgbuilds/dropbox-cli/PKGBUILD @@ -7,9 +7,9 @@ pkgname=dropbox-cli pkgver=2024.04.17 -pkgrel=2 +pkgrel=2.1 pkgdesc="Command line interface for Dropbox" -arch=("any") +arch=("x86_64") url="https://www.dropbox.com" license=("GPL-3.0-or-later") makedepends=("gdk-pixbuf2") @@ -36,4 +36,3 @@ package() { cd "nautilus-dropbox-${pkgver}" install -m 755 -D "${pkgname}" "${pkgdir}/usr/bin/${pkgname}" } - diff --git a/pkgbuilds/hyprland-guiutils/.omarchy/package.json b/pkgbuilds/hyprland-guiutils/.omarchy/package.json new file mode 100644 index 00000000..d87fa12f --- /dev/null +++ b/pkgbuilds/hyprland-guiutils/.omarchy/package.json @@ -0,0 +1,21 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "git_tags": "https://github.com/hyprwm/hyprland-guiutils.git", + "tag_pattern": "v{pkgver}", + "sources": { + "any": [ + "https://github.com/hyprwm/hyprland-guiutils/archive/v{pkgver}/hyprland-guiutils-{pkgver}.tar.gz" + ] + } + }, + "rebuild_on": [ + "aquamarine" + ], + "rebuilt_against": { + "aarch64": { + "aquamarine": "0.15.0-2" + } + } +} diff --git a/pkgbuilds/hyprland-guiutils/PKGBUILD b/pkgbuilds/hyprland-guiutils/PKGBUILD new file mode 100644 index 00000000..036e6bf4 --- /dev/null +++ b/pkgbuilds/hyprland-guiutils/PKGBUILD @@ -0,0 +1,40 @@ +# Maintainer: Caleb Maclennan + +pkgname=hyprland-guiutils +pkgver=0.2.2 +pkgrel=3 +pkgdesc='Hyprland GUI utilities' +arch=(aarch64) +url="https://github.com/hyprwm/$pkgname" +license=(BSD-3-Clause) +depends=( + libgcc + libstdc++ + glibc # libc.so libm.so + hyprlang + hyprtoolkit libhyprtoolkit.so + hyprutils libhyprutils.so + libdrm + pixman +) +makedepends=(cmake) +replaces=(hyprland-qtutils) +_archive="$pkgname-$pkgver" +source=("$url/archive/v$pkgver/$_archive.tar.gz") +sha256sums=('16f92a6c5a22ac58e1fc313f6b202c188da45e804e1f21ff57dfd0da5c1a01b7') + +build() { + cd "$_archive" + local cmake_flags=( + -D CMAKE_BUILD_TYPE=Release + -D CMAKE_INSTALL_PREFIX=/usr + ) + cmake -B build ${cmake_flags[@]} + cmake --build build +} + +package() { + cd "$_archive" + DESTDIR="$pkgdir" cmake --install build + install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname" LICENSE +} diff --git a/pkgbuilds/hyprland/.omarchy/package.json b/pkgbuilds/hyprland/.omarchy/package.json new file mode 100644 index 00000000..9dd97453 --- /dev/null +++ b/pkgbuilds/hyprland/.omarchy/package.json @@ -0,0 +1,21 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "git_tags": "https://github.com/hyprwm/Hyprland.git", + "tag_pattern": "v{pkgver}", + "sources": { + "any": [ + "https://github.com/hyprwm/Hyprland/releases/download/v{pkgver}/source-v{pkgver}.tar.gz" + ] + } + }, + "rebuild_on": [ + "aquamarine" + ], + "rebuilt_against": { + "aarch64": { + "aquamarine": "0.15.0-2" + } + } +} diff --git a/pkgbuilds/hyprland/PKGBUILD b/pkgbuilds/hyprland/PKGBUILD new file mode 100644 index 00000000..cbb70ed8 --- /dev/null +++ b/pkgbuilds/hyprland/PKGBUILD @@ -0,0 +1,105 @@ +# Maintainer: Caleb Maclennan +# Contributor: ThatOneCalculator +# Contributor: Brenno Lemos +# Contributor: Gabriel Fox + +pkgname=(hyprland hyprpm) +pkgver=0.56.2 +pkgrel=3 +pkgdesc='a highly customizable dynamic tiling Wayland compositor' +arch=(aarch64) +url="https://github.com/hyprwm/${pkgname^}" +license=(BSD-3-Clause) +depends=(cairo # libcairo.so + aquamarine libaquamarine.so + libgcc # libgcc_s.so + 'libstdc++' # libstdc++.so + glibc # libc.so libm.so + glib2 libgio-2.0.so libgobject-2.0.so + glslang # libglslang-default-resource-limits.so libglslang.so + hyprcursor libhyprcursor.so + hyprgraphics libhyprgraphics.so + hyprland-guiutils + hyprlang libhyprlang.so + hyprutils libhyprutils.so + hyprwire libhyprwire.so + lcms2 liblcms2.so + libdrm # libdrm.so + libglvnd libEGL.so libGLESv2.so + libinput # libinput.so + libx11 + libxcb # libxcb-dri3.so libxcb-present.so libxcb-render.so libxcb-res.so libxcb-shm.so libxcb.so libxcb-xfixes.so libxcb-xinput.so + libxcomposite # libxcb-composite.so + libxcursor # libXcursor.so + libxfixes + libxkbcommon libxkbcommon.so + libxrender + lua # liblua.so + mesa # libgbm.so + muparser # libmuparser.so + opengl-driver + pango libpango-1.0.so libpangocairo-1.0.so + pixman libpixman-1.so + re2 libre2.so + tomlplusplus libtomlplusplus.so + util-linux-libs libuuid.so + wayland libwayland-server.so + wayland-protocols + xcb-proto + xcb-util + xcb-util-errors # libxcb-errors.so + xcb-util-image + xcb-util-keysyms + xcb-util-renderutil # libxcb-render-util.so + xcb-util-wm # libxcb-ewmh.so libxcb-icccm.so + xorg-xwayland) +makedepends=(cmake + glaze + hyprland-protocols + hyprwayland-scanner + meson + ninja + xorgproto) +optdepends=('hyprpm: build and install plugins' + 'hyprshutdown: clean logout and shutdown helper' + 'xdg-desktop-portal-hyprland: xdg-desktop-portal backend for hyprland') +provides=(wayland-compositor) +_archive="${pkgname^}-$pkgver" +source=("$_archive.tar.gz::$url/releases/download/v$pkgver/source-v$pkgver.tar.gz") +sha256sums=('03ad3f5ef152ff44116ffd56fcf808486211ecabf4f0ba567108ee746ba5cd2e') + +prepare() { + ln -sf hyprland-source "$_archive" + cd "$_archive" + sed -i -e '/^release:/{n;s/-D/-DCMAKE_SKIP_RPATH=ON -D/}' Makefile + sed -i -e '/find_package.glaze/s/7...<8 //' {.,hyprpm,start}/CMakeLists.txt +} + +build() { + cd "$_archive" + make release PREFIX=/usr +} + +package_hyprland() { + cd "$_archive" + make DESTDIR="$pkgdir" install + rm -fv "$pkgdir/usr/include/hyprland/src/version.h.in" + find $pkgdir -name '*hyprpm*' -delete + install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE +} + +package_hyprpm() { + pkgdesc='Plugin manager for Hyprland' + depends=(cmake + cpio + glaze + hyprland + hyprland-protocols + hyprwayland-scanner + meson) + cd "$_archive" + make DESTDIR="$pkgdir" install + find $pkgdir -type f,l -not -name '*hyprpm*' -delete + find $pkgdir -type d -empty -delete + install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE +} diff --git a/pkgbuilds/hyprtoolkit/.omarchy/package.json b/pkgbuilds/hyprtoolkit/.omarchy/package.json new file mode 100644 index 00000000..ee5e6cbf --- /dev/null +++ b/pkgbuilds/hyprtoolkit/.omarchy/package.json @@ -0,0 +1,21 @@ +{ + "source": "local", + "release_ring": "fast", + "upstream": { + "git_tags": "https://github.com/hyprwm/hyprtoolkit.git", + "tag_pattern": "v{pkgver}", + "sources": { + "any": [ + "https://github.com/hyprwm/hyprtoolkit/archive/v{pkgver}/hyprtoolkit-{pkgver}.tar.gz" + ] + } + }, + "rebuild_on": [ + "aquamarine" + ], + "rebuilt_against": { + "aarch64": { + "aquamarine": "0.15.0-2" + } + } +} diff --git a/pkgbuilds/hyprtoolkit/PKGBUILD b/pkgbuilds/hyprtoolkit/PKGBUILD new file mode 100644 index 00000000..c6f1b490 --- /dev/null +++ b/pkgbuilds/hyprtoolkit/PKGBUILD @@ -0,0 +1,50 @@ +# Maintainer: Caleb Maclennan + +pkgname=hyprtoolkit +pkgver=0.5.4 +pkgrel=5.1 +pkgdesc='A modern C++ Wayland-native GUI toolkit' +arch=(aarch64) +url="https://github.com/hyprwm/$pkgname" +license=(BSD-3-Clause) +depends=( + libgcc + libstdc++ + aquamarine libaquamarine.so + cairo libcairo.so + glib2 libglib-2.0.so + glibc # libc.so libm.so + hyprgraphics libhyprgraphics.so + hyprlang libhyprlang.so + hyprutils libhyprutils.so + iniparser libiniparser.so + libdrm # libdrm.so + libglvnd libEGL.so libOpenGL.so + libxkbcommon libxkbcommon.so + mesa # libgbm.so + pango libpango-1.0.so # libpango.so + pixman libpixman-1.so + wayland libwayland-client.so +) +makedepends=(cmake + hyprwayland-scanner) +provides=(libhyprtoolkit.so) +_archive="$pkgname-$pkgver" +source=("$url/archive/v$pkgver/$_archive.tar.gz") +sha256sums=('2fb59789f231c1c4e9154ceffc1e7524c0cae154807c0d57e6166806255b570f') + +build() { + cd "$_archive" + local cmake_options=( + -D CMAKE_BUILD_TYPE=None + -D CMAKE_INSTALL_PREFIX=/usr + ) + cmake -B build -W no-dev ${cmake_options[@]} + cmake --build build +} + +package() { + cd "$_archive" + DESTDIR="$pkgdir" cmake --install build + install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE +} diff --git a/pkgbuilds/limine-mkinitcpio-hook/.omarchy/patches/arm-gradle.patch b/pkgbuilds/limine-mkinitcpio-hook/.omarchy/patches/arm-gradle.patch new file mode 100644 index 00000000..d7af3e8a --- /dev/null +++ b/pkgbuilds/limine-mkinitcpio-hook/.omarchy/patches/arm-gradle.patch @@ -0,0 +1,48 @@ +--- a/PKGBUILD ++++ b/PKGBUILD +@@ -3,6 +3,7 @@ + pkgname="limine-mkinitcpio-hook" + _pkgver=1.38.0 + _extver="" ++_gradle_version=9.7.1 + pkgver="${_pkgver}${_extver}" + pkgrel=1 + pkgdesc="Install kernels for the Limine bootloader." +@@ -10,7 +11,8 @@ + url="https://gitlab.com/Zesko/limine-entry-tool" + source=("${_pkgname}::git+${url}.git#tag=${pkgver}") + source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-x64_bin.tar.gz") +-source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz") ++source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz" ++ "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip") + license=("GPL3") + provides=('limine-entry-tool') + options=(!debug !strip) +@@ -27,12 +29,14 @@ + 'sbctl: Signs UEFI boot files for Secure Boot when enabled' + 'journalctl-desktop-notification: Sends desktop notifications when errors occur' + ) +-makedepends=('git' 'gradle') ++makedepends=('git') ++makedepends_x86_64=('gradle') + backup=(etc/limine-entry-tool.conf) + conflicts=('limine-entry-tool') + sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01') + sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e') +-sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7') ++sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7' ++ 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') + + prepare() { + [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" +@@ -48,7 +52,9 @@ + export GRAALVM_HOME="$srcdir/${_graalvm_version}" + export JAVA_HOME="${GRAALVM_HOME}" + export NATIVE_IMAGE_OPTIONS="-march=compatibility --future-defaults=all" +- /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" ++ local gradle=/usr/bin/gradle ++ [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle" ++ "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" + } + + package() { diff --git a/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD b/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD index e7a24b7d..b2e63c8c 100644 --- a/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD +++ b/pkgbuilds/limine-mkinitcpio-hook/PKGBUILD @@ -3,14 +3,16 @@ _pkgname="limine-entry-tool" pkgname="limine-mkinitcpio-hook" _pkgver=1.38.0 _extver="" +_gradle_version=9.7.1 pkgver="${_pkgver}${_extver}" -pkgrel=1 +pkgrel=1.1 pkgdesc="Install kernels for the Limine bootloader." arch=('x86_64' 'aarch64') url="https://gitlab.com/Zesko/limine-entry-tool" source=("${_pkgname}::git+${url}.git#tag=${pkgver}") source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-x64_bin.tar.gz") -source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz") +source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz" + "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip") license=("GPL3") provides=('limine-entry-tool') options=(!debug !strip) @@ -27,12 +29,14 @@ optdepends=( 'sbctl: Signs UEFI boot files for Secure Boot when enabled' 'journalctl-desktop-notification: Sends desktop notifications when errors occur' ) -makedepends=('git' 'gradle') +makedepends=('git') +makedepends_x86_64=('gradle') backup=(etc/limine-entry-tool.conf) conflicts=('limine-entry-tool') sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01') sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e') -sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7') +sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7' + 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') prepare() { [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" @@ -48,7 +52,9 @@ build() { export GRAALVM_HOME="$srcdir/${_graalvm_version}" export JAVA_HOME="${GRAALVM_HOME}" export NATIVE_IMAGE_OPTIONS="-march=compatibility --future-defaults=all" - /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" + local gradle=/usr/bin/gradle + [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle" + "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" } package() { diff --git a/pkgbuilds/limine-snapper-sync/.omarchy/patches/arm-gradle.patch b/pkgbuilds/limine-snapper-sync/.omarchy/patches/arm-gradle.patch new file mode 100644 index 00000000..76ad31a0 --- /dev/null +++ b/pkgbuilds/limine-snapper-sync/.omarchy/patches/arm-gradle.patch @@ -0,0 +1,46 @@ +--- a/PKGBUILD ++++ b/PKGBUILD +@@ -2,12 +2,14 @@ + pkgname="limine-snapper-sync" + pkgver=1.31.0 + pkgrel=1 ++_gradle_version=9.7.1 + pkgdesc="Integrates Limine boot entries with Snapper snapshots." + arch=('x86_64' 'aarch64') + url="https://gitlab.com/Zesko/limine-snapper-sync" + source=("${pkgname}::git+${url}.git#tag=${pkgver}") + source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-x64_bin.tar.gz") +-source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz") ++source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz" ++ "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip") + license=("GPL3") + options=(!debug !strip) + _graalvm_version=graalvm_ce_jdk25 +@@ -25,12 +27,14 @@ + 'b3sum: Fast Blake3 hash function to prevent duplication.' + 'xxhash: Fast hashing utility for deduplication with shorter hashes.' + ) +-makedepends=('git' 'gradle') ++makedepends=('git') ++makedepends_x86_64=('gradle') + backup=(etc/limine-snapper-sync.conf) + conflicts=('limine-snapper-cli' 'limine-snapper-sync-git') + sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40') + sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0') +-sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71') ++sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71' ++ 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') + + prepare() { + [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" +@@ -46,7 +50,9 @@ + export GRAALVM_HOME="$srcdir/${_graalvm_version}" + export JAVA_HOME="${GRAALVM_HOME}" + export NATIVE_IMAGE_OPTIONS="-march=compatibility" +- /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" ++ local gradle=/usr/bin/gradle ++ [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle" ++ "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" + } + + package() { diff --git a/pkgbuilds/limine-snapper-sync/PKGBUILD b/pkgbuilds/limine-snapper-sync/PKGBUILD index c8f19624..97d9add2 100644 --- a/pkgbuilds/limine-snapper-sync/PKGBUILD +++ b/pkgbuilds/limine-snapper-sync/PKGBUILD @@ -1,13 +1,15 @@ # Maintainer: Zesko pkgname="limine-snapper-sync" pkgver=1.31.0 -pkgrel=1 +pkgrel=1.1 +_gradle_version=9.7.1 pkgdesc="Integrates Limine boot entries with Snapper snapshots." arch=('x86_64' 'aarch64') url="https://gitlab.com/Zesko/limine-snapper-sync" source=("${pkgname}::git+${url}.git#tag=${pkgver}") source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-x64_bin.tar.gz") -source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz") +source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz" + "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip") license=("GPL3") options=(!debug !strip) _graalvm_version=graalvm_ce_jdk25 @@ -25,12 +27,14 @@ optdepends=( 'b3sum: Fast Blake3 hash function to prevent duplication.' 'xxhash: Fast hashing utility for deduplication with shorter hashes.' ) -makedepends=('git' 'gradle') +makedepends=('git') +makedepends_x86_64=('gradle') backup=(etc/limine-snapper-sync.conf) conflicts=('limine-snapper-cli' 'limine-snapper-sync-git') sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40') sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0') -sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71') +sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71' + 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a') prepare() { [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" @@ -46,7 +50,9 @@ build() { export GRAALVM_HOME="$srcdir/${_graalvm_version}" export JAVA_HOME="${GRAALVM_HOME}" export NATIVE_IMAGE_OPTIONS="-march=compatibility" - /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" + local gradle=/usr/bin/gradle + [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle" + "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}" } package() { diff --git a/pkgbuilds/sunshine/.omarchy/patches/arm-dependencies.patch b/pkgbuilds/sunshine/.omarchy/patches/arm-dependencies.patch new file mode 100644 index 00000000..8345fc50 --- /dev/null +++ b/pkgbuilds/sunshine/.omarchy/patches/arm-dependencies.patch @@ -0,0 +1,57 @@ +--- a/PKGBUILD ++++ b/PKGBUILD +@@ -36,7 +36,6 @@ depends=( + 'libcap' + 'libdrm' + 'libevdev' +- 'libmfx' + 'libnotify' + 'libpipewire' + 'libpulse' +@@ -53,6 +52,10 @@ depends=( + 'which' + ) + ++depends_x86_64=( ++ 'libmfx' ++) ++ + makedepends=( + 'appstream' + 'appstream-glib' +@@ -69,6 +72,10 @@ makedepends=( + 'shaderc' + ) + ++makedepends_aarch64=( ++ 'vulkan-headers' ++) ++ + checkdepends=( + 'gcovr' + ) +@@ -111,7 +118,12 @@ if [ -n "${optdepends+x}" ]; then + + prepare() { + cd "$pkgname" +- git submodule update --recursive --init ++ if [[ $CARCH == aarch64 ]]; then ++ git submodule update --init ++ git -C third-party/moonlight-common-c submodule update --init enet ++ else ++ git submodule update --recursive --init ++ fi + + # Backport https://github.com/LizardByte/Sunshine/commit/060b6b07d376e9ef4c4ce4a5c6d648d08fe0eab9 + patch -Np1 -i "${srcdir}/2026.516.143833-060b6b07-remove-setuptools-from-glad-dependencies.patch" +@@ -145,6 +157,10 @@ build() { + -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' + ) + ++ if [[ $CARCH == aarch64 ]]; then ++ _cmake_options+=(-D SUNSHINE_SYSTEM_VULKAN_HEADERS=ON) ++ fi ++ + if [[ "${_use_cuda::1}" != "t" ]]; then + _cmake_options+=(-DSUNSHINE_ENABLE_CUDA=OFF -DCUDA_FAIL_ON_MISSING=OFF) + else diff --git a/pkgbuilds/sunshine/PKGBUILD b/pkgbuilds/sunshine/PKGBUILD index e6100a62..a6b3d23d 100644 --- a/pkgbuilds/sunshine/PKGBUILD +++ b/pkgbuilds/sunshine/PKGBUILD @@ -10,7 +10,7 @@ pkgname='sunshine' pkgver=2026.516.143833 -pkgrel=4 +pkgrel=4.1 pkgdesc="Self-hosted game stream host for Moonlight" arch=('x86_64' 'aarch64') url=https://app.lizardbyte.dev/Sunshine @@ -36,7 +36,6 @@ depends=( 'libcap' 'libdrm' 'libevdev' - 'libmfx' 'libnotify' 'libpipewire' 'libpulse' @@ -55,6 +54,10 @@ depends=( 'which' ) +depends_x86_64=( + 'libmfx' +) + makedepends=( 'appstream' 'appstream-glib' @@ -69,6 +72,10 @@ makedepends=( 'shaderc' ) +makedepends_aarch64=( + 'vulkan-headers' +) + checkdepends=( 'gcovr' ) @@ -123,7 +130,12 @@ fi prepare() { cd "$pkgname" - git submodule update --recursive --init + if [[ $CARCH == aarch64 ]]; then + git submodule update --init + git -C third-party/moonlight-common-c submodule update --init enet + else + git submodule update --recursive --init + fi # Backport https://github.com/LizardByte/Sunshine/commit/060b6b07d376e9ef4c4ce4a5c6d648d08fe0eab9 patch -Np1 -i "${srcdir}/2026.516.143833-060b6b07-remove-setuptools-from-glad-dependencies.patch" @@ -159,6 +171,10 @@ build() { -D SUNSHINE_PUBLISHER_ISSUE_URL='https://app.lizardbyte.dev/support' ) + if [[ $CARCH == aarch64 ]]; then + _cmake_options+=(-D SUNSHINE_SYSTEM_VULKAN_HEADERS=ON) + fi + if [[ "${_use_cuda::1}" != "t" ]]; then _cmake_options+=(-DSUNSHINE_ENABLE_CUDA=OFF -DCUDA_FAIL_ON_MISSING=OFF) else diff --git a/pkgbuilds/xpadneo-dkms/.omarchy/patches/arm-kernel-headers.patch b/pkgbuilds/xpadneo-dkms/.omarchy/patches/arm-kernel-headers.patch new file mode 100644 index 00000000..9dcc175f --- /dev/null +++ b/pkgbuilds/xpadneo-dkms/.omarchy/patches/arm-kernel-headers.patch @@ -0,0 +1,11 @@ +--- a/PKGBUILD ++++ b/PKGBUILD +@@ -14,7 +14,9 @@ arch=(any) + license=('GPL-2.0-only AND GPL-3.0-or-later') +-checkdepends=('dkms' 'fakeroot' 'LINUX-HEADERS') ++checkdepends=('dkms' 'fakeroot') ++checkdepends_x86_64=('LINUX-HEADERS') ++checkdepends_aarch64=('linux-aarch64-headers') + source=("xpadneo-v${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz") + b2sums=('2858e466c5fde7e3d06d878dd7fb695751375e11a112bd3d389f9db5a82ef3ffcc1e3431b213f4807aeb8f05fd4280c7ca2a0a56305b0e984eb9b70df4843735') + diff --git a/pkgbuilds/xpadneo-dkms/PKGBUILD b/pkgbuilds/xpadneo-dkms/PKGBUILD index d14f85cb..3117cde6 100644 --- a/pkgbuilds/xpadneo-dkms/PKGBUILD +++ b/pkgbuilds/xpadneo-dkms/PKGBUILD @@ -9,11 +9,13 @@ pkgname=xpadneo-dkms pkgdesc='Advanced Linux Driver for Xbox One Wireless Gamepad' pkgver=0.10.4 -pkgrel=1 +pkgrel=1.1 url='https://github.com/atar-axis/xpadneo' arch=(any) license=('GPL-2.0-only AND GPL-3.0-or-later') -checkdepends=('dkms' 'fakeroot' 'LINUX-HEADERS') +checkdepends=('dkms' 'fakeroot') +checkdepends_x86_64=('LINUX-HEADERS') +checkdepends_aarch64=('linux-aarch64-headers') source=("xpadneo-v${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz") b2sums=('2858e466c5fde7e3d06d878dd7fb695751375e11a112bd3d389f9db5a82ef3ffcc1e3431b213f4807aeb8f05fd4280c7ca2a0a56305b0e984eb9b70df4843735') From de57b5dfc21961f5806ad6468b2f4944f12706f0 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 5 Sep 2026 00:03:20 -0400 Subject: [PATCH 30/63] Publish only intended package artifacts --- bin/push-build | 27 +++++++++++++++++++-------- build/build.sh | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/bin/push-build b/bin/push-build index 5b850126..a4abae99 100755 --- a/bin/push-build +++ b/bin/push-build @@ -149,11 +149,16 @@ fi # leave nvidia-580xx-dkms and opencl-nvidia-580xx behind. An output's own name # still matches, for pushing just one of them on purpose. # -# pkgbase comes from .PKGINFO rather than the PKGBUILD: it is what makepkg -# actually recorded, and it needs no guessing about which directory built what. -pkgbase_of() { +# Package identity comes from .PKGINFO rather than the filename or PKGBUILD: it +# is what makepkg actually recorded, and it handles epochs and split packages. +package_identity_of() { bsdtar -xOf "$1" .PKGINFO 2>/dev/null | - awk -F ' = ' '$1 == "pkgbase" { print $2; exit }' + awk -F ' = ' ' + $1 == "pkgname" { name = $2 } + $1 == "pkgbase" { base = $2 } + $1 == "pkgver" { version = $2 } + END { print name "\t" base "\t" version } + ' } FILES=() @@ -161,14 +166,18 @@ if [[ -z "$PACKAGES" ]]; then FILES=("${ALL_FILES[@]}") else declare -A MATCHED=() + declare -A LATEST_FILE=() + declare -A LATEST_VERSION=() for file in "${ALL_FILES[@]}"; do - # name-version-release-arch.pkg.tar.zst -> name - pkgname="${file%-*-*-*.pkg.tar.*}" - pkgbase=$(pkgbase_of "$BUILD_OUTPUT_DIR/$file") + IFS=$'\t' read -r pkgname pkgbase pkgver < <(package_identity_of "$BUILD_OUTPUT_DIR/$file") for wanted in $PACKAGES; do if [[ "$pkgname" == "$wanted" || "$pkgbase" == "$wanted" ]]; then - FILES+=("$file") MATCHED["$wanted"]=1 + if [[ -z "${LATEST_FILE[$pkgname]:-}" ]] || + [[ $(vercmp "$pkgver" "${LATEST_VERSION[$pkgname]}") -gt 0 ]]; then + LATEST_FILE["$pkgname"]="$file" + LATEST_VERSION["$pkgname"]="$pkgver" + fi break fi done @@ -181,6 +190,8 @@ else exit 1 fi done + + mapfile -t FILES < <(printf '%s\n' "${LATEST_FILE[@]}" | sort) fi if [[ ${#FILES[@]} -eq 0 ]]; then diff --git a/build/build.sh b/build/build.sh index 9357ebd4..9be27162 100755 --- a/build/build.sh +++ b/build/build.sh @@ -331,11 +331,39 @@ build_package() { if PACMAN=/usr/local/bin/pacman-for-makepkg makepkg "${makepkg_flags[@]}"; then # Ensure output directory exists mkdir -p "$BUILD_OUTPUT_DIR" - + + # Copy only the artifacts makepkg declares as outputs. A PKGBUILD may use + # another pacman package as a source (schist-bin does); a *.pkg.tar.* glob + # would mistake that source archive for one of our freshly built packages. + local -a package_files=() + mapfile -t package_files < <(makepkg --packagelist) + + if [[ ${#package_files[@]} -eq 0 ]]; then + echo " Makepkg produced no package files for $pkg" + FAILED_PACKAGES="$FAILED_PACKAGES $pkg" + return 1 + fi + local dependency_pkg_file="" - for pkg_file in *.pkg.tar.*; do - [[ -f "$pkg_file" && "$pkg_file" != *.sig ]] || continue + local -a new_pkgs=() + local pkg_path pkg_file + for pkg_path in "${package_files[@]}"; do + pkg_file=${pkg_path##*/} + if [[ ! -f "$pkg_file" ]]; then + # makepkg predicts an automatic -debug output whenever debug is + # enabled, but data-only packages may contain no symbols and therefore + # legitimately produce no debug archive. + if [[ "$pkg_file" == *-debug-*.pkg.tar.* ]]; then + continue + fi + + echo " Expected package file was not produced: $pkg_file" + FAILED_PACKAGES="$FAILED_PACKAGES $pkg" + return 1 + fi + cp "$pkg_file" "$BUILD_OUTPUT_DIR/" + new_pkgs+=("$pkg_file") if [[ "$(bsdtar -xOf "$pkg_file" .PKGINFO 2>/dev/null | sed -n 's/^pkgname = //p')" == "$pkg" ]]; then dependency_pkg_file="$BUILD_OUTPUT_DIR/$pkg_file" @@ -344,9 +372,7 @@ build_package() { cd "$BUILD_OUTPUT_DIR" - # Find ALL package files (handles split packages) - local new_pkgs=($(ls -t ${pkg}-*.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db')) - + # Add every output from this build, including split packages. if [[ ${#new_pkgs[@]} -gt 0 ]]; then repo-add omarchy-build.db.tar.zst "${new_pkgs[@]}" >/dev/null 2>&1 ln -sf omarchy-build.db.tar.zst omarchy-build.db From ca99c24b011c58843854b3a94a8336da21fba20b Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 5 Sep 2026 01:09:11 -0400 Subject: [PATCH 31/63] Add ARM builds for 1Password and Voxtype --- .gitignore | 1 + README.md | 58 ++++++- bin/sync-upstream | 141 +++++++++++++++- helpers/package-metadata.sh | 30 +++- helpers/upstream-github.sh | 174 ++++++++++++++++---- pkgbuilds/1password/.omarchy/package.json | 17 +- pkgbuilds/1password/PKGBUILD | 34 ++-- pkgbuilds/voxtype-bin/.omarchy/package.json | 81 ++++++++- pkgbuilds/voxtype-bin/PKGBUILD | 89 +++++++--- 9 files changed, 538 insertions(+), 87 deletions(-) diff --git a/.gitignore b/.gitignore index d5f201f4..3f0c128d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.firecrawl/ src/ logs/ build-output/ diff --git a/README.md b/README.md index c34f0047..d57fcb1a 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,8 @@ bin/sync-upstream openai-codex-desktop # Update specific packages Some vendors publish a release feed of their own that is faster and more precise than the AUR packaging of it. Those packages are `source: local` — Omarchy owns -the PKGBUILD — and declare where releases come from in one of two ways. +the PKGBUILD — and declare where releases come from either as data or, for an +unusual feed, a small hook. A vendor shipping tagged GitHub releases is pure data, declared as `upstream` in `.omarchy/package.json` with no code at all: @@ -361,6 +362,32 @@ digest GitHub's release API reports for every asset — see `pkgbuilds/schist-bin/.omarchy/package.json`. Either way the artifacts themselves are never downloaded. +An architecture may map to an ordered array when its PKGBUILD downloads more +than one release asset. Small versioned files outside the release assets can be +listed under `sources` and are downloaded and hashed when a new version appears: + +```json +"upstream": { + "github": "owner/project", + "digests": true, + "assets": { + "x86_64": ["tool-{pkgver}-x86_64", "tool-{pkgver}-x86_64.asc"], + "aarch64": ["tool-{pkgver}-aarch64", "tool-{pkgver}-aarch64.asc"] + }, + "sources": { + "any": ["https://raw.githubusercontent.com/owner/project/{tag}/LICENSE"] + } +} +``` + +Asset and source keys must be disjoint because each key maps to one PKGBUILD +checksum array (`any` means the unsuffixed `sha256sums`). + +Repositories whose historical releases use incompatible tag schemes may set +`"latest_only": true`. The provider then considers only the newest stable +GitHub release, while retaining all validation for that release. A quarantine +will wait for that release to age instead of falling back to an older one. + `{tag}` and `{pkgver}` interpolate into asset names; a leading `v` on the tag is stripped for `pkgver`; drafts and prereleases are ignored. Only the 100 most recent releases are considered. The provider fails closed on anything it cannot @@ -403,14 +430,33 @@ the tarball named by the selected dist-tag: `dist_tag` defaults to `latest`. The registry's publication timestamp is carried into the provider result, so `min_release_age` works for npm packages. -Exactly one of `github`, `git_tags`, or `npm` may appear in a declaration. + +A vendor with a plain-text Debian `Packages` index can use it to discover the +newest exact package version, then hash immutable source URLs: + +```json +"upstream": { + "debian": "https://example.com/debian/dists/stable/main/binary-amd64/Packages", + "package": "example-app", + "sources": { + "x86_64": ["https://example.com/tool-{pkgver}-x64.tar.gz"], + "aarch64": ["https://example.com/tool-{pkgver}-arm64.tar.gz"] + } +} +``` + +This deliberately accepts only Debian versions that are already valid Arch +`pkgver` values. Feeds needing epoch, revision, or filename translation retain +a hook. Exactly one of `github`, `git_tags`, `npm`, or `debian` may appear in a +declaration. A timestamped provider may also declare `"min_release_age": "24h"` (`s`/`m`/`h`/`d` suffix or bare seconds) to quarantine fresh releases until maintainers have had time to pull a bad or compromised one. GitHub Releases and -npm provide publication times; raw git tags do not, so combining `git_tags` -with this policy fails closed. The newest release that has cleared the window -ships, so a fast release cadence cannot starve updates. The window is enforced +npm provide publication times; raw git tags and Debian Packages indexes do not, +so combining either with this policy fails closed. The newest release that has +cleared the window ships, so a fast release cadence cannot starve updates. The +window is enforced centrally: whatever reports the release must prove its age via `published_at`, or the sync fails. A maintainer deliberately shipping inside the window runs `BYPASS_MIN_RELEASE_AGE=1 bin/sync-upstream ` locally and merges the @@ -679,7 +725,7 @@ Minimal examples: Fields: - `source`: `aur` or `local`. A `local` package can still follow an upstream release, either declaratively via `upstream` or with an `.omarchy/upstream.sh` hook. -- `upstream`: optional for `local` packages whose vendor ships tagged GitHub releases. `{ "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "": "name-{tag}.tar.xz" } }`, or `"digests": true` in place of `checksums` to use the release API's per-asset digests — see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`. +- `upstream`: optional for `local` packages following GitHub releases, git tags, npm dist-tags, or a Debian `Packages` index. GitHub architecture assets may be a string or an ordered array, and can be combined with disjoint versioned `sources` — see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`. - `min_release_age`: optional quarantine for upstream releases (`"24h"`, `"2d"`, or bare seconds). The newest release older than the window ships; anything younger waits, and a release whose age cannot be proven fails the sync. Bypass deliberately with `BYPASS_MIN_RELEASE_AGE=1 bin/sync-upstream `. - `sync`: optional for AUR packages; defaults to `true`. Set `false` for AUR-origin packages that Omarchy maintains manually. - `aur`: optional AUR package name when it differs from the local package directory, usually for split packages. diff --git a/bin/sync-upstream b/bin/sync-upstream index 04c7ac62..8b2864d2 100755 --- a/bin/sync-upstream +++ b/bin/sync-upstream @@ -20,8 +20,8 @@ Update packages that track an upstream vendor release feed instead of the AUR. Packages opt in declaratively through "upstream" in .omarchy/package.json. Providers cover GitHub Releases with checksum manifests or API asset digests, -semver-shaped git tags whose source URLs can be hashed, and npm dist-tags. See README.md for the -schemas. Anything outside those conventions may provide +semver-shaped git tags, npm dist-tags, and plain Debian Packages indexes. See +README.md for the schemas. Anything outside those conventions may provide pkgbuilds//.omarchy/upstream.sh, a hook that reports JSON on stdout: { @@ -378,6 +378,7 @@ sync_package() { github) release=$(github_upstream_release "$package_dir" "$min_age") || release_status=$? ;; git_tags) release=$(git_tags_upstream_release "$package_dir") || release_status=$? ;; npm) release=$(npm_upstream_release "$package_dir") || release_status=$? ;; + debian) release=$(debian_upstream_release "$package_dir") || release_status=$? ;; esac if [[ ${release_status:-0} -ne 0 ]]; then print_error "$provider upstream provider failed for $package" @@ -544,6 +545,31 @@ EOF check "bypass lifts the quarantine" "2.0.0" "$(jq -r '.pkgver // ""' <<<"$out")" check "x86_64 checksum via * binary-mode prefix" "$sum_x20" "$(jq -r '.sha256sums.x86_64[0] // ""' <<<"$out")" + cp "$pkg/.omarchy/package.json" "$pkg/normal.json" + jq '.upstream.latest_only = true' "$pkg/normal.json" > "$pkg/.omarchy/package.json" + FIXTURE_RELEASES=$(jq -n --arg old "$old2d" '[ + {tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false}, + {tag_name: "v0.4.1-8", published_at: $old, draft: false, prerelease: false} + ]') + FIXTURE_CHECKSUMS=$(printf '%s\n' \ + "$sum_x19 ./tool-v1.9.0-x64.tar.xz" \ + "$sum_a19 ./tool-v1.9.0-arm64.tar.xz") + out=$(github_upstream_release "$pkg" 0 2>/dev/null) || out="" + check "latest_only ignores incompatible historical tags" "1.9.0" "$(jq -r '.pkgver // ""' <<<"$out")" + cp "$pkg/normal.json" "$pkg/.omarchy/package.json" + FIXTURE_RELEASES=$(jq -n --arg young "$young" --arg old2 "$old2d" --arg old3 "$old3d" '[ + {tag_name: "v2.0.0", published_at: $young, draft: false, prerelease: false}, + {tag_name: "v1.9.9", published_at: $old2, draft: false, prerelease: true}, + {tag_name: "v1.9.8", published_at: $old2, draft: true, prerelease: false}, + {tag_name: "v1.9.0", published_at: $old2, draft: false, prerelease: false}, + {tag_name: "v1.8.0", published_at: $old3, draft: false, prerelease: false} + ]') + FIXTURE_CHECKSUMS=$(printf '%s\n' \ + "$sum_x19 ./tool-v1.9.0-x64.tar.xz" \ + "$sum_a19 tool-v1.9.0-arm64.tar.xz" \ + "$sum_x20 *tool-v2.0.0-x64.tar.xz" \ + "$sum_a20 tool-v2.0.0-arm64.tar.xz") + out=$(github_upstream_release "$pkg" 8640000 2>/dev/null) || out="" check "everything quarantined reports no update" "{}" "$(jq -c . <<<"$out")" @@ -637,6 +663,117 @@ EOF check "provider rejects a non-boolean digests" "1" "$rc" cp "$digpkg/good.json" "$digpkg/.omarchy/package.json" + echo "Ordered GitHub assets with supplemental sources:" + local multipkg="$TEMP_DIR/selftest-multi-assets" multi_sum support_sum multi_vst + mkdir -p "$multipkg/.omarchy" + printf 'pkgver=1.0.0\npkgrel=1\n' > "$multipkg/PKGBUILD" + cat > "$multipkg/.omarchy/package.json" <<'EOF' +{ + "source": "local", + "upstream": { + "github": "example/tool", + "digests": true, + "assets": { + "x86_64": ["tool-{pkgver}-x86_64", "tool-{pkgver}-x86_64.asc"], + "aarch64": ["tool-{pkgver}-aarch64", "tool-{pkgver}-aarch64.asc"] + }, + "sources": { + "any": ["https://example.test/tool/{tag}/support.txt"] + } + } +} +EOF + local sum_x19_sig sum_a19_sig + sum_x19_sig=$(printf '1%.0s' {1..64}) + sum_a19_sig=$(printf '2%.0s' {1..64}) + FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" --arg xs "$sum_x19_sig" \ + --arg a "$sum_a19" --arg as "$sum_a19_sig" '[ + {tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [ + {name: "tool-1.9.0-x86_64", digest: ("sha256:" + $x)}, + {name: "tool-1.9.0-x86_64.asc", digest: ("sha256:" + $xs)}, + {name: "tool-1.9.0-aarch64", digest: ("sha256:" + $a)}, + {name: "tool-1.9.0-aarch64.asc", digest: ("sha256:" + $as)} + ]} + ]') + upstream_fetch_source() { printf 'remote fixture for %s\n' "$1" > "$2"; } + multi_sum=$(github_upstream_release "$multipkg" 0 2>/dev/null) || multi_sum="" + support_sum=$(printf 'remote fixture for %s\n' 'https://example.test/tool/v1.9.0/support.txt' | sha256sum | cut -d' ' -f1) + check "ordered GitHub assets produce an ordered checksum array" "$sum_x19 $sum_x19_sig" \ + "$(jq -r '.sha256sums.x86_64 | join(" ")' <<<"$multi_sum")" + check "GitHub supplemental source is downloaded and hashed" "$support_sum" \ + "$(jq -r '.sha256sums.any[0]' <<<"$multi_sum")" + multi_vst=0; validate_package_metadata "$multipkg" >/dev/null || multi_vst=$? + check "GitHub asset lists and disjoint sources validate" "0" "$multi_vst" + jq '.upstream.sources.x86_64 = ["https://example.test/duplicate"]' \ + "$multipkg/.omarchy/package.json" > "$multipkg/overlap.json" + cp "$multipkg/.omarchy/package.json" "$multipkg/good.json" + cp "$multipkg/overlap.json" "$multipkg/.omarchy/package.json" + multi_vst=0; validate_package_metadata "$multipkg" >/dev/null || multi_vst=$? + check "GitHub assets and sources cannot target the same checksum array" "1" "$multi_vst" + cp "$multipkg/good.json" "$multipkg/.omarchy/package.json" + + echo "Debian Packages provider:" + local debpkg="$TEMP_DIR/selftest-debian" deb_sum deb_x_sum deb_a_sum deb_vst + mkdir -p "$debpkg/.omarchy" + printf 'pkgver=1.0.0\npkgrel=1\nsha256sums_x86_64=("old")\nsha256sums_aarch64=("old")\n' > "$debpkg/PKGBUILD" + cat > "$debpkg/.omarchy/package.json" <<'EOF' +{ + "source": "local", + "upstream": { + "debian": "https://packages.example.test/dists/stable/main/binary-amd64/Packages", + "package": "example-app", + "sources": { + "x86_64": ["https://downloads.example.test/app-{pkgver}-x64.tar.gz"], + "aarch64": ["https://downloads.example.test/app-{pkgver}-arm64.tar.gz"] + } + } +} +EOF + debian_fetch_packages() { + cat <<'EOF' +Package: unrelated +Version: 99.0.0 + +Package: example-app +Version: 1.9.0 + +Package: example-app +Version: 1.10.0 +EOF + } + deb_sum=$(debian_upstream_release "$debpkg") + deb_x_sum=$(printf 'remote fixture for %s\n' 'https://downloads.example.test/app-1.10.0-x64.tar.gz' | sha256sum | cut -d' ' -f1) + deb_a_sum=$(printf 'remote fixture for %s\n' 'https://downloads.example.test/app-1.10.0-arm64.tar.gz' | sha256sum | cut -d' ' -f1) + check "Debian provider selects the newest exact package stanza" "1.10.0" "$(jq -r '.pkgver' <<<"$deb_sum")" + check "Debian x86_64 source is hashed" "$deb_x_sum" "$(jq -r '.sha256sums.x86_64[0]' <<<"$deb_sum")" + check "Debian aarch64 source is hashed" "$deb_a_sum" "$(jq -r '.sha256sums.aarch64[0]' <<<"$deb_sum")" + deb_vst=0; validate_package_metadata "$debpkg" >/dev/null || deb_vst=$? + check "Debian declaration validates" "0" "$deb_vst" + printf 'pkgver=1.10.0\npkgrel=1\nsha256sums_x86_64=("old")\nsha256sums_aarch64=("old")\n' > "$debpkg/PKGBUILD" + check "checked-in Debian version avoids source downloads" "{}" "$(debian_upstream_release "$debpkg" | jq -c .)" + + echo "End-to-end Debian sync with the checked-in 1password recipe:" + local one_root="$TEMP_DIR/e2e-1password" one_dir one_version=8.12.35 + mkdir -p "$one_root" + cp -a "$BUILD_ROOT/pkgbuilds/1password" "$one_root/1password" + one_dir="$one_root/1password" + debian_fetch_packages() { + printf 'Package: 1password\nVersion: %s\n' "$one_version" + } + upstream_fetch_source() { printf 'remote fixture for %s\n' "$1" > "$2"; } + local one_prev_updated=$UPDATED one_prev_failed=$FAILED + PKGBUILDS_DIR="$one_root" sync_package 1password >/dev/null 2>&1 || true + check "1password sync updates without failures" "updated=1 failed=0" \ + "updated=$((UPDATED - one_prev_updated)) failed=$((FAILED - one_prev_failed))" + check "1password pkgver is the single download version source" "$one_version" \ + "$(grep -m1 '^pkgver=' "$one_dir/PKGBUILD" | cut -d= -f2-)" + check "1password pkgrel resets to 1" "1" \ + "$(grep -m1 '^pkgrel=' "$one_dir/PKGBUILD" | cut -d= -f2-)" + check "1password x86 URL follows the rewritten pkgver" "1password-${one_version}.x64.tar.gz" \ + "$(CARCH=x86_64 bash -c 'source "$1"; basename "${source_x86_64[0]}"' _ "$one_dir/PKGBUILD")" + check "1password ARM URL follows the rewritten pkgver" "1password-${one_version}.arm64.tar.gz" \ + "$(CARCH=aarch64 bash -c 'source "$1"; basename "${source_aarch64[0]}"' _ "$one_dir/PKGBUILD")" + echo "Quarantine backstop:" local rel st rel=$(jq -n --arg p "$old2d" '{pkgver: "1.9.0", published_at: $p, sha256sums: {}}') diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index 7382bbf2..043e2fc5 100644 --- a/helpers/package-metadata.sh +++ b/helpers/package-metadata.sh @@ -15,10 +15,11 @@ # { "source": "local", "channels": ["edge"] } # { "source": "local", "channels": ["edge", "rc", "stable"] } # { "source": "local", "min_release_age": "24h" } -# { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } } +# { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": ["name-{tag}-x64.tar.xz"] } } } # { "source": "local", "upstream": { "github": "owner/repo", "digests": true, "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } } # { "source": "local", "upstream": { "git_tags": "https://example/repo.git", "tag_pattern": "v{pkgver}", "sources": { "any": ["https://example/archive/{tag}.tar.gz"] } } } # { "source": "local", "upstream": { "npm": "@scope/package", "sources": { "any": ["{npm_tarball}"] } } } +# { "source": "local", "upstream": { "debian": "https://example/debian/dists/stable/main/binary-amd64/Packages", "package": "example", "sources": { "any": ["https://example/releases/{pkgver}.tar.gz"] } } } # # bin/sync-aur also writes upstream_commit for AUR-backed packages, and # bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on. @@ -482,30 +483,45 @@ validate_package_metadata() { (.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "array" and length > 0 and all(type == "string" and length > 0)) )); + def valid_assets: + type == "object" and length > 0 and (to_entries | all( + (.key | test("\\A[a-z0-9_]+\\z")) + and (.value | + (type == "string" and length > 0) + or (type == "array" and length > 0 and all(type == "string" and length > 0) and (unique | length) == length) + ) + )); if has("upstream") | not then true elif (.upstream | type) != "object" then false else .upstream | - ([has("github"), has("git_tags"), has("npm")] | map(select(.)) | length) == 1 + ([has("github"), has("git_tags"), has("npm"), has("debian")] | map(select(.)) | length) == 1 and if has("github") then (.github | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z")) and (if has("checksums") then (.checksums | type == "string" and length > 0) else true end) and (if has("digests") then (.digests | type == "boolean") else true end) + and (if has("latest_only") then (.latest_only | type == "boolean") else true end) and (has("checksums") != (has("digests") and .digests == true)) - and (.assets | type == "object" and length > 0 and (to_entries | all( - (.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0) - ))) + and (.assets | valid_assets) + and (if has("sources") then + (.sources | valid_sources) + and ((.assets | keys) as $assets | (.sources | keys) as $sources | ($assets - $sources | length) == ($assets | length)) + else true end) elif has("git_tags") then (.git_tags | type == "string" and test("\\Ahttps://[^[:space:]]+\\.git\\z")) and (.tag_pattern | type == "string" and (split("{pkgver}") | length) == 2) and (.sources | valid_sources) - else + elif has("npm") then (.npm | type == "string" and test("\\A(@[a-z0-9_.-]+/)?[a-z0-9_.-]+\\z")) and ((.dist_tag // "latest") | type == "string" and test("\\A[a-z0-9_.-]+\\z")) and (.sources | valid_sources) + else + (.debian | type == "string" and test("\\Ahttps://[^[:space:]]+\\z")) + and (.package | type == "string" and test("\\A[a-z0-9][a-z0-9+.-]*\\z")) + and (.sources | valid_sources) end end ' "$metadata" >/dev/null; then - echo "invalid upstream for $(basename "$pkgdir"): configure exactly one valid github, git_tags, or npm provider" + echo "invalid upstream for $(basename "$pkgdir"): configure exactly one valid github, git_tags, npm, or debian provider" return 1 fi diff --git a/helpers/upstream-github.sh b/helpers/upstream-github.sh index 1b6e64ce..6c7e1ba4 100644 --- a/helpers/upstream-github.sh +++ b/helpers/upstream-github.sh @@ -20,8 +20,8 @@ # "v", which is stripped for pkgver. Drafts and prereleases are ignored. The # provider emits the same JSON contract as an upstream.sh hook, so # bin/sync-upstream's validation and min_release_age backstop apply -# unchanged. Git-tag and npm providers below cover projects without GitHub -# releases; a feed that fits no convention keeps a bespoke hook. +# unchanged. Git-tag, npm, and Debian Packages providers below cover projects +# without GitHub releases; a feed that fits no convention keeps a bespoke hook. # Return the single declarative provider selected by a package. An empty # result means either no provider or an invalid/ambiguous declaration; the @@ -31,7 +31,7 @@ package_upstream_provider() { metadata=$(metadata_file_for_dir "$pkgdir") jq -r ' (.upstream? | objects) as $u - | [$u | keys[] | select(. == "github" or . == "git_tags" or . == "npm")] + | [$u | keys[] | select(. == "github" or . == "git_tags" or . == "npm" or . == "debian")] | if length == 1 then .[0] else "" end ' "$metadata" } @@ -64,6 +64,11 @@ npm_fetch_metadata() { curl -fsSL "https://registry.npmjs.org/$encoded" } +debian_fetch_packages() { + local url="$1" + curl --proto '=https' --proto-redir '=https' -fsSL "$url" +} + upstream_fetch_source() { local url="$1" output="$2" curl --proto '=https' --proto-redir '=https' -fsSL -o "$output" "$url" @@ -198,6 +203,71 @@ npm_upstream_release() { printf '%s\n' "$release" } +# Discover a package version from a plain-text Debian Packages index, then +# hash the declared immutable sources. This intentionally supports only +# versions that are already valid Arch pkgver values; feeds needing Debian +# epoch/revision translation keep a package-specific hook. +debian_upstream_release() { + local package_dir="$1" metadata index_url package packages + metadata=$(metadata_file_for_dir "$package_dir") + index_url=$(jq -r '.upstream.debian // ""' "$metadata") + package=$(jq -r '.upstream.package // ""' "$metadata") + + if [[ ! "$index_url" =~ ^https://[^[:space:]]+$ || ! "$package" =~ ^[a-z0-9][a-z0-9+.-]*$ ]]; then + echo "invalid Debian Packages provider configuration" >&2 + return 1 + fi + if ! jq -e ' + .upstream.sources | type == "object" and length > 0 and (to_entries | all( + (.key | test("\\A[a-z0-9_]+\\z")) + and (.value | type == "array" and length > 0 and all(type == "string" and length > 0)) + )) + ' "$metadata" >/dev/null; then + echo "invalid Debian Packages source mapping" >&2 + return 1 + fi + if ! packages=$(debian_fetch_packages "$index_url"); then + echo "could not fetch Debian Packages index: $index_url" >&2 + return 1 + fi + packages=${packages//$'\r'/} + + local best_pkgver="" candidate + while IFS= read -r candidate; do + if [[ ! "$candidate" =~ ^[A-Za-z0-9][A-Za-z0-9._+]*$ ]]; then + echo "$package has an unusable Debian version: ${candidate:-}" >&2 + return 1 + fi + if [[ -z "$best_pkgver" || $(vercmp "$candidate" "$best_pkgver") -gt 0 ]]; then + best_pkgver="$candidate" + fi + done < <(awk -v target="$package" ' + BEGIN { RS = ""; FS = "\n" } + { + name = version = "" + for (i = 1; i <= NF; i++) { + if ($i ~ /^Package: /) name = substr($i, 10) + if ($i ~ /^Version: /) version = substr($i, 10) + } + if (name == target) print version + } + ' <<<"$packages") + + [[ -n "$best_pkgver" ]] || { + echo "no usable $package release found in $index_url" >&2 + return 1 + } + + local current_pkgver + current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'") + if [[ $(vercmp "$best_pkgver" "$current_pkgver") -le 0 ]]; then + echo '{}' + return 0 + fi + + upstream_hash_sources "$package_dir" "$best_pkgver" "$best_pkgver" +} + # Emits the newest qualifying release as hook-contract JSON. min_release_age # is honored during selection (newest release older than the window wins, # even when a younger one exists) and BYPASS_MIN_RELEASE_AGE=1 lifts it. @@ -206,7 +276,7 @@ npm_upstream_release() { # not silently choose from. github_upstream_release() { local package_dir="$1" min_age="${2:-0}" - local metadata repo checksums_name use_digests + local metadata repo checksums_name use_digests latest_only metadata=$(metadata_file_for_dir "$package_dir") repo=$(jq -r '(.upstream? | objects | .github) // ""' "$metadata") @@ -218,10 +288,15 @@ github_upstream_release() { # reaches this provider without running the validator first. checksums_name=$(jq -r '(.upstream? | objects | .checksums) | strings' "$metadata") use_digests=$(jq -r '(.upstream? | objects | .digests) | if . == null then "false" elif type == "boolean" then tostring else "invalid" end' "$metadata") + latest_only=$(jq -r '(.upstream? | objects | .latest_only) | if . == null then "false" elif type == "boolean" then tostring else "invalid" end' "$metadata") if [[ "$use_digests" == "invalid" ]]; then echo "upstream.digests must be true or false" >&2 return 1 fi + if [[ "$latest_only" == "invalid" ]]; then + echo "upstream.latest_only must be true or false" >&2 + return 1 + fi if [[ -n "$checksums_name" && "$use_digests" == "true" ]]; then echo "upstream sets both checksums and digests; keep exactly one" >&2 return 1 @@ -230,6 +305,27 @@ github_upstream_release() { echo "upstream needs either checksums (a manifest asset name) or digests: true" >&2 return 1 fi + if ! jq -e ' + def valid_sources: + type == "object" and length > 0 and (to_entries | all( + (.key | test("\\A[a-z0-9_]+\\z")) + and (.value | type == "array" and length > 0 and all(type == "string" and length > 0)) + )); + (.upstream.assets | type == "object" and length > 0 and (to_entries | all( + (.key | test("\\A[a-z0-9_]+\\z")) + and (.value | + (type == "string" and length > 0) + or (type == "array" and length > 0 and all(type == "string" and length > 0) and (unique | length) == length) + ) + ))) + and (if .upstream | has("sources") then + (.upstream.sources | valid_sources) + and ((.upstream.assets | keys) as $assets | (.upstream.sources | keys) as $sources | ($assets - $sources | length) == ($assets | length)) + else true end) + ' "$metadata" >/dev/null; then + echo "invalid upstream.assets or upstream.sources mapping" >&2 + return 1 + fi local arches mapfile -t arches < <(jq -r '(.upstream? | objects | .assets) // {} | keys[]' "$metadata") if [[ ${#arches[@]} -eq 0 ]]; then @@ -243,6 +339,9 @@ github_upstream_release() { echo "could not fetch the release feed for $repo" >&2 return 1 fi + if [[ "$latest_only" == "true" ]]; then + releases=$(jq '[.[] | select((.draft or .prerelease) | not)][0:1]' <<<"$releases") + fi local candidates=0 best_tag="" best_pkgver="" best_published_at="" local tag published_at pkgver published_epoch @@ -302,39 +401,50 @@ github_upstream_release() { return 1 fi - local jq_args=(--arg pkgver "$best_pkgver" --arg published_at "$best_published_at") - local jq_filter='{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}' - local arch template filename checksum checksum_source + local result + result=$(jq -n --arg pkgver "$best_pkgver" --arg published_at "$best_published_at" \ + '{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}') + local arch template filename checksum checksum_source sums for arch in "${arches[@]}"; do if [[ ! "$arch" =~ ^[a-z0-9_]+$ ]]; then echo "invalid architecture key in upstream.assets: '$arch'" >&2 return 1 fi - template=$(jq -r --arg arch "$arch" '.upstream.assets[$arch]' "$metadata") - filename=${template//\{pkgver\}/$best_pkgver} - filename=${filename//\{tag\}/$best_tag} - if [[ "$use_digests" == "true" ]]; then - # Only a "sha256:" digest is stripped to its hex; any other shape - # falls through empty and fails the check below. - checksum=$(jq -r --arg tag "$best_tag" --arg name "$filename" ' - first(.[] | select(.tag_name == $tag)) | (.assets // [])[] - | select(.name == $name) | (.digest // "") - | if type == "string" and test("\\Asha256:[0-9a-f]{64}\\z") then ltrimstr("sha256:") else "" end - ' <<<"$releases") - checksum_source="the release API digest" - else - # Manifest lines are " ", with the name sometimes prefixed - # "./" (sha256sum of a local path) or "*" (binary-mode marker). - checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums") - checksum_source="$checksums_name" - fi - if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then - echo "no valid checksum for $filename in $repo $best_tag $checksum_source" >&2 - return 1 - fi - jq_args+=(--arg "sum_$arch" "$checksum") - jq_filter+=" | .sha256sums[\"$arch\"] = [\$sum_$arch]" + sums='[]' + while IFS= read -r template; do + filename=${template//\{pkgver\}/$best_pkgver} + filename=${filename//\{tag\}/$best_tag} + if [[ "$use_digests" == "true" ]]; then + # Only a "sha256:" digest is stripped to its hex; any other shape + # falls through empty and fails the check below. + checksum=$(jq -r --arg tag "$best_tag" --arg name "$filename" ' + first(.[] | select(.tag_name == $tag)) | (.assets // [])[] + | select(.name == $name) | (.digest // "") + | if type == "string" and test("\\Asha256:[0-9a-f]{64}\\z") then ltrimstr("sha256:") else "" end + ' <<<"$releases") + checksum_source="the release API digest" + else + # Manifest lines are " ", with the name sometimes prefixed + # "./" (sha256sum of a local path) or "*" (binary-mode marker). + checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums") + checksum_source="$checksums_name" + fi + if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then + echo "no valid checksum for $filename in $repo $best_tag $checksum_source" >&2 + return 1 + fi + sums=$(jq -c --arg checksum "$checksum" '. + [$checksum]' <<<"$sums") + done < <(jq -r --arg arch "$arch" ' + .upstream.assets[$arch] | if type == "array" then .[] else . end + ' "$metadata") + result=$(jq -c --arg arch "$arch" --argjson sums "$sums" '.sha256sums[$arch] = $sums' <<<"$result") done - jq -n "${jq_args[@]}" "$jq_filter" + if jq -e '.upstream | has("sources")' "$metadata" >/dev/null; then + local source_release + source_release=$(upstream_hash_sources "$package_dir" "$best_pkgver" "$best_tag") || return 1 + result=$(jq -c --argjson source "$source_release" '.sha256sums += $source.sha256sums' <<<"$result") + fi + + printf '%s\n' "$result" } diff --git a/pkgbuilds/1password/.omarchy/package.json b/pkgbuilds/1password/.omarchy/package.json index b3137453..663db8e5 100644 --- a/pkgbuilds/1password/.omarchy/package.json +++ b/pkgbuilds/1password/.omarchy/package.json @@ -1,5 +1,18 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "e323d0d1f8dea6b75bb651ce14acc73904cd0326" + "upstream": { + "debian": "https://downloads.1password.com/linux/debian/amd64/dists/stable/main/binary-amd64/Packages", + "package": "1password", + "sources": { + "x86_64": [ + "https://downloads.1password.com/linux/tar/stable/x86_64/1password-{pkgver}.x64.tar.gz", + "https://downloads.1password.com/linux/tar/stable/x86_64/1password-{pkgver}.x64.tar.gz.sig" + ], + "aarch64": [ + "https://downloads.1password.com/linux/tar/stable/aarch64/1password-{pkgver}.arm64.tar.gz", + "https://downloads.1password.com/linux/tar/stable/aarch64/1password-{pkgver}.arm64.tar.gz.sig" + ] + } + } } diff --git a/pkgbuilds/1password/PKGBUILD b/pkgbuilds/1password/PKGBUILD index 80d90d80..4e91196d 100644 --- a/pkgbuilds/1password/PKGBUILD +++ b/pkgbuilds/1password/PKGBUILD @@ -1,19 +1,33 @@ pkgname=1password -_tarver=8.12.34 -_tar="1password-${_tarver}.x64.tar.gz" -pkgver=${_tarver//-/_} -pkgrel=34 +case "$CARCH" in + x86_64) _tararch=x64 ;; + aarch64) _tararch=arm64 ;; + *) return 1 ;; +esac + +pkgver=8.12.34 +pkgrel=35 conflicts=('1password-beta' '1password-beta-bin') pkgdesc="Password manager and secure wallet" -arch=('x86_64') +arch=('x86_64' 'aarch64') url='https://1password.com' license=('LicenseRef-1Password-Proprietary') options=(!strip) install="1password.install" -source=(https://downloads.1password.com/linux/tar/stable/${CARCH}/${_tar}{,.sig}) -sha256sums=('297784aa66770b645607a7f04c9ba2c4aebed4f46d21202487f521ba572b7b13' - 'ec085bef60de748895d3c51a8208301ba2ac8fb47db99334539ba4bd1d3260d7' +source_x86_64=( + "https://downloads.1password.com/linux/tar/stable/x86_64/1password-${pkgver}.x64.tar.gz" + "https://downloads.1password.com/linux/tar/stable/x86_64/1password-${pkgver}.x64.tar.gz.sig" +) +source_aarch64=( + "https://downloads.1password.com/linux/tar/stable/aarch64/1password-${pkgver}.arm64.tar.gz" + "https://downloads.1password.com/linux/tar/stable/aarch64/1password-${pkgver}.arm64.tar.gz.sig" +) +sha256sums_x86_64=('297784aa66770b645607a7f04c9ba2c4aebed4f46d21202487f521ba572b7b13' + 'ec085bef60de748895d3c51a8208301ba2ac8fb47db99334539ba4bd1d3260d7' +) +sha256sums_aarch64=('ea5102363d6cf3442b96a7abd6743da8c1d261f56a628e1a3c183d84fa65fdcb' + 'f44db73fa44c3f68c3ab78a9cce140be9355de1dd9be4ce731c9d6597960c907' ) validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22') @@ -21,7 +35,7 @@ package() { depends=('hicolor-icon-theme' 'libgtk-3.so=0' 'nss' 'xdg-utils') # Go to source directory - cd "1password-${_tarver}.x64" + cd "1password-${pkgver}.${_tararch}" # Install icons resolutions=(32x32 64x64 256x256 512x512) @@ -49,7 +63,7 @@ EOF" > ./com.1password.1Password.policy # Move package contents to /opt/1Password cd "${srcdir}" install -dm0755 "${pkgdir}"/opt - mv "1password-${_tarver}.x64" "${pkgdir}/opt/1Password" + mv "1password-${pkgver}.${_tararch}" "${pkgdir}/opt/1Password" # Cleanup un-needed files rm "${pkgdir}"/opt/1Password/com.1password.1Password.policy "${pkgdir}"/opt/1Password/com.1password.1Password.policy.tpl "${pkgdir}"/opt/1Password/install_biometrics_policy.sh diff --git a/pkgbuilds/voxtype-bin/.omarchy/package.json b/pkgbuilds/voxtype-bin/.omarchy/package.json index 0e2fc7fa..778fbb52 100644 --- a/pkgbuilds/voxtype-bin/.omarchy/package.json +++ b/pkgbuilds/voxtype-bin/.omarchy/package.json @@ -1,5 +1,82 @@ { - "source": "aur", + "source": "local", "release_ring": "fast", - "upstream_commit": "6dbf4bedbba0a2f90d8e813becfa5ba289653562" + "min_release_age": "24h", + "upstream": { + "github": "peteonrails/voxtype", + "digests": true, + "latest_only": true, + "assets": { + "x86_64": [ + "voxtype-{pkgver}-linux-x86_64-avx2", + "voxtype-{pkgver}-linux-x86_64-avx2.asc", + "voxtype-{pkgver}-linux-x86_64-avx512", + "voxtype-{pkgver}-linux-x86_64-avx512.asc", + "voxtype-{pkgver}-linux-x86_64-vulkan", + "voxtype-{pkgver}-linux-x86_64-vulkan.asc", + "voxtype-{pkgver}-linux-x86_64-onnx-avx2", + "voxtype-{pkgver}-linux-x86_64-onnx-avx2.asc", + "voxtype-{pkgver}-linux-x86_64-onnx-avx512", + "voxtype-{pkgver}-linux-x86_64-onnx-avx512.asc", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-12", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-12.asc", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-12.libonnxruntime_providers_cuda.so", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-12.libonnxruntime_providers_shared.so", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-13", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-13.asc", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-13.libonnxruntime_providers_cuda.so", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-13.libonnxruntime_providers_shared.so", + "voxtype-{pkgver}-linux-x86_64-onnx-cuda-13.libonnxruntime.so.1.24.4", + "voxtype-{pkgver}-linux-x86_64-onnx-migraphx", + "voxtype-{pkgver}-linux-x86_64-onnx-migraphx.asc", + "voxtype-{pkgver}-linux-x86_64-onnx-migraphx.libonnxruntime_providers_migraphx.so", + "voxtype-{pkgver}-linux-x86_64-onnx-migraphx.libonnxruntime_providers_shared.so", + "voxtype-{pkgver}-linux-x86_64-osd", + "voxtype-{pkgver}-linux-x86_64-osd.asc", + "voxtype-{pkgver}-linux-x86_64-osd-gtk4", + "voxtype-{pkgver}-linux-x86_64-osd-gtk4.asc", + "voxtype-{pkgver}-linux-x86_64-osd-quickshell", + "voxtype-{pkgver}-linux-x86_64-osd-quickshell.asc", + "voxtype-{pkgver}-linux-x86_64-audio-bridge", + "voxtype-{pkgver}-linux-x86_64-audio-bridge.asc" + ], + "aarch64": [ + "voxtype-{pkgver}-linux-aarch64-cpu", + "voxtype-{pkgver}-linux-aarch64-cpu.asc", + "voxtype-{pkgver}-linux-aarch64-onnx", + "voxtype-{pkgver}-linux-aarch64-onnx.asc", + "voxtype-{pkgver}-linux-aarch64-osd", + "voxtype-{pkgver}-linux-aarch64-osd.asc", + "voxtype-{pkgver}-linux-aarch64-osd-gtk4", + "voxtype-{pkgver}-linux-aarch64-osd-gtk4.asc", + "voxtype-{pkgver}-linux-aarch64-osd-quickshell", + "voxtype-{pkgver}-linux-aarch64-osd-quickshell.asc", + "voxtype-{pkgver}-linux-aarch64-audio-bridge", + "voxtype-{pkgver}-linux-aarch64-audio-bridge.asc" + ] + }, + "sources": { + "any": [ + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/config/default.toml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/packaging/systemd/voxtype.service", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/packaging/completions/voxtype.bash", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/packaging/completions/voxtype.zsh", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/packaging/completions/voxtype.fish", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/LICENSE", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/README.md", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/packaging/voxtype-configure.desktop", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/packaging/scripts/voxtype-configure-launcher", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/shell.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/OsdSurface.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/EnginePicker.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/MeetingControls.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/voxtype-shared/Theme.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/voxtype-shared/StateReader.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/voxtype-shared/AudioBridge.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/voxtype-shared/StyleLoader.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/voxtype-shared/RecipeRenderer.qml", + "https://raw.githubusercontent.com/peteonrails/voxtype/{tag}/quickshell/voxtype-shared/qmldir" + ] + } + } } diff --git a/pkgbuilds/voxtype-bin/PKGBUILD b/pkgbuilds/voxtype-bin/PKGBUILD index 62ad9b9f..5333cd28 100644 --- a/pkgbuilds/voxtype-bin/PKGBUILD +++ b/pkgbuilds/voxtype-bin/PKGBUILD @@ -3,7 +3,7 @@ pkgname=voxtype-bin pkgver=1.0.1 pkgrel=1 pkgdesc="Push-to-talk voice-to-text for Linux (pre-built binaries)" -arch=('x86_64') +arch=('x86_64' 'aarch64') url="https://voxtype.io" license=('MIT') depends=( @@ -47,7 +47,7 @@ validpgpkeys=( '9CCF7915B750CAE8B095ED1AA3FC9F33FD209279' ) _github="https://github.com/peteonrails/voxtype/releases/download/v$pkgver" -source=( +source_x86_64=( # Whisper binaries "voxtype-$pkgver-avx2::$_github/voxtype-$pkgver-linux-x86_64-avx2" "voxtype-$pkgver-avx2.asc::$_github/voxtype-$pkgver-linux-x86_64-avx2.asc" @@ -97,7 +97,23 @@ source=( "voxtype-$pkgver-osd-quickshell.asc::$_github/voxtype-$pkgver-linux-x86_64-osd-quickshell.asc" "voxtype-$pkgver-audio-bridge::$_github/voxtype-$pkgver-linux-x86_64-audio-bridge" "voxtype-$pkgver-audio-bridge.asc::$_github/voxtype-$pkgver-linux-x86_64-audio-bridge.asc" - # Config and support files +) +source_aarch64=( + "voxtype-$pkgver-cpu::$_github/voxtype-$pkgver-linux-aarch64-cpu" + "voxtype-$pkgver-cpu.asc::$_github/voxtype-$pkgver-linux-aarch64-cpu.asc" + "voxtype-$pkgver-onnx::$_github/voxtype-$pkgver-linux-aarch64-onnx" + "voxtype-$pkgver-onnx.asc::$_github/voxtype-$pkgver-linux-aarch64-onnx.asc" + "voxtype-$pkgver-osd::$_github/voxtype-$pkgver-linux-aarch64-osd" + "voxtype-$pkgver-osd.asc::$_github/voxtype-$pkgver-linux-aarch64-osd.asc" + "voxtype-$pkgver-osd-gtk4::$_github/voxtype-$pkgver-linux-aarch64-osd-gtk4" + "voxtype-$pkgver-osd-gtk4.asc::$_github/voxtype-$pkgver-linux-aarch64-osd-gtk4.asc" + "voxtype-$pkgver-osd-quickshell::$_github/voxtype-$pkgver-linux-aarch64-osd-quickshell" + "voxtype-$pkgver-osd-quickshell.asc::$_github/voxtype-$pkgver-linux-aarch64-osd-quickshell.asc" + "voxtype-$pkgver-audio-bridge::$_github/voxtype-$pkgver-linux-aarch64-audio-bridge" + "voxtype-$pkgver-audio-bridge.asc::$_github/voxtype-$pkgver-linux-aarch64-audio-bridge.asc" +) +source=( + # Architecture-independent config and support files "config-$pkgver.toml::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/config/default.toml" "voxtype-$pkgver.service::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/packaging/systemd/voxtype.service" "voxtype-$pkgver.bash::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/packaging/completions/voxtype.bash" @@ -125,46 +141,62 @@ source=( "quickshell-voxtype-shared-RecipeRenderer-$pkgver.qml::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/RecipeRenderer.qml" "quickshell-voxtype-shared-qmldir-$pkgver::https://raw.githubusercontent.com/peteonrails/voxtype/v$pkgver/quickshell/voxtype-shared/qmldir" ) -sha256sums=( +sha256sums_x86_64=( # Whisper binaries 'cb3843a894ef47aca230b30bb1c45c2ef8e0d015adf2fa754d60e55123165fd0' # voxtype-avx2 - 'SKIP' # voxtype-avx2.asc + '21357d39412619e8a3fc473153e6e2a28b219aa02007682737afedd73a08bdbe' # voxtype-avx2.asc '77d49275ae4c3a6d93671014278b0dabd69ba52e65d2ee6ab3ec1bff0af34d55' # voxtype-avx512 - 'SKIP' # voxtype-avx512.asc + 'e3421641b4b5fa5d42ee0698eb43552e177f8b095a1acea32445693404b4fd54' # voxtype-avx512.asc 'c569d038057464aa60290296794bcbd79b928ee0efd038e33062a4c015558ed8' # voxtype-vulkan - 'SKIP' # voxtype-vulkan.asc + 'a82bd4108dc70d7d2b6e25a83a6744170502f5110780d4a2e97a3e76047ea60d' # voxtype-vulkan.asc # ONNX CPU binaries '19b8716ff7bd388ffb18843fc0ee5e614faa84544d22918ecf72a7c3d6c02929' # voxtype-onnx-avx2 - 'SKIP' # voxtype-onnx-avx2.asc + '3021aa3a13555bdab0ecccfb522db8391d7e05810d218608a6372477f11cb2af' # voxtype-onnx-avx2.asc '69a6e6fc1c04a55eeb62cf1eaa5fe81873c70eabffff120ce1a4ff7ef62fd559' # voxtype-onnx-avx512 - 'SKIP' # voxtype-onnx-avx512.asc + '305d33b6f4dc8ac7a38a5f522c1af95b462355fa057b2eb39d3add59303145e1' # voxtype-onnx-avx512.asc # ONNX CUDA 12 binary + companion .so 'e7180f17add10bb441368d8495fc99bde0ba1a1978ea5f7a52d7e626779eca16' # voxtype-onnx-cuda-12 - 'SKIP' # voxtype-onnx-cuda-12.asc + '34f7242fe16867ab029af9ed4854d3930aa2ed91bc37e062962edbfc65522af5' # voxtype-onnx-cuda-12.asc 'a8584727d51ba646ac63fc991c2f36ad6cd5b8cc8b1141896e46938700b888d0' # cuda-12.libonnxruntime_providers_cuda.so '1b028afc079628d76a28d7eb09700a4baead4a27f9634ba82c35398486134114' # cuda-12.libonnxruntime_providers_shared.so # ONNX CUDA 13 binary + companion .so + dlopen-target libonnxruntime '4de2aac42b67a05c34ac0b23f771fc83968670fd77d14781cbf4483524304cd1' # voxtype-onnx-cuda-13 - 'SKIP' # voxtype-onnx-cuda-13.asc + 'adb768be0b115f38bc28ea35614ae59e1c1adfb6e11ae08a252c45d52932fab2' # voxtype-onnx-cuda-13.asc 'b6cb7744d0efd2faced5c83ead374c13e7f2630b5a249ffaf393cdb1e092c92b' # cuda-13.libonnxruntime_providers_cuda.so 'c6a12593396095f5670160e284c35d1700b7708cf3037b7042e2a5200ccae772' # cuda-13.libonnxruntime_providers_shared.so '1aacefdf0b4afa145d410b2381bbc3db3d978c485fb182c42a2b0b09f91f5310' # cuda-13.libonnxruntime.so.1.24.4 # ONNX MIGraphX binary + companion .so 'f7a4cc0deaf35110b5106f1fc1f83dded41c0b1780f9fb5aa9cccc3c62e5eb6a' # voxtype-onnx-migraphx - 'SKIP' # voxtype-onnx-migraphx.asc + '9e9c767605923c084c97659326cb0f03ad4e5db0aded4da052fa208b4de9cf45' # voxtype-onnx-migraphx.asc 'ddd67e6193ade819ee21f1706d1b03b9151f1d2d2843701e2d19d8b183631707' # migraphx.libonnxruntime_providers_migraphx.so '17f7cf47ad0d7b5ac895ae588fd62c7f85a13842588161b6a24c7d480f062be4' # migraphx.libonnxruntime_providers_shared.so # OSD launcher + GTK4 frontend '7250027b1672507a6d584f795731c87e1d3b5c1de891438bd55e34b136a2d5cc' # voxtype-osd - 'SKIP' # voxtype-osd.asc + '1a944200fbac1e773c70d036075745b0e868885443e29c30797f262d44928cc1' # voxtype-osd.asc '74fb0f6ad87feb0c1c9e06a8b28a7f7ecee101caef4248f77dcf613b5271238e' # voxtype-osd-gtk4 - 'SKIP' # voxtype-osd-gtk4.asc + '60224685717fea0582d570160282e9e215cd4713b3bdb695edc7bae8b6860cac' # voxtype-osd-gtk4.asc # Quickshell OSD launcher + audio-bridge sidecar (new in v0.7.5) 'b809c5140e844a6add801d7e592775cd89af8cce73fa399b6a3aec15dfd09533' # voxtype-osd-quickshell - 'SKIP' # voxtype-osd-quickshell.asc + '39412691ab1f8ffe4fa6bf1eb9763c29f94b8892d1ba59fd49dc65dd0d80297a' # voxtype-osd-quickshell.asc '45776290e364194d83a8b89445166406c278e890507bf07ec52a5f0e8fa57720' # voxtype-audio-bridge - 'SKIP' # voxtype-audio-bridge.asc - # Config and support files + '5be1174a78d70b78fd8d923c88e3406962443b39a1c452eb985b1e2484ef5668' # voxtype-audio-bridge.asc +) +sha256sums_aarch64=( + 'b5e31a85aaa952d1a78c12b8a16ba5cbdcd92eb31adc7d1a908f3c9d06edd4f1' # voxtype-cpu + '3cd2f3fafca40e394a42b90dac031c67160256898a7f4a52893585b99f8f74bb' # voxtype-cpu.asc + 'c3771f3e568629178201990976520f88da6d7599ec2d9e404a137570d6c1e108' # voxtype-onnx + 'cd58773349eac0108cdad0efcc78d13f5d7c21623117381afa99b912c397b94a' # voxtype-onnx.asc + 'ea910d4fd1fe331d38dbed1c3a639cb7e0c04542919192ff6f74be2139afe3c6' # voxtype-osd + 'e079ebdd567e318502b710af95e4987fe13623d65673e21877afe88fe18bda55' # voxtype-osd.asc + '0d2148e0cd32bac538692470edc06aa9a2f5c6a891aaa373f59fbe78c247fae3' # voxtype-osd-gtk4 + '0ce841f2caa9a1a7e8294a521bef0bab8823dddacd36fff66012b1127e0c1957' # voxtype-osd-gtk4.asc + '097bd518d5e2eac2c3cbad714b65dd8058c818dcb4d900b9a16e442af7d65b8a' # voxtype-osd-quickshell + 'f48b8071f78fde0b2d20072e875a8b8e0d8fab4caf6c8ec91cd8e2aa0b031063' # voxtype-osd-quickshell.asc + '35170ad89fea2874fce0f08758ccc2164892ed643aacae632bcfbc6f10433976' # voxtype-audio-bridge + '787965900647ee9b04c8b65123d04b63e38636bfc671fa3b53360823f886b42c' # voxtype-audio-bridge.asc +) +sha256sums=( + # Architecture-independent config and support files 'f4b2bccd56b31a6a50e1c0a8b6b72383dc1e636f9ccb8cb070442d58b7314579' # config/default.toml '531c3658e229619e56bb01659fb81f401767b85e1d6e2acd1ac67ee3414a168c' # voxtype.service '65c95805d9b03ccc2fadb9d63a03ab79974b00091df8457ee8ef290ec6bd5b12' # voxtype.bash @@ -189,16 +221,20 @@ sha256sums=( ) package() { - # Install Whisper CPU binaries to /usr/lib/voxtype/ - install -Dm755 "$srcdir/voxtype-$pkgver-avx2" "$pkgdir/usr/lib/voxtype/voxtype-avx2" - install -Dm755 "$srcdir/voxtype-$pkgver-avx512" "$pkgdir/usr/lib/voxtype/voxtype-avx512" + if [[ "$CARCH" == aarch64 ]]; then + install -Dm755 "$srcdir/voxtype-$pkgver-cpu" "$pkgdir/usr/lib/voxtype/voxtype-cpu" + install -Dm755 "$srcdir/voxtype-$pkgver-onnx" "$pkgdir/usr/lib/voxtype/voxtype-onnx" + else + # Install Whisper CPU binaries to /usr/lib/voxtype/ + install -Dm755 "$srcdir/voxtype-$pkgver-avx2" "$pkgdir/usr/lib/voxtype/voxtype-avx2" + install -Dm755 "$srcdir/voxtype-$pkgver-avx512" "$pkgdir/usr/lib/voxtype/voxtype-avx512" - # Install Whisper Vulkan GPU binary - install -Dm755 "$srcdir/voxtype-$pkgver-vulkan" "$pkgdir/usr/lib/voxtype/voxtype-vulkan" + # Install Whisper Vulkan GPU binary + install -Dm755 "$srcdir/voxtype-$pkgver-vulkan" "$pkgdir/usr/lib/voxtype/voxtype-vulkan" - # Install ONNX CPU binaries (no GPU EP, no companion .so files) - install -Dm755 "$srcdir/voxtype-$pkgver-onnx-avx2" "$pkgdir/usr/lib/voxtype/voxtype-onnx-avx2" - install -Dm755 "$srcdir/voxtype-$pkgver-onnx-avx512" "$pkgdir/usr/lib/voxtype/voxtype-onnx-avx512" + # Install ONNX CPU binaries (no GPU EP, no companion .so files) + install -Dm755 "$srcdir/voxtype-$pkgver-onnx-avx2" "$pkgdir/usr/lib/voxtype/voxtype-onnx-avx2" + install -Dm755 "$srcdir/voxtype-$pkgver-onnx-avx512" "$pkgdir/usr/lib/voxtype/voxtype-onnx-avx512" # GPU-using ONNX binaries each live in their own subdirectory alongside # the EP companion shared libs they dlopen at runtime. ort 2.0.0-rc.12's @@ -248,7 +284,8 @@ package() { # voxtype-onnx-rocm name. The AMD GPU EP changed from ROCm to MIGraphX # in v0.7.0; ship one release with both names to soften the transition. # Drop in v0.8.0. - ln -sf "voxtype-onnx-migraphx" "$pkgdir/usr/lib/voxtype/voxtype-onnx-rocm" + ln -sf "voxtype-onnx-migraphx" "$pkgdir/usr/lib/voxtype/voxtype-onnx-rocm" + fi # /usr/bin/voxtype symlink and the unversioned voxtype-onnx-cuda symlink # are created by the .install script's post_install/post_upgrade hooks From 7397d1fbfd27c60873383ab6da12e53cd2f64cbe Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 5 Sep 2026 02:28:08 -0400 Subject: [PATCH 32/63] Install Git for upstream syncs --- .github/workflows/sync-upstream.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 0083ec39..c19c9ae3 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -39,7 +39,7 @@ jobs: archlinux:base-devel bash -lc ' set -euo pipefail - pacman -Syu --noconfirm jq + pacman -Syu --noconfirm git jq groupadd -g "$HOST_GID" runner useradd -m -u "$HOST_UID" -g "$HOST_GID" runner diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4b78b90..9fc270f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: -w /workspace \ archlinux:base-devel bash -lc ' set -euo pipefail - pacman -Syu --noconfirm jq + pacman -Syu --noconfirm git jq ./bin/sync-upstream self-test ./bin/sync-rebuilds --self-test ./bin/omarchy-pkgs self-test From 5d656b0b8a6475735ed9c32cb53f63b66243d072 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 5 Sep 2026 16:07:29 +0200 Subject: [PATCH 33/63] Add original vi package (#314) --- pkgbuilds/vi/.omarchy/package.json | 4 + pkgbuilds/vi/LICENSE | 121 ++++++++++++++++++ pkgbuilds/vi/PKGBUILD | 66 ++++++++++ .../vi/fix-tubesize-short-overflow.patch | 12 ++ pkgbuilds/vi/format-security.patch | 26 ++++ ...CFFCA918CF3AF47147588051E8B148A9999C34.asc | 52 ++++++++ pkgbuilds/vi/linenum.patch | 61 +++++++++ pkgbuilds/vi/navkeys.patch | 55 ++++++++ pkgbuilds/vi/preserve-dir.patch | 99 ++++++++++++++ 9 files changed, 496 insertions(+) create mode 100644 pkgbuilds/vi/.omarchy/package.json create mode 100644 pkgbuilds/vi/LICENSE create mode 100644 pkgbuilds/vi/PKGBUILD create mode 100644 pkgbuilds/vi/fix-tubesize-short-overflow.patch create mode 100644 pkgbuilds/vi/format-security.patch create mode 100644 pkgbuilds/vi/keys/pgp/86CFFCA918CF3AF47147588051E8B148A9999C34.asc create mode 100644 pkgbuilds/vi/linenum.patch create mode 100644 pkgbuilds/vi/navkeys.patch create mode 100644 pkgbuilds/vi/preserve-dir.patch diff --git a/pkgbuilds/vi/.omarchy/package.json b/pkgbuilds/vi/.omarchy/package.json new file mode 100644 index 00000000..a07823fd --- /dev/null +++ b/pkgbuilds/vi/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "aur", + "upstream_commit": "9aa84b0998874261e94bbbe25e0dcf3de7286b94" +} diff --git a/pkgbuilds/vi/LICENSE b/pkgbuilds/vi/LICENSE new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/pkgbuilds/vi/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/pkgbuilds/vi/PKGBUILD b/pkgbuilds/vi/PKGBUILD new file mode 100644 index 00000000..4483e826 --- /dev/null +++ b/pkgbuilds/vi/PKGBUILD @@ -0,0 +1,66 @@ +# Maintainer: libele +# Contributor: Evangelos Foutras +# Contributor: Eric Bélanger + +pkgname=vi +pkgver=070224 +pkgrel=9 +epoch=1 +pkgdesc="The original ex/vi text editor" +arch=('aarch64' 'arm' 'armv6h' 'armv7h' 'i486' 'i686' 'pentium4' 'x86_64') +url="https://ex-vi.sourceforge.net/" +license=('BSD-4-Clause-UC AND Caldera-no-preamble') +depends=('ncurses') +optdepends=('s-nail: used by the preserve command for notification') +provides=('vi') +conflicts=('ex-vi-compat' 'heirloom-ex-vi') +source=(https://sources.archlinux.org/other/$pkgname/ex-$pkgver.tar.xz{,.sig} + fix-tubesize-short-overflow.patch + navkeys.patch + format-security.patch + linenum.patch + preserve-dir.patch) +sha256sums=('c3e52dd44edd1f6bf0b52207f717c56149dc50aac0d131fff3851d589727f52f' + 'SKIP' + '42167fabebe30a13d594346b1d254db82090ba41742a9f35b8895d37092053f0' + '0e9e2d381f1d8cb86daae68462b3849825b003c08007725c0db9939d3d5bf58d' + 'ec50373ab48a9b327c5d02b10406bc3d04310d692de6c17098a59b1afeb08e03' + '4539f46cd839475c168a9c463f041354463e90e810b973a10c499c2727b008e7' + 'f0ef9ed2ccea98bf47b08dd3faa2abb911b4e6c0579f5294e9d0e8742282ff65') +validpgpkeys=('86CFFCA918CF3AF47147588051E8B148A9999C34') # Evangelos Foutras + +prepare() { + # Extract specific license files. + sed -n '1,36p' ex-$pkgver/LICENSE > BSD-4-Clause-UC.txt + sed -n '39,69p' ex-$pkgver/LICENSE > Caldera-no-preamble.txt + + cd ex-$pkgver + + patch -Np1 -i ../fix-tubesize-short-overflow.patch + patch -Np1 -i ../navkeys.patch + + # https://bugs.archlinux.org/task/76121 + patch -Np1 -i ../format-security.patch + + # https://bugs.archlinux.org/task/70770 + patch -Np1 -i ../linenum.patch + + # https://bugs.archlinux.org/task/20653 + patch -Np1 -i ../preserve-dir.patch +} + +build() { + cd ex-$pkgver + + export CFLAGS=-std=gnu90 + make PREFIX=/usr LIBEXECDIR=/usr/lib/ex PRESERVEDIR=/var/lib/ex \ + TERMLIB=ncurses FEATURES="-DCHDIR -DFASTTAG -DUCVISUAL -DMB -DBIT8" +} + +package() { + cd ex-$pkgver + + make PREFIX=/usr LIBEXECDIR=/usr/lib/ex PRESERVEDIR=/var/lib/ex \ + INSTALL=/usr/bin/install DESTDIR="$pkgdir" install + install -vDm 644 ../*.txt -t "$pkgdir/usr/share/licenses/$pkgname/" +} diff --git a/pkgbuilds/vi/fix-tubesize-short-overflow.patch b/pkgbuilds/vi/fix-tubesize-short-overflow.patch new file mode 100644 index 00000000..46cc2af2 --- /dev/null +++ b/pkgbuilds/vi/fix-tubesize-short-overflow.patch @@ -0,0 +1,12 @@ +diff -upr ex-070224.orig/ex_vis.h ex-070224/ex_vis.h +--- ex-070224.orig/ex_vis.h 2005-08-06 14:41:15.000000000 +0300 ++++ ex-070224/ex_vis.h 2015-05-02 20:41:13.938665281 +0300 +@@ -104,7 +104,7 @@ var enum { + */ + var short TUBELINES; + var short TUBECOLS; +-var short TUBESIZE; ++var int TUBESIZE; + + /* + * The screen in visual and crtopen is of varying size; the basic diff --git a/pkgbuilds/vi/format-security.patch b/pkgbuilds/vi/format-security.patch new file mode 100644 index 00000000..3676bae1 --- /dev/null +++ b/pkgbuilds/vi/format-security.patch @@ -0,0 +1,26 @@ +diff --git a/ex_unix.c b/ex_unix.c +index 4ad17f9..80815d1 100644 +--- a/ex_unix.c ++++ b/ex_unix.c +@@ -179,7 +179,7 @@ uexp: + if (warn && hush == 0 && chng && xchng != chng && value(WARN) && dol > zero) { + xchng = chng; + vnfl(); +- printf(mesg(catgets(catd, 1, 197, ++ printf("%s", mesg(catgets(catd, 1, 197, + "[No write]|[No write since last change]"))); + noonl(); + flush(); +diff --git a/ex_vops2.c b/ex_vops2.c +index 48050ff..e59727a 100644 +--- a/ex_vops2.c ++++ b/ex_vops2.c +@@ -211,7 +211,7 @@ showmode(int mode) + if (value(TERSE)) + putchar(str[0]); + else +- printf(&str[1]); ++ printf("%s", &str[1]); + vgoto(sdl, sdc); + cursor = ocurs; + splitw = 0; diff --git a/pkgbuilds/vi/keys/pgp/86CFFCA918CF3AF47147588051E8B148A9999C34.asc b/pkgbuilds/vi/keys/pgp/86CFFCA918CF3AF47147588051E8B148A9999C34.asc new file mode 100644 index 00000000..22af2c00 --- /dev/null +++ b/pkgbuilds/vi/keys/pgp/86CFFCA918CF3AF47147588051E8B148A9999C34.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBEzdbBcBCAC8QJ+5x2TjAlM8lL+6SKlj2BV3SS5YcLN3zL27ax8Li98VTn3G +lacdavKCQKUNbeHxSNKILuzMnx74kL5LOl9pSaDB6XgLMGu4RGhFFzKv3wO9cuBN +sxfHQFdXdJ4hY/Kiji0zH6XPyl8BGiUxXqjth+hoo0siJOiME2t/SL0U9KRnJyPt +fOngeUlKIcnBr+/0wboTgQpO3fa502CNV98mRVCY6/3gJY2kJP8G5rNWa6wywRtr +N94SiD0NL7R1ozTqIy4/K/7dB7nJOnQQxxmElal7bFr7k69L0cuc3XKkyhTrAnzM +bMp2vcXbgpcSoi/zLNgqDo0wq1CbmZxfgmbNABEBAAG0J0V2YW5nZWxvcyBGb3V0 +cmFzIDxmb3V0cmVsaXNAZ21haWwuY29tPokBkgQTAQgAhgWCYslmrAQLCQgHCRBR +6LFIqZmcNEcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmdG +FjAlcBwlLXsuKdWKZbbHWcAUXvnvjaY6ZeOCCVu9hwMVCAoEFgIDAQIXgAIbAwIe +ARYhBIbP/KkYzzr0cUdYgFHosUipmZw0AACV5wf9EmIB+G4SXx2OLgEtL3ohZfhk +/wKwh6AaOT94SWhAllGtLNtCCSUgaHgz05oVdLkGrYBBcgkvcr6K7TH2T7v7tuop +VeX+qNI+EKYkL+fPdpgb6wAqLeLiULZa0XM1kxfp1xFNFPIfXcjuhbRYbkzrj/MH +0P1XZYoBDO3yo+cWJt7z3fFw06tA9KZxn/iOKGblVPMZS2UGlA8UCIj01NRZWeNY +TD9JtcEPiRHt6Xnodfpvm5Tdp86Uo6BXeIjmpZXYPfR7NXcmwLFQMjOqb7roqd8+ +66pzMDJ/onYO+DZZs3EEcIDL0LykoK64cts6vwPj8FzdM4Z2nOn/wT7QRdq6lrQr +RXZhbmdlbG9zIEZvdXRyYXMgPGV2YW5nZWxvc0Bmb3V0cmVsaXMuY29tPokBlQQT +AQgAiQWCYslmqwQLCQgHCRBR6LFIqZmcNEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u +cy5zZXF1b2lhLXBncC5vcmfCF18eejk+B+7x5ZO1TGdelYn1jDioOzy+sqxgQCyR +WAMVCAoEFgIDAQIXgAIZAQIbAwIeARYhBIbP/KkYzzr0cUdYgFHosUipmZw0AABl +rQf+LMOENiR6NLyG5b7ut+d3ATilp7ECuwLE/9vQDaDQa5nkvQixsjomHklpdA5Y +UPer6J6WKf88iAU21H7qW9gqw4K5OjVXHZYI+RLaS+1qEz7Y5FFN/Axdu2GuvZvp +9B0frJ7zPVtM7+TbIkXqjYMiFbw24e5s3+y1CO3yEKc4dbHKQb493Axx/xCMtGzi +9rrebFuIQVJw+IaguGAk8tV/Jf/jsA7GjjrHhWHCcnY8HqCnPtWgUqxW15Yql5VS +pGcOAQMx/xeMHWqzSpE0NXTvBzuA81l4ovngBfy3Vjia7z/n/XxxLV3ehO8d6gir +yuEEdkkBPG48ysS9NxqxpWjCnLQrRXZhbmdlbG9zIEZvdXRyYXMgPGZvdXRyZWxp +c0BhcmNobGludXgub3JnPokBkgQTAQgAhgWCYslmrAQLCQgHCRBR6LFIqZmcNEcU +AAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmd4ABJkF/uaJLUS +de0Gt8Tr94eHgbzWjcuTeOp+7VARHQMVCAoEFgIDAQIXgAIbAwIeARYhBIbP/KkY +zzr0cUdYgFHosUipmZw0AABvDwf+NxwZ805EPlYZ53+rvvTLE1SqT8b7EaqVflff +HRoJoT+2XajrpPeT2cN0ePjoRtZgYEhj3qPCIz3R4uaGu7uWkX4NmqfJGDsmOAdN +XSibWKcz0cnbqwUmFsS6QmfcsAZxCD+LYt6C7KYhzGT0xvhP2OVHVOFDIj/I+iwH +MK41swTiQ101OtKPuqW1uLXzYDfbdTOTQ6RrOgtWlLtF94mF7sGNPnyKpTksgdw0 +4vnxbvQWSPuAsGwXN8hQk1Zy6I35Z+/qeXqh6AfPasUb0i/8PO5gZkcqW/pccOpy +7kFIKkjvAIKMl/ILR7Epd0LJlJHLctB+g06eGW3jYoH2E+c1x7kBDQRM3WwXAQgA +niGcmhZ5gty8/k0Nh5ov7MAnwcUtPQ7rXDb7iRsNyHkbx/o5FBDlU/zFdqE/drqs +oRYxI/4VqnDXBJPr9Pk7d2f4iUdO/pSa0XzY+q8vOtWDky67K5Bl2YkdX9F7audf +uggM75IJo2y1EF2F4D0pcWiDjT9kkjNpVBBCuKqcM4yWaf4pmYHihgC4tvBhJx8f +6YrnL8N/wCPpvqyvYJaL0kg+hcnVgYyOnbFcrlkeuGMDrF6aoKaN6Sl54dtnxkep +4/dCyW5A2w/V6ubaK5XRsN1yeY1bLvz4iRAy0CbtAvZuUsb+DEVDQ40aPvTw52Um +6hfCN8bBOTQs8t4sGM12DwARAQABiQF+BBgBCAByBYJiyWasCRBR6LFIqZmcNEcU +AAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmc3jDQWZ2z5IqgI +XRUrrTn72ljmV28aDWVAt3ozjarlLAIbDBYhBIbP/KkYzzr0cUdYgFHosUipmZw0 +AACo2AgAktLNEDb6geHvgsWWw0vklq4UxTIedKP7iQfg20v/fHrtY22V2SKE67Sb +bl8xezEr0NfHF/M8r2zUpHdI+r7HE2QrgE2NEBeWGoY54vyaN5wYxTJxJmUsOrQj +ujJqwoBcj2DIiTprNROzdjWuPY7JR/Vw80TrfAwFwcikAb6QJvc3i6oqant06Um6 +ycmH1zSFX1SMte/gXd0RJ8gyQmbgpo12HDof92/13ZRrdUG8Rk33fHAK2hN9pr+l +f29yxIMXFh5z9mwEkLCd7bz85k+VNOC/pcURJhXjH3n+ssK0pEHZ684OctKn6At8 +CxByRzRqssZbq46oABvbsGeZq1Qw7A== +=UALx +-----END PGP PUBLIC KEY BLOCK----- diff --git a/pkgbuilds/vi/linenum.patch b/pkgbuilds/vi/linenum.patch new file mode 100644 index 00000000..6821b3b3 --- /dev/null +++ b/pkgbuilds/vi/linenum.patch @@ -0,0 +1,61 @@ +From 891c4022e24063608b554b6b3e4b5e2533e5443b Mon Sep 17 00:00:00 2001 +From: n-t-roff +Date: Fri, 22 Apr 2016 19:08:05 +0200 +Subject: [PATCH] set nu bug fixed + +--- + ex_put.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ex_put.c b/ex_put.c +index fb7446e..0760747 100644 +--- a/ex_put.c ++++ b/ex_put.c +@@ -298,7 +298,7 @@ normline(int unused, int max) + + (void)unused; + if (max > 0) +- vcntcol = 0; ++ vcntcol = value(NUMBER) << 3; + if (shudclob) + slobber(linebuf[0]); + /* pdp-11 doprnt is not reentrant so can't use "printf" here + +From ad7f1e567324494021135bd2ec680b656a3f1cab Mon Sep 17 00:00:00 2001 +From: n-t-roff +Date: Wed, 27 Jul 2016 18:58:47 +0200 +Subject: [PATCH] Number mode command line bugfix + +The prev. number mode bugfix did introduce a bug in the command line +cursor position when using backspace in number mode. Should be +fixed now. +--- + ex_put.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/ex_put.c b/ex_put.c +index 26236d4..09ad403 100644 +--- a/ex_put.c ++++ b/ex_put.c +@@ -277,9 +277,12 @@ int + numbline(int i, int max) + { + ++ extern short vcntcol; + if (shudclob) + slobber(' '); + max -= printf("%6d ", i); ++ if (max > 0) ++ vcntcol = value(NUMBER) << 3; + return normline(0, max); + } + +@@ -297,8 +300,6 @@ normline(int unused, int max) + int c, n; + int ret = 0; + +- if (max > 0) +- vcntcol = value(NUMBER) << 3; + if (shudclob) + slobber(linebuf[0]); + /* pdp-11 doprnt is not reentrant so can't use "printf" here diff --git a/pkgbuilds/vi/navkeys.patch b/pkgbuilds/vi/navkeys.patch new file mode 100644 index 00000000..46924379 --- /dev/null +++ b/pkgbuilds/vi/navkeys.patch @@ -0,0 +1,55 @@ +diff -ru ex-050325/ex_tty.c ex-050325-du/ex_tty.c +--- ex-050325/ex_tty.c 2005-03-04 13:42:58.000000000 +0100 ++++ ex-050325-du/ex_tty.c 2009-06-25 20:06:57.000000000 +0200 +@@ -132,7 +132,8 @@ + &ED, &EI, &F0, &F1, &F2, &F3, &F4, &F5, &F6, &F7, &F8, &F9, + &HO, &IC, &IM, &IP, &KD, &KE, &KH, &KL, &KR, &KS, &KU, &LL, &ND, &xNL, + &xPC, &RC, &SC, &SE, &SF, &SO, &SR, &TA, &TE, &TI, &UP, &VB, &VS, &VE, +- &AL_PARM, &DL_PARM, &UP_PARM, &DOWN_PARM, &LEFT_PARM, &RIGHT_PARM ++ &AL_PARM, &DL_PARM, &UP_PARM, &DOWN_PARM, &LEFT_PARM, &RIGHT_PARM, ++ &kI, &kD, &kh, &at7, &kP, &kN + }; + bool *sflags[] = { + &AM, &BS, &DA, &DB, &EO, &HC, +@@ -170,7 +171,12 @@ + addmac1(KD, "j", "down", arrows, 1); + addmac1(KL, "h", "left", arrows, 1); + addmac1(KR, "l", "right", arrows, 1); +- addmac1(KH, "H", "home", arrows, 1); ++ addmac1(kI, "i", "insert", arrows, 1); ++ addmac1(kD, "x", "delete", arrows, 1); ++ addmac1(kh, "^", "home", arrows, 1); ++ addmac1(at7, "$", "end", arrows, 1); ++ addmac1(kP, "", "pgup", arrows, 1); ++ addmac1(kN, "", "pgdn", arrows, 1); + + /* + * Handle funny termcap capabilities +@@ -341,7 +347,7 @@ + *(*fp++) = flag; + namp += 2; + } while (*namp); +- namp = "albcbtcdceclcmcrcsdcdldmdoedeik0k1k2k3k4k5k6k7k8k9hoicimipkdkekhklkrkskullndnlpcrcscsesfsosrtatetiupvbvsveALDLUPDOLERI"; ++ namp = "albcbtcdceclcmcrcsdcdldmdoedeik0k1k2k3k4k5k6k7k8k9hoicimipkdkekhklkrkskullndnlpcrcscsesfsosrtatetiupvbvsveALDLUPDOLERIkIkDkh@7kPkN"; + sp = sstrs; + do { + string = tgetstr(namp, &aoftspace); +diff -ru ex-050325/ex_tty.h ex-050325-du/ex_tty.h +--- ex-050325/ex_tty.h 2004-12-01 19:21:06.000000000 +0100 ++++ ex-050325-du/ex_tty.h 2009-06-25 20:06:05.000000000 +0200 +@@ -183,6 +183,15 @@ + var bool XX; /* Tektronix 4025 insert line */ + /* X? is reserved for severely nauseous glitches */ + /* If there are enough of these we may need bit masks! */ ++/* ++ * Insert, Delete, Home, End, PgUp an PgDown keys ++ */ ++var char *kI; ++var char *kD; ++var char *kh; ++var char *at7; ++var char *kP; ++var char *kN; + + /* + * From the tty modes... diff --git a/pkgbuilds/vi/preserve-dir.patch b/pkgbuilds/vi/preserve-dir.patch new file mode 100644 index 00000000..ddfe8783 --- /dev/null +++ b/pkgbuilds/vi/preserve-dir.patch @@ -0,0 +1,99 @@ +diff -Naur ex-050325.ori/ex.1 ex-050325/ex.1 +--- ex-050325.ori/ex.1 2005-02-17 07:39:36.000000000 -0500 ++++ ex-050325/ex.1 2011-02-01 16:21:51.496666674 -0500 +@@ -1950,7 +1950,7 @@ + .B /var/tmp/Rx\fInnnnnnnnnn\fP + named buffer temporary + .TP +-.B /var/preserve ++.B /var/lib/ex + preservation directory + .SH DOCUMENTATION + The document +diff -Naur ex-050325.ori/expreserve.c ex-050325/expreserve.c +--- ex-050325.ori/expreserve.c 2005-03-04 07:44:46.000000000 -0500 ++++ ex-050325/expreserve.c 2011-02-01 16:22:06.583333342 -0500 +@@ -272,7 +272,7 @@ + #ifdef notdef + char pattern[] = "/usr/preserve/Exaa`XXXXX"; + #else +-char pattern[] = "/var/preserve/Exa`XXXXXXXXXX"; ++char pattern[] = "/var/lib/ex/Exa`XXXXXXXXXX"; + #endif + + /* +@@ -296,7 +296,7 @@ + timestamp = ctime(&time); + timestamp[16] = 0; /* blast from seconds on */ + putenv("MAILRC=/dev/null"); +- sprintf(cmd, "/bin/mail %s", pp->pw_name); ++ sprintf(cmd, "/usr/bin/mail %s", pp->pw_name); + setuid(getuid()); + mf = popen(cmd, "w"); + if (mf == NULL) +diff -Naur ex-050325.ori/exrecover.c ex-050325/exrecover.c +--- ex-050325.ori/exrecover.c 2005-03-04 07:44:46.000000000 -0500 ++++ ex-050325/exrecover.c 2011-02-01 16:21:51.496666674 -0500 +@@ -167,7 +167,7 @@ + #ifdef notdef + char mydir[] = "/usr/preserve"; + #else +-char mydir[] = "/var/preserve"; ++char mydir[] = "/var/lib/ex"; + #endif + + /* +diff -Naur ex-050325.ori/Makefile ex-050325/Makefile +--- ex-050325.ori/Makefile 2005-03-24 18:50:09.000000000 -0500 ++++ ex-050325/Makefile 2011-02-01 16:21:51.496666674 -0500 +@@ -83,7 +83,7 @@ + BINDIR = $(PREFIX)/bin + LIBEXECDIR = $(PREFIX)/libexec + MANDIR = $(PREFIX)/share/man +-PRESERVEDIR = /var/preserve ++PRESERVEDIR = /var/lib/ex + + # + # DESTDIR is prepended to the installation paths. It is mostly useful +diff -Naur ex-050325.ori/vi.1 ex-050325/vi.1 +--- ex-050325.ori/vi.1 2005-02-17 07:39:36.000000000 -0500 ++++ ex-050325/vi.1 2011-02-01 16:21:51.496666674 -0500 +@@ -979,7 +979,7 @@ + .B /var/tmp/Rx\fInnnnnnnnnn\fP + named buffer temporary + .TP +-.B /var/preserve ++.B /var/lib/ex + preservation directory + .SH SEE ALSO + ex(1), +--- ex-050325.original/ex.1 2012-01-27 20:43:00.602119283 +0100 ++++ ex-050325/ex.1 2012-01-27 20:47:24.535436516 +0100 +@@ -1932,10 +1932,10 @@ + Determines the terminal type. + .SH FILES + .TP +-.B /usr/libexec/expreserve ++.B /usr/lib/ex/expreserve + preserve command + .TP +-.B /usr/libexec/exrecover ++.B /usr/lib/ex/exrecover + recover command + .TP + .B /etc/termcap +--- ex-050325.original/vi.1 2012-01-27 20:43:00.602119283 +0100 ++++ ex-050325/vi.1 2012-01-27 20:46:33.815439611 +0100 +@@ -961,10 +961,10 @@ + Determines the terminal type. + .SH FILES + .TP +-.B /usr/libexec/expreserve ++.B /usr/lib/ex/expreserve + preserve command + .TP +-.B /usr/libexec/exrecover ++.B /usr/lib/ex/exrecover + recover command + .TP + .B /etc/termcap From 3682eaf7184e9ed1858735eb3dd4835fb603b93c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 5 Sep 2026 14:20:51 -0400 Subject: [PATCH 34/63] Add omareel, a screen recorder and editor for Omarchy --- pkgbuilds/omareel/.omarchy/package.json | 6 +++ pkgbuilds/omareel/PKGBUILD | 61 +++++++++++++++++++++++++ pkgbuilds/omareel/omareel.install | 13 ++++++ 3 files changed, 80 insertions(+) create mode 100644 pkgbuilds/omareel/.omarchy/package.json create mode 100644 pkgbuilds/omareel/PKGBUILD create mode 100644 pkgbuilds/omareel/omareel.install diff --git a/pkgbuilds/omareel/.omarchy/package.json b/pkgbuilds/omareel/.omarchy/package.json new file mode 100644 index 00000000..fed23784 --- /dev/null +++ b/pkgbuilds/omareel/.omarchy/package.json @@ -0,0 +1,6 @@ +{ + "source": "local", + "rebuild_on": [ + "hyprland" + ] +} diff --git a/pkgbuilds/omareel/PKGBUILD b/pkgbuilds/omareel/PKGBUILD new file mode 100644 index 00000000..651de0a3 --- /dev/null +++ b/pkgbuilds/omareel/PKGBUILD @@ -0,0 +1,61 @@ +# Maintainer: Ryan + +pkgname=omareel +pkgver=0.1.0 +pkgrel=1 +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' +license=('MIT') +install='omareel.install' +options=('!debug') +depends=( + 'ffmpeg' + 'gpu-screen-recorder' + 'hicolor-icon-theme' + 'hyprland' + 'layer-shell-qt' + 'libevdev' + 'libjpeg-turbo' + 'qt6-base' + 'qt6-declarative' + 'qt6-multimedia' + 'qt6-svg' + 'slurp' + 'xdg-desktop-portal' +) +makedepends=( + 'cmake' + 'ninja' + 'pkgconf' + 'qt6-shadertools' + 'wayland' +) +optdepends=('gifsicle: optimize exported GIF files') +conflicts=('omarecord') +source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") +sha256sums=('SKIP') + +build() { + cd "$srcdir/$pkgname-$pkgver" + ./bin/build +} + +package() { + cd "$srcdir/$pkgname-$pkgver" + install -Dm755 build/omareel "$pkgdir/usr/bin/omareel" + ln -s omareel "$pkgdir/usr/bin/omarecord" + # The capture-exclusion plugin is bound to the Hyprland commit it was built + # against; rebuild_on hyprland keeps this package in step with the compositor. + install -Dm755 build/plugin/omareel-capture-exclude.so "$pkgdir/usr/lib/omareel/omareel-capture-exclude.so" + install -Dm644 build/plugin/omareel-capture-exclude.so.hash "$pkgdir/usr/lib/omareel/omareel-capture-exclude.so.hash" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 LICENSES/lucide.txt "$pkgdir/usr/share/licenses/$pkgname/lucide.txt" + install -Dm644 pkg/omareel.svg "$pkgdir/usr/share/icons/hicolor/scalable/apps/omareel.svg" + local size + for size in 16 32 48 64 128 256 512; do + install -Dm644 "pkg/icons/omareel-$size.png" \ + "$pkgdir/usr/share/icons/hicolor/${size}x${size}/apps/omareel.png" + done + install -Dm644 pkg/omareel.desktop "$pkgdir/usr/share/applications/omareel.desktop" +} diff --git a/pkgbuilds/omareel/omareel.install b/pkgbuilds/omareel/omareel.install new file mode 100644 index 00000000..3fa0c2a1 --- /dev/null +++ b/pkgbuilds/omareel/omareel.install @@ -0,0 +1,13 @@ +post_install() { + command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q + command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -t -f usr/share/icons/hicolor + return 0 +} + +post_upgrade() { + post_install +} + +post_remove() { + post_install +} From 2944f5e802c5daa4753e02aa62a5f8acc95bef9d Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 5 Sep 2026 14:47:12 -0400 Subject: [PATCH 35/63] omareel: pin the v0.1.0 archive checksum --- pkgbuilds/omareel/PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgbuilds/omareel/PKGBUILD b/pkgbuilds/omareel/PKGBUILD index 651de0a3..e43106e0 100644 --- a/pkgbuilds/omareel/PKGBUILD +++ b/pkgbuilds/omareel/PKGBUILD @@ -34,7 +34,7 @@ makedepends=( optdepends=('gifsicle: optimize exported GIF files') conflicts=('omarecord') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('SKIP') +sha256sums=('330d8e2805135af33bf35378e2e961094fc375075d38120c8270bd42fe04ce5b') build() { cd "$srcdir/$pkgname-$pkgver" From d57a9955d5c872e77100a6102c8c43e2fa6c800c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 5 Sep 2026 16:59:22 -0400 Subject: [PATCH 36/63] 1password: map the tarball architecture inside package() so the PKGBUILD's version can be read without CARCH The file-scope case on CARCH ended in 'return 1', which aborted any 'source PKGBUILD' that did not export CARCH before pkgver and pkgrel were assigned. check-versions reads PKGBUILDs that way, saw an empty version, queued 8.12.34-35 on every tick, and promotion then refused to overwrite the already-published artifact. Every channel has been in backoff since 06:41 UTC today because of it. --- pkgbuilds/1password/PKGBUILD | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pkgbuilds/1password/PKGBUILD b/pkgbuilds/1password/PKGBUILD index 4e91196d..bfb2dae7 100644 --- a/pkgbuilds/1password/PKGBUILD +++ b/pkgbuilds/1password/PKGBUILD @@ -1,11 +1,4 @@ pkgname=1password - -case "$CARCH" in - x86_64) _tararch=x64 ;; - aarch64) _tararch=arm64 ;; - *) return 1 ;; -esac - pkgver=8.12.34 pkgrel=35 conflicts=('1password-beta' '1password-beta-bin') @@ -34,6 +27,17 @@ validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22') package() { depends=('hicolor-icon-theme' 'libgtk-3.so=0' 'nss' 'xdg-utils') + # 1Password names its tarballs by a vendor architecture suffix. arch=() + # above already limits which architectures makepkg builds, so no fallback + # branch is needed here; keeping this inside package() means sourcing the + # PKGBUILD without CARCH (as the version check does) still reads the + # version correctly. + local _tararch + case "$CARCH" in + x86_64) _tararch=x64 ;; + aarch64) _tararch=arm64 ;; + esac + # Go to source directory cd "1password-${pkgver}.${_tararch}" From d673d67a1cf240e9f2b26f4fe35c3382f5c8005c Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 5 Sep 2026 16:59:22 -0400 Subject: [PATCH 37/63] Read PKGBUILD variables with CARCH set and refuse to queue a package whose version cannot be read makepkg always exports CARCH, so PKGBUILDs may branch on it at file scope. Every place the tooling sourced a PKGBUILD did so without CARCH, taking the wrong branch or aborting partway, and check-versions turned the resulting empty pkgver/pkgrel into the version '-', which never matches a published version and queues an endless rebuild. package_pkgbuild_var reads one variable the way makepkg would see it, for the architecture being checked, and reports whether the source succeeded. check-versions, sync-rebuilds and omarchy-pkgs use it. check-versions now warns and skips a package whose pkgver or pkgrel is empty instead of comparing a partial version. A self-test covers a PKGBUILD that branches on CARCH before assigning its version. --- bin/check-versions | 31 +++++++++++++++++++++++-------- bin/omarchy-pkgs | 3 ++- bin/sync-rebuilds | 22 +++++++++++++++++++++- helpers/package-metadata.sh | 24 ++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/bin/check-versions b/bin/check-versions index 9849701a..925570f6 100755 --- a/bin/check-versions +++ b/bin/check-versions @@ -79,10 +79,26 @@ get_repo_version() { ' } +# The PKGBUILD's full version (epoch:pkgver-pkgrel) for the architecture being +# checked. Prints nothing when pkgver or pkgrel cannot be read: a partial +# version like "-" would compare unequal to everything published and queue a +# rebuild on every tick, so an unreadable PKGBUILD must not queue at all. get_pkgbuild_version() { local pkgdir="$1" - if [[ -f "$pkgdir/PKGBUILD" ]]; then - (cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi') + local epoch pkgver pkgrel + + [[ -f "$pkgdir/PKGBUILD" ]] || return 1 + + epoch=$(package_pkgbuild_var "$pkgdir" epoch "$ARCH") + pkgver=$(package_pkgbuild_var "$pkgdir" pkgver "$ARCH") + pkgrel=$(package_pkgbuild_var "$pkgdir" pkgrel "$ARCH") + + [[ -n "$pkgver" && -n "$pkgrel" ]] || return 1 + + if [[ -n "$epoch" ]]; then + echo "${epoch}:${pkgver}-${pkgrel}" + else + echo "${pkgver}-${pkgrel}" fi } @@ -97,8 +113,8 @@ check_vcs_unchanged() { # If epoch or pkgrel changed in PKGBUILD, release even if upstream is unchanged. local pkgbuild_epoch pkgbuild_pkgrel - pkgbuild_epoch=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${epoch:-}"') - pkgbuild_pkgrel=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgrel}"') + pkgbuild_epoch=$(package_pkgbuild_var "$pkgdir" epoch "$ARCH") + pkgbuild_pkgrel=$(package_pkgbuild_var "$pkgdir" pkgrel "$ARCH") local repo_pkgrel="${repo_version##*-}" local repo_no_pkgrel="${repo_version%-*}" @@ -126,12 +142,11 @@ check_package() { local mirror="$3" local pkgbuild_version repo_version - pkgbuild_version=$(get_pkgbuild_version "$pkgdir") - repo_version=$(get_repo_version "$pkg" "$mirror") - - if [[ -z "$pkgbuild_version" ]]; then + if ! pkgbuild_version=$(get_pkgbuild_version "$pkgdir") || [[ -z "$pkgbuild_version" ]]; then + print_warning "$pkg: could not read pkgver/pkgrel from its PKGBUILD for $ARCH — not queueing" return 1 fi + repo_version=$(get_repo_version "$pkg" "$mirror") if grep -qE '^pkgver[[:space:]]*\(\)' "$pkgdir/PKGBUILD"; then if [[ -n "$repo_version" && -n "$(package_extract_vcs_hash_from_version "$repo_version")" ]]; then diff --git a/bin/omarchy-pkgs b/bin/omarchy-pkgs index 0ca607b9..b35016fb 100755 --- a/bin/omarchy-pkgs +++ b/bin/omarchy-pkgs @@ -21,6 +21,7 @@ BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/host-helpers.sh" +source "$BUILD_ROOT/helpers/package-metadata.sh" UPSTREAM_URL="${OMARCHY_UPSTREAM_URL:-https://github.com/basecamp/omarchy.git}" EDGE_DB_URL="${OMARCHY_EDGE_DB_URL:-https://pkgs.omarchy.org/edge/$(reference_arch)/omarchy.db.tar.zst}" @@ -92,7 +93,7 @@ version_is_rc() { version_is_prerelease "$1"; } pkgbuild_var() { local pkg="$1" var="$2" - (cd "$BUILD_ROOT/pkgbuilds/$pkg" && bash -c "source PKGBUILD 2>/dev/null; echo \"\${$var}\"") + package_pkgbuild_var "$BUILD_ROOT/pkgbuilds/$pkg" "$var" } # Prints the published version of $pkg from the edge DB. Distinguishes diff --git a/bin/sync-rebuilds b/bin/sync-rebuilds index b9aa81d1..3009b898 100755 --- a/bin/sync-rebuilds +++ b/bin/sync-rebuilds @@ -237,7 +237,7 @@ pkgbuild_field() { local package_dir="$1" local field="$2" - (cd "$package_dir" && env -u OMARCHY_SRC bash -c "source PKGBUILD 2>/dev/null; echo \"\${$field:-}\"") + package_pkgbuild_var "$package_dir" "$field" } # 2 -> 3, and 1.1 -> 1.2. Anything else is a pkgrel this command has no business @@ -781,6 +781,26 @@ cmd_self_test() { unset SELFTEST_ARCHES check "pkgrel untouched" 1 "$(pkgrel_of "$root/pkgbuilds/t-x86")" + echo "A PKGBUILD that branches on CARCH at file scope still reads its version:" + root=$(selftest_root carch-branch) + mkdir -p "$root/pkgbuilds/t-carch/.omarchy" + cat > "$root/pkgbuilds/t-carch/PKGBUILD" <<'PKG' +pkgname=t-carch +case "$CARCH" in + x86_64) _suffix=x64 ;; + aarch64) _suffix=arm64 ;; + *) return 1 ;; +esac +pkgver=1.0 +pkgrel=3 +arch=(x86_64 aarch64) +PKG + echo '{"source":"local"}' > "$root/pkgbuilds/t-carch/.omarchy/package.json" + check "pkgver read for x86_64" "1.0" "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" pkgver x86_64)" + check "pkgrel read for x86_64" "3" "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" pkgrel x86_64)" + check "arch-specific value follows CARCH" "arm64" "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" _suffix aarch64)" + check "unsupported arch reports failure" 1 "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" pkgver armv7h >/dev/null; echo $?)" + echo "" if [[ "$failures" -eq 0 ]]; then print_success "Self-test passed" diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index 043e2fc5..564cd2d6 100644 --- a/helpers/package-metadata.sh +++ b/helpers/package-metadata.sh @@ -144,6 +144,30 @@ package_has_pkgbuild() { [[ -f "$pkgdir/PKGBUILD" ]] } +# Read one variable from a PKGBUILD the way makepkg would see it. +# +# makepkg always exports CARCH, so PKGBUILDs may branch on it at file scope +# (per-architecture sources, tarball suffixes, even `return` for an +# unsupported architecture). Sourcing without CARCH takes the wrong branch or +# aborts partway, which leaves pkgver and pkgrel empty — and an empty version +# never equals the published one, so the package is queued for a rebuild that +# promotion then refuses. Every read of a PKGBUILD goes through here. +# +# Prints the value; exit status is that of `source PKGBUILD` itself, so a +# caller can tell "variable empty" from "PKGBUILD could not be read". +package_pkgbuild_var() { + local pkgdir="$1" + local var="$2" + local arch="${3:-${ARCH:-x86_64}}" + + (cd "$pkgdir" && env -u OMARCHY_SRC CARCH="$arch" bash -c ' + source PKGBUILD >/dev/null 2>&1 + rc=$? + printf "%s\n" "${!1:-}" + exit "$rc" + ' _ "$var") +} + # The architectures declared by a PKGBUILD. Set CARCH while reading it so a # conditional arch=() assignment is evaluated for the architecture we are # actually checking, even when the repository host is a different one. From 341299f4774ceb4822018c62e5d7dae3686b48f9 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 01:56:23 -0500 Subject: [PATCH 38/63] Let the Hermes package bootstrap native desktop updates Install Hermes into its writable native layout instead of shipping a frozen /opt desktop. Preserve the native update path, migrate tagged bootstraps, and keep existing CLI launchers until the desktop is ready. Co-Authored-By: GPT-6 Codex (xhigh) --- .github/workflows/test.yml | 3 + pkgbuilds/hermes-desktop/PKGBUILD | 140 ++-------- .../hermes-desktop/hermes-desktop.desktop | 2 +- pkgbuilds/hermes-desktop/hermes-desktop.sh | 263 ++++++++++++++++-- test/hermes-desktop-test.py | 211 ++++++++++++++ 5 files changed, 478 insertions(+), 141 deletions(-) create mode 100644 test/hermes-desktop-test.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9fc270f3..9c359c5c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,9 @@ jobs: with: persist-credentials: false + - name: Test Hermes installation and launch + run: python3 test/hermes-desktop-test.py + # An Arch container for vercmp: version ordering has to be decided by # the same comparator pacman uses on users' machines. - name: Run self-tests diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 472c804c..f00eee30 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -1,135 +1,53 @@ # Maintainer: David Heinemeier Hansson -# Nous builds Hermes Desktop for macOS and Windows only -- their download page -# offers a .dmg and an .exe and tells Linux users to install from a terminal -- -# so there is no vendor binary to repackage. Their electron-builder config does -# carry a Linux target, though, and it works; this builds it. -# -# The app only runs against a Hermes runtime built from its own commit, so it -# provisions one itself under ~/.hermes on first launch and the package stays -# on the newest tag. Pairing it with the mise CLI instead was tried and does -# not work: PyPI trails the tags, and the version gap fails the readiness probe -# with a 401. Pinning back to the tag behind PyPI's release does not rescue it -# either -- v2026.7.20's desktop hangs after "backend is ready" without ever -# opening a window, against its own matching runtime. -# -# The app is only a shell: it runs `hermes serve` against a Hermes CLI it does -# not ship, and clones its own copy with the upstream install script when it -# finds none. /usr/bin/hermes-desktop heads that off. See hermes-desktop.sh. - pkgname=hermes-desktop -pkgver=2026.8.18 +pkgver=2026.8.31 pkgrel=2 -pkgdesc='Native desktop shell for Hermes Agent' +pkgdesc='Installer and launcher for the self-updating Hermes desktop' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' license=('MIT') +# Hermes builds its desktop under the user's runtime so both the app and CLI +# can update it. The compiler and Electron libraries must survive installation. depends=( - 'alsa-lib' - 'at-spi2-core' - 'cairo' - 'dbus' - 'expat' - 'curl' - 'gcc-libs' - 'gdk-pixbuf2' - 'git' - 'glib2' - 'glibc' - 'gtk3' - 'hicolor-icon-theme' - 'libcups' - 'libdrm' - 'libglvnd' - 'libnotify' - 'libsecret' - 'libx11' - 'libxcb' - 'libxcomposite' - 'libxdamage' - 'libxext' - 'libxfixes' - 'libxkbcommon' - 'libxrandr' - 'mesa' - 'nspr' - 'nss' - 'pango' - 'systemd-libs' - 'xdg-utils' + 'alsa-lib' 'at-spi2-core' 'bash' 'cairo' 'curl' 'dbus' 'expat' 'gcc' + 'gdk-pixbuf2' 'git' 'glib2' 'glibc' 'gtk3' 'hicolor-icon-theme' 'libcups' + 'libdrm' 'libglvnd' 'libnotify' 'libsecret' 'libx11' 'libxcb' 'libxcomposite' + 'libxdamage' 'libxext' 'libxfixes' 'libxkbcommon' 'libxrandr' 'make' 'mesa' + 'nodejs' 'npm' 'nspr' 'nss' 'pango' 'python' 'systemd-libs' 'util-linux' + 'xdg-terminal-exec' 'xdg-utils' ) +makedepends=('imagemagick') -optdepends=('omarchy: installs the Hermes CLI the app needs on first launch') - -# The build runs the repo's own npm workspace install, which fetches Electron -# and rebuilds node-pty against it. -makedepends=('git' 'imagemagick' 'nodejs' 'npm' 'python') - -# Electron bundles prebuilt binaries that stripping corrupts. -options=('!strip' '!debug') - -# The tag's commit. apps/desktop/scripts/write-build-stamp.mjs pins the app's -# first-launch bootstrap to a Hermes commit, and resolves it from $GITHUB_SHA -# before falling back to `git rev-parse`. That fallback is wrong here: makepkg -# builds inside this repository, so git ascends out of srcdir and stamps the -# app with an omarchy-pkgs commit that means nothing upstream. -_commit=e624e9fde561e1add9388384012b295fde669ade - -_srcdir="hermes-agent-${pkgver}" -source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz" +# Snapshot the bootstrap installer, including upstream's Linux sandbox fixes. +# Only installation uses it; updates belong to the native runtime on main. +_commit=245e48008fa814b3251f50755eb656bd9fb86cb1 +source=("install-${_commit}.sh::https://raw.githubusercontent.com/NousResearch/hermes-agent/${_commit}/scripts/install.sh" + "LICENSE-${_commit}::https://raw.githubusercontent.com/NousResearch/hermes-agent/${_commit}/LICENSE" 'hermes-desktop.sh' 'hermes-desktop.desktop' 'hermes-desktop.png') -sha256sums=('1e3d39d3638ec15fa9d31af262568a953e9272090deb1c50c44cd401175f5b80' - 'f5833b969ce451aadee9f08d92db55cce3c7c9213175080590bf37444854d676' - '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' +sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' + '821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6' + '5113ba692fe00b0f4806f799ce16b85a44bb2bd901ca17577ba64c887b1fb33d' + 'c37d4cfa4801eccbd769fddaebb2115d54faa49a5b7bc0f305f563e3cefd9bd8' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') -build() { - cd "${srcdir}/${_srcdir}" - - export GITHUB_SHA="${_commit}" - export GITHUB_REF_NAME="v${pkgver}" - - # The desktop workspace resolves against the repo root, so the install has to - # happen there rather than in apps/desktop. - npm ci - - cd apps/desktop - npm run pack -} - package() { - cd "${srcdir}/${_srcdir}/apps/desktop/release/linux-unpacked" - - install -dm755 "${pkgdir}/opt/${pkgname}" - cp -a . "${pkgdir}/opt/${pkgname}/" - + install -Dm644 "${srcdir}/install-${_commit}.sh" "${pkgdir}/usr/share/${pkgname}/install.sh" + install -Dm644 "${srcdir}/LICENSE-${_commit}" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" install -Dm755 "${srcdir}/hermes-desktop.sh" "${pkgdir}/usr/bin/${pkgname}" - install -Dm644 "${srcdir}/hermes-desktop.desktop" \ - "${pkgdir}/usr/share/applications/${pkgname}.desktop" - - install -Dm644 "${srcdir}/hermes-desktop.png" \ - "${pkgdir}/usr/share/icons/hicolor/1024x1024/apps/${pkgname}.png" + # Native registration uses hermes.desktop but omits URI handling. Keep the + # original handler ID hidden, and let the native entry replace the visible one. + install -Dm644 "${srcdir}/hermes-desktop.desktop" "${pkgdir}/usr/share/applications/hermes-desktop.desktop" + printf 'NoDisplay=true\n' >>"${pkgdir}/usr/share/applications/hermes-desktop.desktop" + sed '/^MimeType=/d' "${srcdir}/hermes-desktop.desktop" >"${pkgdir}/usr/share/applications/hermes.desktop" + install -Dm644 "${srcdir}/hermes-desktop.png" "${pkgdir}/usr/share/icons/hicolor/1024x1024/apps/${pkgname}.png" local size for size in 512 256 128 64 48; do magick "${srcdir}/hermes-desktop.png" -resize "${size}x${size}" "${srcdir}/icon-${size}.png" - install -Dm644 "${srcdir}/icon-${size}.png" \ - "${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/${pkgname}.png" + install -Dm644 "${srcdir}/icon-${size}.png" "${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/${pkgname}.png" done - - install -Dm644 "${srcdir}/${_srcdir}/LICENSE" \ - "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" - install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \ - "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt" - - # Chromium's setuid sandbox is only needed where unprivileged user namespaces - # are unavailable; where they work, setuid root is the worse of the two. - if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then - chmod 4755 "${pkgdir}/opt/${pkgname}/chrome-sandbox" - else - chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox" - fi } diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.desktop b/pkgbuilds/hermes-desktop/hermes-desktop.desktop index 8186b658..b156fcbb 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.desktop +++ b/pkgbuilds/hermes-desktop/hermes-desktop.desktop @@ -4,7 +4,7 @@ Type=Application Name=Hermes GenericName=AI Agent Comment=The self-improving AI agent that grows with you -Exec=hermes-desktop %U +Exec=/usr/bin/hermes-desktop %U Icon=hermes-desktop Terminal=false Categories=Development; diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 5c4d35ef..e8a17166 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -1,41 +1,246 @@ #!/bin/bash set -euo pipefail -# Hermes Desktop is a shell around a Hermes runtime, and it only works against -# one built from its own commit. A CLI from PyPI is always a different release -# -- PyPI trails the tags -- and the mismatch fails the app's readiness probe -# with 401 Unauthorized. So keep it away from whatever `hermes` is on PATH, -# which on Omarchy is the mise CLI installed for the terminal agent, and let -# the app provision and manage its own runtime under ~/.hermes. That is the -# arrangement upstream ships, and the only one that starts. -export HERMES_DESKTOP_IGNORE_EXISTING=1 +export HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" +HERMES_HOME=$(realpath -ms "$HERMES_HOME") +root="$HERMES_HOME/hermes-agent" +marker="$root/.omarchy-hermes-desktop" +installer=/usr/share/hermes-desktop/install.sh +cli=("$root/venv/bin/python" "$root/hermes") -# Chromium cannot reliably infer the Secret Service password-store backend -# from a Hyprland session, even when GNOME Keyring is already providing it. -# Keep an explicit user choice (such as KWallet), otherwise select the -# libsecret backend that this package depends on. -export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}" +die() { + echo "Hermes: $*" >&2 + exit 1 +} -# Reconcile every launch rather than trusting whatever installed us. A plain -# `pacman -S hermes-desktop`, or an install interrupted partway, leaves any -# Hermes the terminal agent had built still sitting there, and by then the -# menu entry that would have tidied it up is disabled because we are present. -if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then - omarchy-install-hermes-cli >/dev/null 2>&1 || true +desktop_executable() { + local executable + for executable in "$root/apps/desktop/release/linux-unpacked/"{Hermes,hermes}; do + if [[ -f $executable && -x $executable ]]; then + printf '%s\n' "$executable" + return 0 + fi + done + return 1 +} + +native_wrapper() { + local wrapper=$1 entry=$2 suffix=${3:-} + [[ -f $wrapper && ! -L $wrapper ]] && cmp -s "$wrapper" <( + printf '#!/usr/bin/env bash\nunset PYTHONPATH\nunset PYTHONHOME\nexec "%s/venv/bin/python" "%s/%s"%s "$@"\n' "$root" "$root" "$entry" "$suffix" + ) +} + +runtime_ready() { + [[ -f $root/.hermes-bootstrap-complete && -x ${cli[0]} ]] && + [[ -x $HOME/.local/bin/hermes ]] && + native_wrapper "$HOME/.local/bin/hermes" hermes && + desktop_executable >/dev/null && + timeout 15 env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" --version >/dev/null 2>&1 +} + +ready() { + if [[ -f $marker ]]; then + grep -qxF ready "$marker" || return 1 + fi + runtime_ready +} + +restore_launchers() { + local command wrapper entry + for command in hermes hermes-agent hermes-acp; do + [[ -f $install_backup/$command ]] || continue + wrapper="$HOME/.local/bin/$command" + entry=hermes + [[ $command == "hermes-agent" ]] && entry=run_agent.py + if [[ ! -e $wrapper && ! -L $wrapper ]] || native_wrapper "$wrapper" "$entry" || + { [[ -f $wrapper && ! -L $wrapper ]] && grep -qxF '# Written by omarchy-install-hermes-cli.' "$wrapper"; } || + { [[ $command == "hermes-acp" ]] && native_wrapper "$wrapper" hermes ' acp'; }; then + cp -p "$install_backup/$command" "$wrapper" + else + echo "Keeping changed $wrapper; its original is in $install_backup." >&2 + return + fi + done + rm -rf "$install_backup" +} + +stage() { + bash "$installer" --dir "$root" --branch main --non-interactive --stage "$1" +} + +install_desktop() { + (( EUID != 0 )) || die "Run this installer as your desktop user, without sudo." + mkdir -p "$HERMES_HOME" + exec 9>"$HERMES_HOME/.omarchy-hermes-desktop.lock" + flock -n 9 || die "Another Hermes installation is already running." + if ready; then + exec 9>&- + return + fi + + # Upstream replaces these launchers. Admit its exact shims or Omarchy's + # marked predecessor before starting work, never a user's custom command. + local command wrapper entry + for command in hermes hermes-agent hermes-acp; do + wrapper="$HOME/.local/bin/$command" + [[ -e $wrapper || -L $wrapper ]] || continue + entry=hermes + [[ $command == "hermes-agent" ]] && entry=run_agent.py + if native_wrapper "$wrapper" "$entry"; then continue; fi + if [[ $command == "hermes-acp" ]] && native_wrapper "$wrapper" hermes ' acp'; then continue; fi + if [[ $command == "hermes" && -f $wrapper && ! -L $wrapper ]] && + grep -qxF '# Written by omarchy-install-hermes-cli.' "$wrapper"; then continue; fi + die "Keeping the existing $wrapper. Move or update that installation before installing Hermes Desktop." + done + + # The native prerequisite stage may install Node's three user symlinks. + for command in node npm npx; do + wrapper="$HOME/.local/bin/$command" + if [[ -e $wrapper || -L $wrapper ]] && + [[ ! -L $wrapper || $(readlink "$wrapper") != "$HERMES_HOME/node/bin/$command" ]]; then + die "Keeping the existing $wrapper. Move it before installing Hermes Desktop." + fi + done + + local legacy=false + if [[ -e $root || -L $root ]]; then + [[ -d $root/.git && ! -L $root && ! -L $root/.git ]] || die "Keeping $root; it is not a managed checkout." + if [[ -f $marker ]] && grep -qxF updating "$marker"; then + legacy=true + elif [[ ! -f $marker ]]; then + python -I - "$root/.hermes-bootstrap-complete" <<'PY' || die "Keeping the existing checkout; install its desktop with 'hermes desktop'." +import json, sys +try: + stamp = json.load(open(sys.argv[1])) + assert stamp.get('desktopVersion') and stamp.get('pinnedCommit') in { + 'e624e9fde561e1add9388384012b295fde669ade', + '29112bef099274229cadff79cdff7bf7b99c4b77', + } +except (OSError, ValueError, AssertionError, AttributeError): + sys.exit(1) +PY + legacy=true + fi + [[ -z $(git -C "$root" status --porcelain) ]] || die "Keeping local changes in $root. Save them before installing the desktop." + case "$(git -C "$root" remote get-url origin)" in + https://github.com/NousResearch/hermes-agent.git | git@github.com:NousResearch/hermes-agent.git) ;; + *) die "Keeping the checkout's custom origin. Install its desktop with 'hermes desktop'." ;; + esac + if [[ $legacy == true ]]; then + case "$(git -C "$root" rev-parse HEAD)" in + e624e9fde561e1add9388384012b295fde669ade | 29112bef099274229cadff79cdff7bf7b99c4b77) ;; + *) + if [[ $(git -C "$root" symbolic-ref --short -q HEAD) != "main" ]] || + ! git -C "$root" merge-base --is-ancestor HEAD refs/remotes/origin/main; then + die "The legacy checkout has moved. Run 'hermes update', then 'hermes desktop'." + fi + ;; + esac + fi + fi + + install_backup=$(mktemp -d "$HERMES_HOME/.omarchy-hermes-launchers.XXXXXX") + for command in hermes hermes-agent hermes-acp; do + wrapper="$HOME/.local/bin/$command" + if [[ -f $wrapper ]]; then cp -p "$wrapper" "$install_backup/$command"; fi + done + trap restore_launchers EXIT + trap 'exit 130' INT + trap 'exit 143' TERM + + local mise_predecessor=false + if [[ -f $install_backup/hermes ]] && grep -qxF '# Written by omarchy-install-hermes-cli.' "$install_backup/hermes"; then + mise_predecessor=true + fi + + if [[ $legacy == true ]]; then + if [[ $mise_predecessor == true ]]; then + printf '%s\n' 'pipx:hermes-agent[extras=all]' >"$root/.git/omarchy-mise-predecessor" + fi + if ! grep -qxF '/.omarchy-hermes-desktop' "$root/.git/info/exclude"; then + printf '/.omarchy-hermes-desktop\n' >>"$root/.git/info/exclude" + fi + printf 'updating\n' >"$marker" + local refspec='+refs/heads/main:refs/remotes/origin/main' + if ! git -C "$root" config --get-all remote.origin.fetch | grep -qxF "$refspec"; then + git -C "$root" config --add remote.origin.fetch "$refspec" + fi + git -C "$root" fetch origin "$refspec" + env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" update --yes --branch main + elif [[ ! -e $root ]]; then + stage prerequisites + stage repository + printf '/.omarchy-hermes-desktop\n' >>"$root/.git/info/exclude" + printf 'pending\n' >"$marker" + fi + + if [[ $mise_predecessor == true ]]; then + printf '%s\n' 'pipx:hermes-agent[extras=all]' >"$root/.git/omarchy-mise-predecessor" + fi + + if [[ $legacy == true ]]; then + env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" desktop --build-only + else + for command in venv python-deps node-deps config desktop; do stage "$command"; done + fi + + # Build first: a failed download leaves the old terminal CLI usable. + stage complete + stage path + runtime_ready || die "The CLI or desktop is not ready. See the installer output above." + if ! grep -qxF '/.omarchy-hermes-desktop' "$root/.git/info/exclude"; then + printf '/.omarchy-hermes-desktop\n' >>"$root/.git/info/exclude" + fi + printf 'ready\n' >"$marker" + trap - EXIT INT TERM + rm -rf "$install_backup" + exec 9>&- + if [[ -f $root/.git/omarchy-mise-predecessor ]] && command -v omarchy-install-hermes-cli >/dev/null 2>&1; then + omarchy-install-hermes-cli || echo 'Run omarchy-install-hermes-cli again to finish the old CLI cleanup.' >&2 + fi + echo "Hermes is ready. Update it in the app or with 'hermes update'." +} + +case "${1:-}" in + --check) ready; exit ;; + --install) install_desktop; exit ;; + --setup) + shift + if /usr/bin/hermes-desktop --install; then + setsid --fork /usr/bin/hermes-desktop "$@" >/dev/null 2>&1 + exit + else + status=$? + echo "Hermes installation failed. Retry with: hermes-desktop --install" >&2 + if [[ -t 0 ]]; then read -r -p 'Press Enter to close.'; fi + exit "$status" + fi + ;; +esac + +if ! ready; then + if [[ -t 0 && -t 1 ]]; then + install_desktop + else + exec xdg-terminal-exec /usr/bin/hermes-desktop --setup "$@" + fi fi -# Chromium's own Ozone detection falls back to XWayland often enough to matter, -# and the result is a blurry window on every scaled display. Ask for Wayland -# directly, unless the user has already picked a platform themselves. -platform_flags=() -if [[ -n "${WAYLAND_DISPLAY:-}" || ${XDG_SESSION_TYPE:-} == wayland ]]; then - platform_flags=(--ozone-platform=wayland) +export HERMES_DESKTOP_HERMES_ROOT="$root" +export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}" +unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME +flags=() +if [[ -n ${WAYLAND_DISPLAY:-} || ${XDG_SESSION_TYPE:-} == "wayland" ]]; then + flags=(--ozone-platform=wayland) for flag in "$@"; do - case "$flag" in - --ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; - esac + case "$flag" in --ozone-platform=* | --ozone-platform-hint=*) flags=() ;; esac done fi -exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@" +# Disable only the unusable setuid helper; Chromium keeps its namespace sandbox. +if unshare --user --map-root-user true 2>/dev/null; then + flags+=(--disable-setuid-sandbox) +fi +exec "$(desktop_executable)" "${flags[@]}" "$@" diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py new file mode 100644 index 00000000..07b3b581 --- /dev/null +++ b/test/hermes-desktop-test.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +"""Exercise the packaged entry point with a disposable HOME and installer.""" +import json +import os +import signal +import time +from pathlib import Path +import subprocess +import tempfile +import unittest + +SOURCE = Path(__file__).resolve().parents[1] / 'pkgbuilds/hermes-desktop/hermes-desktop.sh' +INSTALLER = r'''#!/bin/bash +set -eu +while (( $# )); do + case $1 in --dir) root=$2; shift;; --stage) stage=$2; shift;; esac + shift +done +printf '%s\n' "$stage" >> "$TEST_LOG" +[[ ${FAIL_STAGE:-} != "$stage" ]] || exit 42 +case $stage in +repository) + mkdir -p "$root/venv/bin" + git init -q "$root" + git -C "$root" remote add origin https://github.com/NousResearch/hermes-agent.git + touch "$root/hermes" + cp "$TEST_PYTHON" "$root/venv/bin/python" + git -C "$root" add hermes venv + git -C "$root" -c user.email=test@example.com -c user.name=Test commit -qm initial + ;; +desktop) + mkdir -p "$root/apps/desktop/release/linux-unpacked" + cp "$TEST_GUI" "$root/apps/desktop/release/linux-unpacked/Hermes" + printf '/apps/\n/.hermes-bootstrap-complete\n' >> "$root/.git/info/exclude" + ;; +complete) touch "$root/.hermes-bootstrap-complete";; +path) + mkdir -p "$HOME/.local/bin" + for command in hermes hermes-agent hermes-acp; do + entry=hermes; suffix='' + [[ $command != hermes-agent ]] || entry=run_agent.py + [[ $command != hermes-acp ]] || suffix=' acp' + printf '#!/usr/bin/env bash\nunset PYTHONPATH\nunset PYTHONHOME\nexec "%s/venv/bin/python" "%s/%s"%s "$@"\n' "$root" "$root" "$entry" "$suffix" > "$HOME/.local/bin/$command" + chmod +x "$HOME/.local/bin/$command" + done + [[ ${FAIL_AFTER_PATH:-} != 1 ]] || exit 43 + ;; +esac +''' + +class LauncherTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory(prefix='hermes-package-') + self.addCleanup(self.temp.cleanup) + self.base = Path(self.temp.name) + self.home = self.base / 'home with spaces' + self.home.mkdir() + self.root = self.home / '.hermes/hermes-agent' + self.bin = self.base / 'bin' + self.bin.mkdir() + self.log = self.base / 'stages' + self.output = self.base / 'launch.json' + self.env = dict(os.environ, HOME=str(self.home), HERMES_HOME=str(self.home / '.hermes'), + PATH=f'{self.bin}:/usr/bin:/bin', TEST_LOG=str(self.log), + TEST_GUI=str(self.base / 'gui'), TEST_PYTHON=str(self.base / 'python'), + TEST_OUTPUT=str(self.output), GIT_CONFIG_NOSYSTEM='1', + GIT_CONFIG_GLOBAL='/dev/null') + for k in ['FAIL_STAGE', 'FAIL_AFTER_PATH', 'PYTHONHOME', 'PYTHONPATH']: + self.env.pop(k, None) + self.write(self.base / 'installer', INSTALLER) + self.write(self.base / 'python', '#!/bin/bash\n[[ ${FAIL_READY:-} != 1 ]]\n') + self.write(self.base / 'gui', '''#!/usr/bin/env python3 +import json,os,sys,time +json.dump({'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) +if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) +''') + self.write(self.bin / 'unshare', '#!/bin/bash\nexit 0\n') + self.write(self.bin / 'xdg-terminal-exec', '#!/bin/bash\nprintf "%s\\n" "$@" > "$TEST_OUTPUT"\n') + source = SOURCE.read_text().replace('/usr/share/hermes-desktop/install.sh', str(self.base / 'installer')) + source = source.replace('/usr/bin/hermes-desktop', str(self.base / 'launcher')) + self.write(self.base / 'launcher', source) + + def write(self, path, text): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text) + path.chmod(0o755) + + def run_launcher(self, *args, ok=True, **env): + result = subprocess.run(['bash', str(self.base / 'launcher'), *args], env=self.env | env, + text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self.assertEqual(result.returncode == 0, ok, result.stdout) + return result + + def install(self): + self.run_launcher('--install') + self.run_launcher('--check') + + def test_fresh_install_publishes_cli_after_desktop(self): + self.install() + stages = self.log.read_text().splitlines() + self.assertLess(stages.index('desktop'), stages.index('path')) + self.assertEqual((self.root / '.omarchy-hermes-desktop').read_text(), 'ready\n') + self.assertEqual(subprocess.check_output(['git','-C',str(self.root),'status','--porcelain'],env=self.env),b'') + + def test_warm_launch_and_package_reinstall_leave_runtime_untouched(self): + self.install() + before = self.log.read_bytes() + self.run_launcher('--install') + self.run_launcher('hermes://test?a=b', 'two words', WAYLAND_DISPLAY='wayland-1', ELECTRON_RUN_AS_NODE='1') + observed = json.loads(self.output.read_text()) + self.assertEqual(observed['args'], ['--ozone-platform=wayland','--disable-setuid-sandbox','hermes://test?a=b','two words']) + self.assertEqual(observed['root'],str(self.root)) + self.assertIsNone(observed['node']) + self.assertEqual(observed['password'],'gnome-libsecret') + self.assertEqual(before,self.log.read_bytes()) + + def test_explicit_platform_and_password_are_preserved(self): + self.install() + self.run_launcher('--ozone-platform=x11', WAYLAND_DISPLAY='wayland-1', HERMES_DESKTOP_PASSWORD_STORE='kwallet6') + result=json.loads(self.output.read_text()) + self.assertNotIn('--ozone-platform=wayland',result['args']) + self.assertEqual(result['password'],'kwallet6') + + def test_foreign_command_and_symlink_are_preserved(self): + wrapper=self.home/'.local/bin/hermes' + self.write(wrapper,'#!/bin/bash\necho custom\n') + before=wrapper.read_bytes() + self.run_launcher('--install',ok=False) + self.assertEqual(before,wrapper.read_bytes()) + self.assertFalse(self.log.exists()) + wrapper.unlink() + wrapper.symlink_to(self.base/'missing') + self.run_launcher('--install',ok=False) + self.assertTrue(wrapper.is_symlink()) + + def test_customized_native_wrapper_is_preserved(self): + self.install() + wrapper=self.home/'.local/bin/hermes' + wrapper.write_text(wrapper.read_text().replace('unset PYTHONPATH','export CUSTOM=yes\nunset PYTHONPATH')) + before=wrapper.read_bytes() + self.run_launcher('--install',ok=False) + self.assertEqual(wrapper.read_bytes(),before) + + def test_failed_build_preserves_old_cli_and_retry_succeeds(self): + wrapper=self.home/'.local/bin/hermes' + self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') + before=wrapper.read_bytes() + self.run_launcher('--install',ok=False,FAIL_STAGE='desktop') + self.assertEqual(wrapper.read_bytes(),before) + self.run_launcher('--check',ok=False) + self.install() + self.assertNotEqual(wrapper.read_bytes(),before) + + def test_late_path_failure_restores_old_cli(self): + wrapper=self.home/'.local/bin/hermes' + self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') + before=wrapper.read_bytes() + self.run_launcher('--install',ok=False,FAIL_AFTER_PATH='1') + self.assertEqual(wrapper.read_bytes(),before) + self.run_launcher('--check',ok=False) + self.install() + + def test_dirty_pending_checkout_is_preserved(self): + self.run_launcher('--install',ok=False,FAIL_STAGE='desktop') + (self.root/'hermes').write_text('user changes') + before=self.log.read_bytes() + self.run_launcher('--install',ok=False) + self.assertEqual((self.root/'hermes').read_text(),'user changes') + self.assertEqual(self.log.read_bytes(),before) + + def test_cold_graphical_launch_opens_visible_setup(self): + self.run_launcher('hermes://open') + self.assertEqual(self.output.read_text().splitlines(),[str(self.base/'launcher'),'--setup','hermes://open']) + self.assertFalse(self.log.exists()) + + def test_unexecutable_native_cli_is_repaired(self): + self.install() + wrapper=self.home/'.local/bin/hermes' + wrapper.chmod(0o644) + self.run_launcher('--check',ok=False) + self.install() + self.assertTrue(os.access(wrapper,os.X_OK)) + + def test_old_cli_ownership_survives_package_only_setup(self): + wrapper=self.home/'.local/bin/hermes' + self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') + self.install() + receipt=self.root/'.git/omarchy-mise-predecessor' + self.assertEqual(receipt.read_text(),'pipx:hermes-agent[extras=all]\n') + + def test_graphical_setup_detaches_and_releases_install_lock(self): + started=time.monotonic() + self.run_launcher('--setup', 'hermes://open', TEST_GUI_WAIT='1') + self.assertLess(time.monotonic()-started,5) + for _ in range(100): + if self.output.exists(): break + time.sleep(.02) + observed=json.loads(self.output.read_text()) + self.addCleanup(lambda: os.kill(observed['pid'],signal.SIGTERM)) + self.assertIn('hermes://open',observed['args']) + self.run_launcher('--install') + lock=self.home/'.hermes/.omarchy-hermes-desktop.lock' + self.assertEqual(subprocess.run(['flock','--nonblock',str(lock),'true']).returncode,0) + + def test_pending_marker_overrides_old_completion(self): + self.install() + (self.root/'.omarchy-hermes-desktop').write_text('pending\n') + self.run_launcher('--check',ok=False) + +if __name__=='__main__': + unittest.main() From 01e1a8de0f1d39dfe24711ae4fe0f0ae4a4d8f82 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 01:57:42 -0500 Subject: [PATCH 39/63] Exercise lock release on a cold terminal launch Co-Authored-By: GPT-6 Codex (xhigh) --- test/hermes-desktop-test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py index 07b3b581..15f03357 100644 --- a/test/hermes-desktop-test.py +++ b/test/hermes-desktop-test.py @@ -2,6 +2,7 @@ """Exercise the packaged entry point with a disposable HOME and installer.""" import json import os +import pty import signal import time from pathlib import Path @@ -202,6 +203,23 @@ if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) lock=self.home/'.hermes/.omarchy-hermes-desktop.lock' self.assertEqual(subprocess.run(['flock','--nonblock',str(lock),'true']).returncode,0) + def test_cold_terminal_launch_releases_lock_before_exec(self): + master,slave=pty.openpty() + process=subprocess.Popen(['bash',str(self.base/'launcher')],stdin=slave,stdout=slave,stderr=slave,env=self.env | {'TEST_GUI_WAIT':'1'}) + os.close(slave) + def cleanup(): + if process.poll() is None: process.terminate() + process.wait(timeout=5) + os.close(master) + self.addCleanup(cleanup) + for _ in range(100): + if self.output.exists(): break + time.sleep(.02) + self.assertTrue(self.output.exists()) + self.assertIsNone(process.poll()) + lock=self.home/'.hermes/.omarchy-hermes-desktop.lock' + self.assertEqual(subprocess.run(['flock','--nonblock',str(lock),'true']).returncode,0) + def test_pending_marker_overrides_old_completion(self): self.install() (self.root/'.omarchy-hermes-desktop').write_text('pending\n') From 68a4a6cc220820d0bc4d067f8709d8892fd1275e Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 01:59:58 -0500 Subject: [PATCH 40/63] Keep in-app updates on the verified Hermes CLI --- pkgbuilds/hermes-desktop/PKGBUILD | 2 +- pkgbuilds/hermes-desktop/hermes-desktop.sh | 1 + test/hermes-desktop-test.py | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index f00eee30..f75815ba 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -30,7 +30,7 @@ source=("install-${_commit}.sh::https://raw.githubusercontent.com/NousResearch/h 'hermes-desktop.png') sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' '821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6' - '5113ba692fe00b0f4806f799ce16b85a44bb2bd901ca17577ba64c887b1fb33d' + '259547e64eda2fd6514bc5bd9e6637de063a0ac5e2739d4c873f18838fe26b3a' 'c37d4cfa4801eccbd769fddaebb2115d54faa49a5b7bc0f305f563e3cefd9bd8' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index e8a17166..2de3f099 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -228,6 +228,7 @@ if ! ready; then fi export HERMES_DESKTOP_HERMES_ROOT="$root" +export PATH="$HOME/.local/bin:$PATH" export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}" unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py index 15f03357..05bcd36d 100644 --- a/test/hermes-desktop-test.py +++ b/test/hermes-desktop-test.py @@ -71,10 +71,11 @@ class LauncherTests(unittest.TestCase): self.write(self.base / 'installer', INSTALLER) self.write(self.base / 'python', '#!/bin/bash\n[[ ${FAIL_READY:-} != 1 ]]\n') self.write(self.base / 'gui', '''#!/usr/bin/env python3 -import json,os,sys,time -json.dump({'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) +import json,os,sys,time,shutil +json.dump({'selected_cli':shutil.which('hermes'),'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) ''') + self.write(self.bin / 'hermes', '#!/bin/bash\nexit 99\n') self.write(self.bin / 'unshare', '#!/bin/bash\nexit 0\n') self.write(self.bin / 'xdg-terminal-exec', '#!/bin/bash\nprintf "%s\\n" "$@" > "$TEST_OUTPUT"\n') source = SOURCE.read_text().replace('/usr/share/hermes-desktop/install.sh', str(self.base / 'installer')) @@ -111,6 +112,7 @@ if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) observed = json.loads(self.output.read_text()) self.assertEqual(observed['args'], ['--ozone-platform=wayland','--disable-setuid-sandbox','hermes://test?a=b','two words']) self.assertEqual(observed['root'],str(self.root)) + self.assertEqual(observed['selected_cli'],str(self.home/'.local/bin/hermes')) self.assertIsNone(observed['node']) self.assertEqual(observed['password'],'gnome-libsecret') self.assertEqual(before,self.log.read_bytes()) From 2b1edd47b218911ab222bc85b8631dbae4b66e2c Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 02:02:52 -0500 Subject: [PATCH 41/63] Revert "Keep in-app updates on the verified Hermes CLI" This reverts commit 68a4a6cc220820d0bc4d067f8709d8892fd1275e. --- pkgbuilds/hermes-desktop/PKGBUILD | 2 +- pkgbuilds/hermes-desktop/hermes-desktop.sh | 1 - test/hermes-desktop-test.py | 6 ++---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index f75815ba..f00eee30 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -30,7 +30,7 @@ source=("install-${_commit}.sh::https://raw.githubusercontent.com/NousResearch/h 'hermes-desktop.png') sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' '821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6' - '259547e64eda2fd6514bc5bd9e6637de063a0ac5e2739d4c873f18838fe26b3a' + '5113ba692fe00b0f4806f799ce16b85a44bb2bd901ca17577ba64c887b1fb33d' 'c37d4cfa4801eccbd769fddaebb2115d54faa49a5b7bc0f305f563e3cefd9bd8' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 2de3f099..e8a17166 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -228,7 +228,6 @@ if ! ready; then fi export HERMES_DESKTOP_HERMES_ROOT="$root" -export PATH="$HOME/.local/bin:$PATH" export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}" unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py index 05bcd36d..15f03357 100644 --- a/test/hermes-desktop-test.py +++ b/test/hermes-desktop-test.py @@ -71,11 +71,10 @@ class LauncherTests(unittest.TestCase): self.write(self.base / 'installer', INSTALLER) self.write(self.base / 'python', '#!/bin/bash\n[[ ${FAIL_READY:-} != 1 ]]\n') self.write(self.base / 'gui', '''#!/usr/bin/env python3 -import json,os,sys,time,shutil -json.dump({'selected_cli':shutil.which('hermes'),'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) +import json,os,sys,time +json.dump({'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) ''') - self.write(self.bin / 'hermes', '#!/bin/bash\nexit 99\n') self.write(self.bin / 'unshare', '#!/bin/bash\nexit 0\n') self.write(self.bin / 'xdg-terminal-exec', '#!/bin/bash\nprintf "%s\\n" "$@" > "$TEST_OUTPUT"\n') source = SOURCE.read_text().replace('/usr/share/hermes-desktop/install.sh', str(self.base / 'installer')) @@ -112,7 +111,6 @@ if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) observed = json.loads(self.output.read_text()) self.assertEqual(observed['args'], ['--ozone-platform=wayland','--disable-setuid-sandbox','hermes://test?a=b','two words']) self.assertEqual(observed['root'],str(self.root)) - self.assertEqual(observed['selected_cli'],str(self.home/'.local/bin/hermes')) self.assertIsNone(observed['node']) self.assertEqual(observed['password'],'gnome-libsecret') self.assertEqual(before,self.log.read_bytes()) From f4adf308f9c5facc132d4727750376da7ced1db1 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 02:17:27 -0500 Subject: [PATCH 42/63] Make native Hermes setup retryable without a late sudo step Stage the initial clone before publishing its ownership marker and build the desktop through the same native CLI used by updates. Keep a concurrent checkout intact and reset pkgrel for the version change. Co-Authored-By: Claude Opus 5 (default) --- pkgbuilds/hermes-desktop/PKGBUILD | 4 +-- pkgbuilds/hermes-desktop/hermes-desktop.sh | 25 +++++++++----- test/hermes-desktop-test.py | 39 ++++++++++++++++++---- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index f00eee30..4f2f7954 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -2,7 +2,7 @@ pkgname=hermes-desktop pkgver=2026.8.31 -pkgrel=2 +pkgrel=1 pkgdesc='Installer and launcher for the self-updating Hermes desktop' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' @@ -30,7 +30,7 @@ source=("install-${_commit}.sh::https://raw.githubusercontent.com/NousResearch/h 'hermes-desktop.png') sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' '821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6' - '5113ba692fe00b0f4806f799ce16b85a44bb2bd901ca17577ba64c887b1fb33d' + 'b4c282ead8a5d1a631f702474ea36eb7362ab1f0f87ad2e71f60a8698de07035' 'c37d4cfa4801eccbd769fddaebb2115d54faa49a5b7bc0f305f563e3cefd9bd8' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index e8a17166..4d9b8cd6 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -66,7 +66,7 @@ restore_launchers() { } stage() { - bash "$installer" --dir "$root" --branch main --non-interactive --stage "$1" + bash "$installer" --dir "${2:-$root}" --branch main --non-interactive --stage "$1" } install_desktop() { @@ -170,20 +170,29 @@ PY env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" update --yes --branch main elif [[ ! -e $root ]]; then stage prerequisites - stage repository - printf '/.omarchy-hermes-desktop\n' >>"$root/.git/info/exclude" - printf 'pending\n' >"$marker" + # Publish only a complete clone with its ownership marker. An interrupted + # clone stays aside for inspection and cannot block the next installation. + local repository_dir + repository_dir=$(mktemp -d "$HERMES_HOME/.omarchy-hermes-repository.XXXXXX") + echo "Preparing the Hermes checkout in $repository_dir" + stage repository "$repository_dir/hermes-agent" + printf '/.omarchy-hermes-desktop\n' >>"$repository_dir/hermes-agent/.git/info/exclude" + printf 'pending\n' >"$repository_dir/hermes-agent/.omarchy-hermes-desktop" + mv -T --no-clobber "$repository_dir/hermes-agent" "$root" + [[ ! -e $repository_dir/hermes-agent ]] || die "Keeping the checkout that appeared at $root during installation." + rmdir "$repository_dir" fi if [[ $mise_predecessor == true ]]; then printf '%s\n' 'pipx:hermes-agent[extras=all]' >"$root/.git/omarchy-mise-predecessor" fi - if [[ $legacy == true ]]; then - env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" desktop --build-only - else - for command in venv python-deps node-deps config desktop; do stage "$command"; done + if [[ $legacy == false ]]; then + for command in venv python-deps node-deps config; do stage "$command"; done fi + # The native builder also handles updates and supports the namespace + # sandbox. The shell installer's desktop stage still requires a sudo chown. + env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" desktop --build-only # Build first: a failed download leaves the old terminal CLI usable. stage complete diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py index 15f03357..78062b6a 100644 --- a/test/hermes-desktop-test.py +++ b/test/hermes-desktop-test.py @@ -23,16 +23,16 @@ case $stage in repository) mkdir -p "$root/venv/bin" git init -q "$root" + [[ ${FAIL_PARTIAL_CLONE:-} != 1 ]] || exit 44 git -C "$root" remote add origin https://github.com/NousResearch/hermes-agent.git touch "$root/hermes" cp "$TEST_PYTHON" "$root/venv/bin/python" git -C "$root" add hermes venv git -C "$root" -c user.email=test@example.com -c user.name=Test commit -qm initial - ;; -desktop) - mkdir -p "$root/apps/desktop/release/linux-unpacked" - cp "$TEST_GUI" "$root/apps/desktop/release/linux-unpacked/Hermes" - printf '/apps/\n/.hermes-bootstrap-complete\n' >> "$root/.git/info/exclude" + if [[ ${RACE_CLONE:-} == 1 ]]; then + mkdir -p "$HOME/.hermes/hermes-agent" + printf 'foreign checkout\n' > "$HOME/.hermes/hermes-agent/keep" + fi ;; complete) touch "$root/.hermes-bootstrap-complete";; path) @@ -69,7 +69,20 @@ class LauncherTests(unittest.TestCase): for k in ['FAIL_STAGE', 'FAIL_AFTER_PATH', 'PYTHONHOME', 'PYTHONPATH']: self.env.pop(k, None) self.write(self.base / 'installer', INSTALLER) - self.write(self.base / 'python', '#!/bin/bash\n[[ ${FAIL_READY:-} != 1 ]]\n') + self.write(self.base / 'python', '''#!/bin/bash +set -eu +if [[ ${2:-} == desktop ]]; then + [[ ${3:-} == --build-only ]] + printf 'desktop\\n' >> "$TEST_LOG" + [[ ${FAIL_STAGE:-} != desktop ]] || exit 42 + root=$(dirname "$1") + mkdir -p "$root/apps/desktop/release/linux-unpacked" + cp "$TEST_GUI" "$root/apps/desktop/release/linux-unpacked/Hermes" + printf '/apps/\\n/.hermes-bootstrap-complete\\n' >> "$root/.git/info/exclude" +else + [[ ${FAIL_READY:-} != 1 ]] +fi +''') self.write(self.base / 'gui', '''#!/usr/bin/env python3 import json,os,sys,time json.dump({'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) @@ -103,6 +116,20 @@ if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) self.assertEqual((self.root / '.omarchy-hermes-desktop').read_text(), 'ready\n') self.assertEqual(subprocess.check_output(['git','-C',str(self.root),'status','--porcelain'],env=self.env),b'') + def test_interrupted_clone_stays_aside_and_retry_succeeds(self): + self.run_launcher('--install', ok=False, FAIL_PARTIAL_CLONE='1') + self.assertFalse(self.root.exists()) + partials=list((self.home/'.hermes').glob('.omarchy-hermes-repository.*/hermes-agent/.git')) + self.assertEqual(len(partials),1) + self.install() + self.assertTrue(partials[0].is_dir()) + + def test_clone_publication_preserves_concurrent_checkout(self): + self.run_launcher('--install', ok=False, RACE_CLONE='1') + self.assertEqual((self.root/'keep').read_text(),'foreign checkout\n') + self.assertFalse((self.root/'.omarchy-hermes-desktop').exists()) + self.assertFalse((self.home/'.local/bin/hermes').exists()) + def test_warm_launch_and_package_reinstall_leave_runtime_untouched(self): self.install() before = self.log.read_bytes() From 351f188d025d7e07d47bd1cb78bd11bada0ea2ac Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 02:21:16 -0500 Subject: [PATCH 43/63] Register Hermes Desktop only after publishing its native CLI Keep build-time registration private, verify the published launcher, and then let the native command register the final desktop entry. Keep that launcher first on the desktop environment PATH for subsequent registrations. Co-Authored-By: GPT-6 Codex (xhigh) --- pkgbuilds/hermes-desktop/PKGBUILD | 2 +- pkgbuilds/hermes-desktop/hermes-desktop.sh | 7 +++- test/hermes-desktop-test.py | 40 ++++++++++++++++++---- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 4f2f7954..5d32d599 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -30,7 +30,7 @@ source=("install-${_commit}.sh::https://raw.githubusercontent.com/NousResearch/h 'hermes-desktop.png') sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' '821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6' - 'b4c282ead8a5d1a631f702474ea36eb7362ab1f0f87ad2e71f60a8698de07035' + '85834b56348529413bbc248282d3a1769a2276a016affe9eafb7dd8773d9e12f' 'c37d4cfa4801eccbd769fddaebb2115d54faa49a5b7bc0f305f563e3cefd9bd8' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 4d9b8cd6..77f87ef3 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -192,12 +192,15 @@ PY fi # The native builder also handles updates and supports the namespace # sandbox. The shell installer's desktop stage still requires a sudo chown. - env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" desktop --build-only + # The build registers a desktop entry, too. Keep it private until the new + # CLI is published so a different hermes on PATH cannot become its target. + env -u PYTHONPATH -u PYTHONHOME XDG_DATA_HOME="$install_backup/desktop" "${cli[@]}" desktop --build-only # Build first: a failed download leaves the old terminal CLI usable. stage complete stage path runtime_ready || die "The CLI or desktop is not ready. See the installer output above." + env -u PYTHONPATH -u PYTHONHOME PATH="$HOME/.local/bin:$PATH" "${cli[@]}" desktop --skip-build --build-only if ! grep -qxF '/.omarchy-hermes-desktop' "$root/.git/info/exclude"; then printf '/.omarchy-hermes-desktop\n' >>"$root/.git/info/exclude" fi @@ -238,6 +241,8 @@ fi export HERMES_DESKTOP_HERMES_ROOT="$root" export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}" +# Upstream desktop registration resolves its launcher through PATH. +export PATH="$HOME/.local/bin:$PATH" unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME flags=() diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py index 78062b6a..03958958 100644 --- a/test/hermes-desktop-test.py +++ b/test/hermes-desktop-test.py @@ -62,6 +62,8 @@ class LauncherTests(unittest.TestCase): self.log = self.base / 'stages' self.output = self.base / 'launch.json' self.env = dict(os.environ, HOME=str(self.home), HERMES_HOME=str(self.home / '.hermes'), + XDG_DATA_HOME=str(self.home / '.local/share'), + XDG_CONFIG_HOME=str(self.home / '.config'), XDG_CACHE_HOME=str(self.home / '.cache'), PATH=f'{self.bin}:/usr/bin:/bin', TEST_LOG=str(self.log), TEST_GUI=str(self.base / 'gui'), TEST_PYTHON=str(self.base / 'python'), TEST_OUTPUT=str(self.output), GIT_CONFIG_NOSYSTEM='1', @@ -72,13 +74,21 @@ class LauncherTests(unittest.TestCase): self.write(self.base / 'python', '''#!/bin/bash set -eu if [[ ${2:-} == desktop ]]; then - [[ ${3:-} == --build-only ]] - printf 'desktop\\n' >> "$TEST_LOG" - [[ ${FAIL_STAGE:-} != desktop ]] || exit 42 root=$(dirname "$1") - mkdir -p "$root/apps/desktop/release/linux-unpacked" - cp "$TEST_GUI" "$root/apps/desktop/release/linux-unpacked/Hermes" - printf '/apps/\\n/.hermes-bootstrap-complete\\n' >> "$root/.git/info/exclude" + if [[ ${3:-} == --skip-build ]]; then + [[ ${4:-} == --build-only && -x $root/apps/desktop/release/linux-unpacked/Hermes ]] + [[ ${FAIL_FINAL_REGISTER:-} != 1 ]] || exit 45 + else + [[ ${3:-} == --build-only ]] + printf 'desktop\\n' >> "$TEST_LOG" + [[ ${FAIL_STAGE:-} != desktop ]] || exit 42 + mkdir -p "$root/apps/desktop/release/linux-unpacked" + cp "$TEST_GUI" "$root/apps/desktop/release/linux-unpacked/Hermes" + printf '/apps/\\n/.hermes-bootstrap-complete\\n' >> "$root/.git/info/exclude" + fi + entry_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications" + mkdir -p "$entry_dir" + printf 'Exec=%s desktop\\n' "$(command -v hermes || printf '%s/venv/bin/python -m hermes_cli.main' "$root")" > "$entry_dir/hermes.desktop" else [[ ${FAIL_READY:-} != 1 ]] fi @@ -130,6 +140,24 @@ if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) self.assertFalse((self.root/'.omarchy-hermes-desktop').exists()) self.assertFalse((self.home/'.local/bin/hermes').exists()) + def test_desktop_registration_uses_published_cli(self): + self.write(self.bin/'hermes', '#!/bin/bash\necho foreign\n') + self.install() + entry=self.home/'.local/share/applications/hermes.desktop' + self.assertEqual(entry.read_text(),f'Exec={self.home}/.local/bin/hermes desktop\n') + + def test_failed_registration_preserves_previous_launcher(self): + wrapper=self.home/'.local/bin/hermes' + self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') + entry=self.home/'.local/share/applications/hermes.desktop' + self.write(entry,'previous desktop entry\n') + before=wrapper.read_bytes() + self.run_launcher('--install',ok=False,FAIL_FINAL_REGISTER='1') + self.assertEqual(wrapper.read_bytes(),before) + self.assertEqual(entry.read_text(),'previous desktop entry\n') + self.run_launcher('--check',ok=False) + self.install() + def test_warm_launch_and_package_reinstall_leave_runtime_untouched(self): self.install() before = self.log.read_bytes() From 707b5e6c51a4589f3dba2acb136b9ea85fa30856 Mon Sep 17 00:00:00 2001 From: powderluv Date: Sun, 6 Sep 2026 02:57:34 -0700 Subject: [PATCH 44/63] Bump libfprint-git for Synaptics fingerprint reader 06cb:010b (#321) * Bump libfprint-git for Synaptics 06cb:010b support * Describe libfprint-git as the driver the fingerprint setup installs Co-Authored-By: Claude Fable 5.1 --------- Co-authored-by: powderluv Co-authored-by: David Heinemeier Hansson Co-authored-by: Claude Fable 5.1 --- pkgbuilds/libfprint-git/PKGBUILD | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/pkgbuilds/libfprint-git/PKGBUILD b/pkgbuilds/libfprint-git/PKGBUILD index 26e4a71e..07fd2b9c 100644 --- a/pkgbuilds/libfprint-git/PKGBUILD +++ b/pkgbuilds/libfprint-git/PKGBUILD @@ -1,22 +1,18 @@ # Maintainer: Mikko Nyman # Based on the Arch libfprint package by Jan Alexander Steffens (heftig). # -# Carried the focaltech_moc driver and the FocalTech FT9349 device ID -# (USB 2808:a97a, ASUS ExpertBook Ultra B9406CAA) while mainline libfprint -# releases (1.94.x) lacked them. libfprint 1.94.100 ships both, so Omarchy -# has moved back to stock libfprint and no longer installs this package. -# -# Kept around pinned to the last known good commit as an escape hatch; -# the fallback patch stays for the conditional in prepare(). +# Omarchy's fingerprint setup installs this in place of stock libfprint, so +# readers upstream supports ahead of an Arch release reach users by bumping +# the pin here. Pinned to the commit adding the Synaptics reader USB +# 06cb:010b, missing from stock libfprint 1.94.100; the FocalTech MOC support +# carried by the previous pin is retained. pkgname=libfprint-git -pkgver=1.94.10.r12.gd79f157 -pkgrel=1.1 -# The repo already carries an untested 1.94.10.r220.g695663f build that sorts -# above the pinned pkgver; the epoch makes the pinned build win the version -# comparison and gives it a fresh filename that can be promoted. +pkgver=1.94.100.r10.g6f9479c +pkgrel=1 +# Retain the epoch used to supersede the previously published untested build. epoch=1 -pkgdesc="Library for fingerprint readers (libfprint master with FocalTech MOC updates)" +pkgdesc="Library for fingerprint readers (pinned upstream snapshot with FocalTech and Synaptics 06cb:010b support)" url="https://fprint.freedesktop.org/" arch=(x86_64) license=(LGPL-2.1-or-later) @@ -47,7 +43,7 @@ provides=(libfprint libfprint-2.so) conflicts=(libfprint) groups=(fprint) source=( - "git+https://gitlab.freedesktop.org/libfprint/libfprint.git#commit=d79f157282085738ea8ffbe8c2ae96fb8b3ad831" + "git+https://gitlab.freedesktop.org/libfprint/libfprint.git#commit=6f9479c3d55f847c1b3769f28ceb99227f9858cf" "focaltech_moc-add-pid-0xA97A.patch" ) b2sums=( @@ -79,9 +75,6 @@ build() { ) arch-meson libfprint build "${meson_options[@]}" meson compile -C build - # Regenerate autosuspend.hwdb from the patched id_table[] so the - # udev-hwdb test in check() doesn't fail on the new PID 0xA97A. - ninja -C build sync-udev-hwdb } check() { From 377ca9cdfd71d9cb1f2a7ca50b4864606f3334d8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 6 Sep 2026 21:46:10 +0200 Subject: [PATCH 45/63] Package Muse Code from Meta's native binaries (#329) --- pkgbuilds/muse-code/.omarchy/package.json | 4 ++++ pkgbuilds/muse-code/PKGBUILD | 27 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 pkgbuilds/muse-code/.omarchy/package.json create mode 100644 pkgbuilds/muse-code/PKGBUILD diff --git a/pkgbuilds/muse-code/.omarchy/package.json b/pkgbuilds/muse-code/.omarchy/package.json new file mode 100644 index 00000000..db153c3e --- /dev/null +++ b/pkgbuilds/muse-code/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "release_ring": "fast" +} diff --git a/pkgbuilds/muse-code/PKGBUILD b/pkgbuilds/muse-code/PKGBUILD new file mode 100644 index 00000000..6a7a0943 --- /dev/null +++ b/pkgbuilds/muse-code/PKGBUILD @@ -0,0 +1,27 @@ +# Maintainer: David Heinemeier Hansson + +pkgname=muse-code +pkgver=1.0.3.r2198.1 +pkgrel=1 +pkgdesc="Meta's terminal coding agent" +arch=('x86_64' 'aarch64') +url="https://dev.meta.ai" +license=('LicenseRef-Meta-Model-API') +depends=('ca-certificates') +optdepends=('git: workspace worktrees and version control') +# Arch's unrelated MusE sequencer also owns /usr/bin/muse. +conflicts=('muse' 'muse-code-bin' 'muse-bin' 'musecode-bin' 'musecode') +options=('!debug' '!strip') + +# Meta publishes standalone, statically linked binaries. Keep the build number +# in pkgver so releases with the same marketing version still upgrade. +_upstream_ver="${pkgver/.r/-R}" +_download="https://lookaside.facebook.com/lookaside/muse/download/" +source_x86_64=("${pkgname}-${pkgver}-x86_64::${_download}?channel=muse&version=${_upstream_ver}&file=muse-x86-linux") +source_aarch64=("${pkgname}-${pkgver}-aarch64::${_download}?channel=muse&version=${_upstream_ver}&file=muse-aarch64-linux") +sha256sums_x86_64=('75a68f98c437dfd17d264730c5bc72d57e5f1e18d10472a9f53261ffcc091352') +sha256sums_aarch64=('4ffcf55f5eb0668643f30c5febd90d188b9a2da65858918444d31f6046940120') + +package() { + install -Dm755 "${srcdir}/${pkgname}-${pkgver}-${CARCH}" "${pkgdir}/usr/bin/muse" +} From 2cfa93bfdc5f8fd19d69deefebf16c31ddf4a15a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 6 Sep 2026 21:47:13 +0200 Subject: [PATCH 46/63] Revert "Package Muse Code from Meta's native binaries (#329)" (#330) This reverts commit 377ca9cdfd71d9cb1f2a7ca50b4864606f3334d8. --- pkgbuilds/muse-code/.omarchy/package.json | 4 ---- pkgbuilds/muse-code/PKGBUILD | 27 ----------------------- 2 files changed, 31 deletions(-) delete mode 100644 pkgbuilds/muse-code/.omarchy/package.json delete mode 100644 pkgbuilds/muse-code/PKGBUILD diff --git a/pkgbuilds/muse-code/.omarchy/package.json b/pkgbuilds/muse-code/.omarchy/package.json deleted file mode 100644 index db153c3e..00000000 --- a/pkgbuilds/muse-code/.omarchy/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "source": "local", - "release_ring": "fast" -} diff --git a/pkgbuilds/muse-code/PKGBUILD b/pkgbuilds/muse-code/PKGBUILD deleted file mode 100644 index 6a7a0943..00000000 --- a/pkgbuilds/muse-code/PKGBUILD +++ /dev/null @@ -1,27 +0,0 @@ -# Maintainer: David Heinemeier Hansson - -pkgname=muse-code -pkgver=1.0.3.r2198.1 -pkgrel=1 -pkgdesc="Meta's terminal coding agent" -arch=('x86_64' 'aarch64') -url="https://dev.meta.ai" -license=('LicenseRef-Meta-Model-API') -depends=('ca-certificates') -optdepends=('git: workspace worktrees and version control') -# Arch's unrelated MusE sequencer also owns /usr/bin/muse. -conflicts=('muse' 'muse-code-bin' 'muse-bin' 'musecode-bin' 'musecode') -options=('!debug' '!strip') - -# Meta publishes standalone, statically linked binaries. Keep the build number -# in pkgver so releases with the same marketing version still upgrade. -_upstream_ver="${pkgver/.r/-R}" -_download="https://lookaside.facebook.com/lookaside/muse/download/" -source_x86_64=("${pkgname}-${pkgver}-x86_64::${_download}?channel=muse&version=${_upstream_ver}&file=muse-x86-linux") -source_aarch64=("${pkgname}-${pkgver}-aarch64::${_download}?channel=muse&version=${_upstream_ver}&file=muse-aarch64-linux") -sha256sums_x86_64=('75a68f98c437dfd17d264730c5bc72d57e5f1e18d10472a9f53261ffcc091352') -sha256sums_aarch64=('4ffcf55f5eb0668643f30c5febd90d188b9a2da65858918444d31f6046940120') - -package() { - install -Dm755 "${srcdir}/${pkgname}-${pkgver}-${CARCH}" "${pkgdir}/usr/bin/muse" -} From 908d99ad805858d677b220651d35a2313255b25f Mon Sep 17 00:00:00 2001 From: dhh <2741+dhh@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:32:10 +0000 Subject: [PATCH 47/63] chore: sync upstream releases --- pkgbuilds/github-copilot-cli/PKGBUILD | 8 +++++--- pkgbuilds/omakade/PKGBUILD | 4 ++-- pkgbuilds/openai-codex-desktop/PKGBUILD | 6 +++--- pkgbuilds/openclaw/PKGBUILD | 4 ++-- pkgbuilds/strata/PKGBUILD | 6 +++--- pkgbuilds/tensaku/PKGBUILD | 4 ++-- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/pkgbuilds/github-copilot-cli/PKGBUILD b/pkgbuilds/github-copilot-cli/PKGBUILD index da9b7ac0..917dca58 100644 --- a/pkgbuilds/github-copilot-cli/PKGBUILD +++ b/pkgbuilds/github-copilot-cli/PKGBUILD @@ -6,7 +6,7 @@ _npmmodule=@github/copilot pkgname=github-copilot-cli _pkgexec=copilot -pkgver=1.0.82 +pkgver=1.0.83 pkgrel=1 pkgdesc="GitHub Copilot CLI brings the power of Copilot coding agent directly to your terminal." @@ -30,8 +30,10 @@ source=("https://registry.npmjs.org/${_npmmodule}/-/copilot-${pkgver}.tgz" "CHANGELOG-${pkgver}.md::${_urlraw}/changelog.md") noextract=("copilot-${pkgver}.tgz") -sha256sums=('563cbfe9b873fa52e998376cfea499eb77a5a300fb6db48fcc85f8eb297da4b9' - '653b32793f5e5d29952c0e301649473c8097afc15270aa488efa08aa9399afcf') +sha256sums=( + '135506fc2b13163ab55dbf76a06e2fbcbad04ecac76b4e9c6659f0ba309e6a86' + '0c0064a10effac8adf9ad97338bafaa0d7d7d5bf191cc1c0384e05ff4366d36c' +) # Document: https://wiki.archlinux.org/title/Node.js_package_guidelines package() { diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 946ed43c..12904bcc 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,5 +1,5 @@ pkgname=omakade -pkgver=1.4.0 +pkgver=1.6.1 pkgrel=1 pkgdesc='A beautiful, local-first game library for Omarchy' arch=('x86_64') @@ -9,7 +9,7 @@ depends=('glib2' 'libsecret' 'qt6-base' 'qt6-declarative' 'qt6-wayland' 'sdl3') makedepends=('cmake' 'ninja' 'pkgconf') options=('!debug') source=("$pkgname-$pkgver.tar.gz::https://github.com/tsouth89/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") -sha256sums=('a45021f2f894a6f6b7bf68c06942b2979046c6c398f7ce836c4436d57848abb9') +sha256sums=('7de0575fb6a7c16f7e8f88e15b6fa15e8486e924e92d07bf6029c0d94a0887e5') build() { cmake -S "$pkgname-$pkgver" -B build -G Ninja \ diff --git a/pkgbuilds/openai-codex-desktop/PKGBUILD b/pkgbuilds/openai-codex-desktop/PKGBUILD index bfd6ebf9..9e6a496b 100644 --- a/pkgbuilds/openai-codex-desktop/PKGBUILD +++ b/pkgbuilds/openai-codex-desktop/PKGBUILD @@ -5,7 +5,7 @@ # the version and checksums below from that repository's package index. pkgname=openai-codex-desktop -pkgver=26.901.20858 +pkgver=26.901.51231 pkgrel=1 pkgdesc="Official ChatGPT desktop app with Codex" arch=('x86_64' 'aarch64') @@ -71,8 +71,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}") noextract=("${_deb_x86_64}" "${_deb_aarch64}") sha256sums=('b3a4503b5931f102444bc7015c3cf4e40266cf034e0d682bd2a407dc5b3ee58c') -sha256sums_x86_64=('42a6477f22f4136d62321eda7b4697a79da1eb66d61dcb85ab0420860a1a5223') -sha256sums_aarch64=('a9b6cabc7a4f38e62e78b37b5950050d3a189d4502dc7ca6e1285a7899d87469') +sha256sums_x86_64=('62580188d87c3d3a9369dab7c73b42a8a32518d4df8a2d5bae6466ddeac5c05e') +sha256sums_aarch64=('02a2f5c6cb69509c62abcbdd13c76b139cdb2ca9edde7537239ddde024077ea0') package() { cd "${srcdir}" diff --git a/pkgbuilds/openclaw/PKGBUILD b/pkgbuilds/openclaw/PKGBUILD index 5b2f50c6..2da0c56a 100644 --- a/pkgbuilds/openclaw/PKGBUILD +++ b/pkgbuilds/openclaw/PKGBUILD @@ -7,7 +7,7 @@ # upstream's release cadence outruns the AUR. pkgname=openclaw -pkgver=2026.9.1 +pkgver=2026.9.2 pkgrel=1 pkgdesc='Multi-channel AI gateway with extensible messaging integrations' arch=(x86_64 aarch64) @@ -29,7 +29,7 @@ optdepends=( 'go: for installing skill tools not packaged for Arch' ) source=($pkgname-$pkgver.tgz::https://registry.npmjs.org/$pkgname/-/$pkgname-$pkgver.tgz) -sha256sums=('1bfcac877d53f1e41b69d15c24e081895b2f07d6ff2ffdfe0bf8a7336ab00e59') +sha256sums=('3431f4cd2d8dbd6b936def2694ac27e19fa0256295cf4ada0f652ecf1c9ee520') options=(!debug !strip) install=$pkgname.install noextract=($pkgname-$pkgver.tgz) diff --git a/pkgbuilds/strata/PKGBUILD b/pkgbuilds/strata/PKGBUILD index 976374f7..fae541e9 100644 --- a/pkgbuilds/strata/PKGBUILD +++ b/pkgbuilds/strata/PKGBUILD @@ -1,6 +1,6 @@ pkgname=strata -pkgver=0.8.0 -pkgrel=2 +pkgver=0.10.0 +pkgrel=1 pkgdesc='Fast, keyboard-first file manager for modern Linux desktops' arch=('x86_64' 'aarch64') url='https://github.com/lgse/strata' @@ -38,7 +38,7 @@ conflicts=('strata-git') options=('!debug' '!lto') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('8a6179ad5673afd8036ff6640c1740f6fa676516a05346c180d67ef98c4a56f7') +sha256sums=('01ea2676a0c59fac5daa0962ed6e721861bafd478b19cf5d9afaab6823175fe8') prepare() { cd "$pkgname-$pkgver" diff --git a/pkgbuilds/tensaku/PKGBUILD b/pkgbuilds/tensaku/PKGBUILD index cc49c792..0e6353be 100644 --- a/pkgbuilds/tensaku/PKGBUILD +++ b/pkgbuilds/tensaku/PKGBUILD @@ -6,7 +6,7 @@ # pushes to the AUR. Edit depends / package() etc. here — never in the # AUR repo directly. pkgname=tensaku -pkgver=0.28.0 +pkgver=0.29.0 pkgrel=1 pkgdesc='Modern screenshot annotation tool for Wayland' arch=('x86_64' 'aarch64') @@ -20,7 +20,7 @@ makedepends=('rust') optdepends=('hyprland: window snapping and pin placement' 'sway: window snapping and pin placement') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('08f61dce1a4b629a1229c0b2cead642e165319e4b61a6f605fc3568d9f6183ee') +sha256sums=('31595a8b4600107a63c1e5889f50f40e1ef832e51cfc038c1a7750b987fd34d0') prepare() { cd "$pkgname-$pkgver" From 397cf6f5279bc1098f3102427121abbee6bd2833 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 6 Sep 2026 17:12:14 -0400 Subject: [PATCH 48/63] Package the locate service options --- pkgbuilds/omarchy-settings-dev/PKGBUILD | 1 + pkgbuilds/omarchy-settings/PKGBUILD | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pkgbuilds/omarchy-settings-dev/PKGBUILD b/pkgbuilds/omarchy-settings-dev/PKGBUILD index 8cda17b2..6c7f8d98 100644 --- a/pkgbuilds/omarchy-settings-dev/PKGBUILD +++ b/pkgbuilds/omarchy-settings-dev/PKGBUILD @@ -202,6 +202,7 @@ package() { install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" fi + install -Dm644 default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf "$pkgdir/usr/lib/systemd/system/plocate-updatedb.service.d/10-omarchy.conf" # Same for applications/** — used by omarchy-refresh-applications, # omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share. diff --git a/pkgbuilds/omarchy-settings/PKGBUILD b/pkgbuilds/omarchy-settings/PKGBUILD index a457e6d8..f466293f 100644 --- a/pkgbuilds/omarchy-settings/PKGBUILD +++ b/pkgbuilds/omarchy-settings/PKGBUILD @@ -197,6 +197,10 @@ package() { install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" fi + # Older release pins still configure locate through install/config/locate.sh. + if [[ -f default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf ]]; then + install -Dm644 default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf "$pkgdir/usr/lib/systemd/system/plocate-updatedb.service.d/10-omarchy.conf" + fi # Same for applications/** — used by omarchy-refresh-applications, # omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share. From beb164a9a218ce680737ef46a94e05786fd9e72c Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 22:29:49 -0500 Subject: [PATCH 49/63] Resolve Hermes profile homes before package setup --- pkgbuilds/hermes-desktop/PKGBUILD | 2 +- pkgbuilds/hermes-desktop/hermes-desktop.sh | 20 ++++--- test/hermes-desktop-test.py | 67 +++++++++++++++++++++- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 5d32d599..aa4b57dc 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -30,7 +30,7 @@ source=("install-${_commit}.sh::https://raw.githubusercontent.com/NousResearch/h 'hermes-desktop.png') sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' '821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6' - '85834b56348529413bbc248282d3a1769a2276a016affe9eafb7dd8773d9e12f' + '27040084e691421dc1748e7646ae7ee79d5777e8ee1e1ec7581a68432d4a29c4' 'c37d4cfa4801eccbd769fddaebb2115d54faa49a5b7bc0f305f563e3cefd9bd8' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 77f87ef3..fe00939c 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -1,18 +1,24 @@ #!/bin/bash set -euo pipefail -export HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" -HERMES_HOME=$(realpath -ms "$HERMES_HOME") -root="$HERMES_HOME/hermes-agent" -marker="$root/.omarchy-hermes-desktop" -installer=/usr/share/hermes-desktop/install.sh -cli=("$root/venv/bin/python" "$root/hermes") - die() { echo "Hermes: $*" >&2 exit 1 } +HERMES_HOME=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}") +# A profile session shares its parent home's installation and launchers. +home_parent=${HERMES_HOME%/*} +if [[ ${home_parent##*/} == "profiles" ]]; then + HERMES_HOME=${home_parent%/*} +fi +[[ $HERMES_HOME == /* && $HERMES_HOME != "/" ]] || die "Use a Hermes data directory other than /." +export HERMES_HOME +root="$HERMES_HOME/hermes-agent" +marker="$root/.omarchy-hermes-desktop" +installer=/usr/share/hermes-desktop/install.sh +cli=("$root/venv/bin/python" "$root/hermes") + desktop_executable() { local executable for executable in "$root/apps/desktop/release/linux-unpacked/"{Hermes,hermes}; do diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py index 03958958..d4f60c28 100644 --- a/test/hermes-desktop-test.py +++ b/test/hermes-desktop-test.py @@ -95,7 +95,7 @@ fi ''') self.write(self.base / 'gui', '''#!/usr/bin/env python3 import json,os,sys,time -json.dump({'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) +json.dump({'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'home':os.environ['HERMES_HOME'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) ''') self.write(self.bin / 'unshare', '#!/bin/bash\nexit 0\n') @@ -126,6 +126,71 @@ if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) self.assertEqual((self.root / '.omarchy-hermes-desktop').read_text(), 'ready\n') self.assertEqual(subprocess.check_output(['git','-C',str(self.root),'status','--porcelain'],env=self.env),b'') + def test_profile_setup_installs_in_machine_home(self): + profile = self.home / '.hermes/profiles/coder' + self.write(profile / 'config.yaml', 'profile data\n') + self.env['HERMES_HOME'] = str(profile) + '/./' + self.install() + self.assertEqual((self.root / '.omarchy-hermes-desktop').read_text(), 'ready\n') + self.assertEqual(list(profile.iterdir()), [profile / 'config.yaml']) + self.assertEqual((profile / 'config.yaml').read_text(), 'profile data\n') + + def test_profile_warm_check_install_and_launch_share_machine_runtime(self): + self.install() + before = self.log.read_bytes() + self.env['HERMES_HOME'] = str(self.home / '.hermes/profiles/coder') + self.run_launcher('--check') + self.run_launcher('--install') + self.run_launcher('hermes://profile') + observed = json.loads(self.output.read_text()) + self.assertEqual(observed['root'], str(self.root)) + self.assertEqual(observed['home'], str(self.root.parent)) + self.assertEqual(self.log.read_bytes(), before) + + def test_custom_home_profile_uses_its_own_parent_root(self): + custom_home = self.base / 'custom data' + self.root = custom_home / 'hermes-agent' + self.env['HERMES_HOME'] = str(custom_home / 'profiles/coder') + self.install() + self.run_launcher() + observed = json.loads(self.output.read_text()) + self.assertEqual(observed['root'], str(self.root)) + self.assertEqual(observed['home'], str(custom_home)) + self.assertFalse((self.home / '.hermes').exists()) + self.assertFalse((custom_home / 'profiles').exists()) + + def test_custom_home_with_profiles_ancestor_is_preserved(self): + custom_home = self.base / 'profiles/team/custom data' + self.root = custom_home / 'hermes-agent' + self.env['HERMES_HOME'] = str(custom_home) + self.install() + self.run_launcher() + observed = json.loads(self.output.read_text()) + self.assertEqual(observed['root'], str(self.root)) + self.assertEqual(observed['home'], str(custom_home)) + + def test_profile_resolution_preserves_symlink_home_spelling(self): + target = self.base / 'physical data' + target.mkdir() + alias = self.base / 'data alias' + alias.symlink_to(target, target_is_directory=True) + self.root = alias / 'hermes-agent' + self.env['HERMES_HOME'] = str(alias / 'profiles/coder') + self.install() + self.run_launcher() + observed = json.loads(self.output.read_text()) + self.assertEqual(observed['root'], str(self.root)) + self.assertEqual(observed['home'], str(alias)) + self.assertTrue(alias.is_symlink()) + + def test_filesystem_root_home_is_rejected_before_mutation(self): + self.write(self.bin / 'mkdir', '#!/bin/bash\nprintf called >> "$TEST_LOG"\nexit 42\n') + for home in ['/', '/profiles/coder', '/profiles/coder/./']: + with self.subTest(home=home): + result = self.run_launcher('--install', ok=False, HERMES_HOME=home) + self.assertIn('Use a Hermes data directory other than /.', result.stdout) + self.assertFalse(self.log.exists()) + def test_interrupted_clone_stays_aside_and_retry_succeeds(self): self.run_launcher('--install', ok=False, FAIL_PARTIAL_CLONE='1') self.assertFalse(self.root.exists()) From b36bfce10976acdf463469d81dd13b2499d32c23 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Sun, 6 Sep 2026 22:53:12 -0500 Subject: [PATCH 50/63] Restore Hermes PKGBUILD formatting --- pkgbuilds/hermes-desktop/PKGBUILD | 61 +++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index aa4b57dc..5783f0b5 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -11,13 +11,47 @@ license=('MIT') # Hermes builds its desktop under the user's runtime so both the app and CLI # can update it. The compiler and Electron libraries must survive installation. depends=( - 'alsa-lib' 'at-spi2-core' 'bash' 'cairo' 'curl' 'dbus' 'expat' 'gcc' - 'gdk-pixbuf2' 'git' 'glib2' 'glibc' 'gtk3' 'hicolor-icon-theme' 'libcups' - 'libdrm' 'libglvnd' 'libnotify' 'libsecret' 'libx11' 'libxcb' 'libxcomposite' - 'libxdamage' 'libxext' 'libxfixes' 'libxkbcommon' 'libxrandr' 'make' 'mesa' - 'nodejs' 'npm' 'nspr' 'nss' 'pango' 'python' 'systemd-libs' 'util-linux' - 'xdg-terminal-exec' 'xdg-utils' + 'alsa-lib' + 'at-spi2-core' + 'bash' + 'cairo' + 'curl' + 'dbus' + 'expat' + 'gcc' + 'gdk-pixbuf2' + 'git' + 'glib2' + 'glibc' + 'gtk3' + 'hicolor-icon-theme' + 'libcups' + 'libdrm' + 'libglvnd' + 'libnotify' + 'libsecret' + 'libx11' + 'libxcb' + 'libxcomposite' + 'libxdamage' + 'libxext' + 'libxfixes' + 'libxkbcommon' + 'libxrandr' + 'make' + 'mesa' + 'nodejs' + 'npm' + 'nspr' + 'nss' + 'pango' + 'python' + 'systemd-libs' + 'util-linux' + 'xdg-terminal-exec' + 'xdg-utils' ) + makedepends=('imagemagick') # Snapshot the bootstrap installer, including upstream's Linux sandbox fixes. @@ -35,19 +69,24 @@ sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') package() { - install -Dm644 "${srcdir}/install-${_commit}.sh" "${pkgdir}/usr/share/${pkgname}/install.sh" - install -Dm644 "${srcdir}/LICENSE-${_commit}" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" + install -Dm644 "${srcdir}/install-${_commit}.sh" \ + "${pkgdir}/usr/share/${pkgname}/install.sh" + install -Dm644 "${srcdir}/LICENSE-${_commit}" \ + "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" install -Dm755 "${srcdir}/hermes-desktop.sh" "${pkgdir}/usr/bin/${pkgname}" # Native registration uses hermes.desktop but omits URI handling. Keep the # original handler ID hidden, and let the native entry replace the visible one. - install -Dm644 "${srcdir}/hermes-desktop.desktop" "${pkgdir}/usr/share/applications/hermes-desktop.desktop" + install -Dm644 "${srcdir}/hermes-desktop.desktop" \ + "${pkgdir}/usr/share/applications/hermes-desktop.desktop" printf 'NoDisplay=true\n' >>"${pkgdir}/usr/share/applications/hermes-desktop.desktop" sed '/^MimeType=/d' "${srcdir}/hermes-desktop.desktop" >"${pkgdir}/usr/share/applications/hermes.desktop" - install -Dm644 "${srcdir}/hermes-desktop.png" "${pkgdir}/usr/share/icons/hicolor/1024x1024/apps/${pkgname}.png" + install -Dm644 "${srcdir}/hermes-desktop.png" \ + "${pkgdir}/usr/share/icons/hicolor/1024x1024/apps/${pkgname}.png" local size for size in 512 256 128 64 48; do magick "${srcdir}/hermes-desktop.png" -resize "${size}x${size}" "${srcdir}/icon-${size}.png" - install -Dm644 "${srcdir}/icon-${size}.png" "${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/${pkgname}.png" + install -Dm644 "${srcdir}/icon-${size}.png" \ + "${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/${pkgname}.png" done } From 29c621f83538d1ca7aefc2ce1f256d0550dbc5a7 Mon Sep 17 00:00:00 2001 From: Tyler South Date: Sat, 5 Sep 2026 17:27:44 -0400 Subject: [PATCH 51/63] omakade: update to 1.6.1 with full runtime deps and aarch64 --- pkgbuilds/omakade/.omarchy/package.json | 2 +- pkgbuilds/omakade/PKGBUILD | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkgbuilds/omakade/.omarchy/package.json b/pkgbuilds/omakade/.omarchy/package.json index 584e2235..482bc952 100644 --- a/pkgbuilds/omakade/.omarchy/package.json +++ b/pkgbuilds/omakade/.omarchy/package.json @@ -2,7 +2,7 @@ "source": "local", "release_ring": "fast", "upstream": { - "github": "tsouth89/omakade", + "github": "btsouth/omakade", "checksums": "SHA256SUMS", "assets": { "any": "omakade-{pkgver}.tar.gz" diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 12904bcc..4dc090e0 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,14 +1,14 @@ pkgname=omakade pkgver=1.6.1 -pkgrel=1 +pkgrel=2 pkgdesc='A beautiful, local-first game library for Omarchy' -arch=('x86_64') -url='https://tsouth89.github.io/omakade/' +arch=('x86_64' 'aarch64') +url='https://btsouth.github.io/omakade/' license=('GPL-3.0-or-later') -depends=('glib2' 'libsecret' 'qt6-base' 'qt6-declarative' 'qt6-wayland' 'sdl3') +depends=('glib2' 'hicolor-icon-theme' 'libsecret' 'libzip' 'qt6-base' 'qt6-declarative' 'qt6-imageformats' 'qt6-svg' 'qt6-wayland' 'sdl3') makedepends=('cmake' 'ninja' 'pkgconf') options=('!debug') -source=("$pkgname-$pkgver.tar.gz::https://github.com/tsouth89/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") +source=("$pkgname-$pkgver.tar.gz::https://github.com/btsouth/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") sha256sums=('7de0575fb6a7c16f7e8f88e15b6fa15e8486e924e92d07bf6029c0d94a0887e5') build() { From ca63e47ecdc90ec2f7f48b4e940365ce45d8ff29 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Mon, 7 Sep 2026 01:23:14 -0400 Subject: [PATCH 52/63] Stop auto-syncing retroarch-joypad-autoconfig-git from the AUR The package carries a deliberate commit pin (59d557a) so every architecture packages the same tree. The AUR sync kept trying to revert that to the unpinned branch-tip recipe, recording a version downgrade in the process. Mark it local so exactly one source owns the recipe; it is Omarchy-maintained until it moves to an upstream release feed. --- .../retroarch-joypad-autoconfig-git/.omarchy/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkgbuilds/retroarch-joypad-autoconfig-git/.omarchy/package.json b/pkgbuilds/retroarch-joypad-autoconfig-git/.omarchy/package.json index d2ffaee8..2a9719d3 100644 --- a/pkgbuilds/retroarch-joypad-autoconfig-git/.omarchy/package.json +++ b/pkgbuilds/retroarch-joypad-autoconfig-git/.omarchy/package.json @@ -1,4 +1,3 @@ { - "source": "aur", - "upstream_commit": "80c65c27a9d5511d2b4ce2ca7ad2396ad528f0da" + "source": "local" } From 6dc21c7763d8650eb6d208dcdb16dfe8df07506c Mon Sep 17 00:00:00 2001 From: ryanrhughes <1630358+ryanrhughes@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:26:37 +0000 Subject: [PATCH 53/63] chore: sync AUR packages --- pkgbuilds/basecamp-cli/.omarchy/package.json | 2 +- pkgbuilds/basecamp-cli/PKGBUILD | 10 +++++----- pkgbuilds/claude-code/.omarchy/package.json | 2 +- pkgbuilds/claude-code/PKGBUILD | 6 +++--- pkgbuilds/cursor-bin/.omarchy/package.json | 2 +- pkgbuilds/cursor-bin/PKGBUILD | 6 +++--- pkgbuilds/dropbox-cli/PKGBUILD | 1 + pkgbuilds/omazed/.omarchy/package.json | 2 +- pkgbuilds/omazed/PKGBUILD | 4 ++-- pkgbuilds/omazed/omazed.install | 5 ----- pkgbuilds/openai-codex-bin/.omarchy/package.json | 2 +- pkgbuilds/openai-codex-bin/PKGBUILD | 10 +++++----- 12 files changed, 24 insertions(+), 28 deletions(-) diff --git a/pkgbuilds/basecamp-cli/.omarchy/package.json b/pkgbuilds/basecamp-cli/.omarchy/package.json index 2b88c4f5..0beb986e 100644 --- a/pkgbuilds/basecamp-cli/.omarchy/package.json +++ b/pkgbuilds/basecamp-cli/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "fe2a6345e271f618dc3adf31e48557a717866395" + "upstream_commit": "1e7bd48ef192b3d6a2854916e75e14ea43f6f0b7" } diff --git a/pkgbuilds/basecamp-cli/PKGBUILD b/pkgbuilds/basecamp-cli/PKGBUILD index ba35e3a4..b7ed938c 100644 --- a/pkgbuilds/basecamp-cli/PKGBUILD +++ b/pkgbuilds/basecamp-cli/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Basecamp pkgname=basecamp-cli -pkgver=0.9.1 +pkgver=0.10.0 pkgrel=1 pkgdesc="CLI for Basecamp project management" arch=('x86_64' 'aarch64') @@ -13,10 +13,10 @@ optdepends=( 'zsh: for zsh shell completions' 'fish: for fish shell completions' ) -source_x86_64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.9.1/basecamp_${pkgver}_linux_amd64.tar.gz") -source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.9.1/basecamp_${pkgver}_linux_arm64.tar.gz") -sha256sums_x86_64=('bcb1fa3a03e702bbf81a3004ded4bc7808605e41106cbc768ba4006b89e0db2f') -sha256sums_aarch64=('ff50313024b6ca14e40146e30e1c9c9c00a4e4103fe55615eeee3e7c359d88d1') +source_x86_64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.10.0/basecamp_${pkgver}_linux_amd64.tar.gz") +source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.10.0/basecamp_${pkgver}_linux_arm64.tar.gz") +sha256sums_x86_64=('ca8b3a085f90323c8ab1694b83ff624cf10c459618ca6010ce204534f3f01987') +sha256sums_aarch64=('dd1b5db88b9b309e95ae1ec0d642d23f4955a3abe159bffe00796a0ef6030c1d') package() { install -Dm755 "basecamp" "${pkgdir}/usr/bin/basecamp" diff --git a/pkgbuilds/claude-code/.omarchy/package.json b/pkgbuilds/claude-code/.omarchy/package.json index 78236624..5ce322c2 100644 --- a/pkgbuilds/claude-code/.omarchy/package.json +++ b/pkgbuilds/claude-code/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "6505bc21a7c41ec546cf2fd1a7ffbee9a58b8327" + "upstream_commit": "87aa5d5f2771c83dae737fe1bcfbc129110291fb" } diff --git a/pkgbuilds/claude-code/PKGBUILD b/pkgbuilds/claude-code/PKGBUILD index 0baa2494..ca3c84a6 100644 --- a/pkgbuilds/claude-code/PKGBUILD +++ b/pkgbuilds/claude-code/PKGBUILD @@ -4,7 +4,7 @@ # Automation repository: https://github.com/fabifont/claude-code-aur pkgname=claude-code -pkgver=2.1.259 +pkgver=2.1.263 pkgrel=1 pkgdesc="An agentic coding tool that lives in your terminal" arch=('x86_64' 'aarch64') @@ -29,8 +29,8 @@ source_x86_64=("claude-${pkgver}-x86_64::https://downloads.claude.ai/claude-code source_aarch64=("claude-${pkgver}-aarch64::https://downloads.claude.ai/claude-code-releases/${pkgver}/linux-arm64/claude") sha256sums=('SKIP') -sha256sums_x86_64=('f7dd62ae415378018cd21dd950eb3bac174ab085830304d3b8b098146bfd47b6') -sha256sums_aarch64=('c6ff03c389ccdeae0f19e9dc32488eeba61fbef4796f531dbfba6c00f45040d0') +sha256sums_x86_64=('26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba') +sha256sums_aarch64=('7d25d7c8ae6c6e009cc7dae4e817f674179fd31fb7761bcd56fee4c2902b4c03') package() { install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude" diff --git a/pkgbuilds/cursor-bin/.omarchy/package.json b/pkgbuilds/cursor-bin/.omarchy/package.json index 3cdbb867..8461d51b 100644 --- a/pkgbuilds/cursor-bin/.omarchy/package.json +++ b/pkgbuilds/cursor-bin/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "e796ff912a08aa8fa30b8140ffcacb9d43386fa5" + "upstream_commit": "c5073d62f60f3973a14340eb1b40fc5b5a4ea9f0" } diff --git a/pkgbuilds/cursor-bin/PKGBUILD b/pkgbuilds/cursor-bin/PKGBUILD index 9d1c5c25..14325f67 100644 --- a/pkgbuilds/cursor-bin/PKGBUILD +++ b/pkgbuilds/cursor-bin/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Gunther Schulz pkgname=cursor-bin -pkgver=3.19.7 +pkgver=3.19.13 pkgrel=1 pkgdesc='AI-first coding environment' arch=('x86_64') @@ -12,7 +12,7 @@ _electron=electron42 depends=(xdg-utils ripgrep $_electron nodejs 'gcc-libs' 'hicolor-icon-theme' 'libxkbfile') options=(!strip !debug) # Don't break ext of VSCode -_commit=90de2327392570a5f5f625c656c6749d228e6437 +_commit=dd066f332fcea7382764400fde902f61920648d5 source=("https://downloads.cursor.com/production/${_commit}/linux/x64/deb/amd64/deb/cursor_${pkgver}_amd64.deb" "https://gitlab.archlinux.org/archlinux/packaging/packages/code/-/raw/main/code."{sh,mjs} rg.sh) @@ -20,7 +20,7 @@ sha512sums=('SKIP' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') -sha512sums[0]=f1749bb4824dd5bff10853ba341320ca79b4d606151bc1ad54ccd9fea3c44aa4b838f3d767ba9396a63c15c461c7d1a389f5a1ecbaf26ed64000cae5e981c342 +sha512sums[0]=3c952035fa1809caded7059b7addbb01a9143cd248ecff27cdb3cb3af47d4bc824ed60dfd9757e105a342e53280e075d81f775790295a3272818bab5a994b305 noextract=(cursor_${pkgver}_amd64.deb) # avoid double tarball _app=usr/share/cursor/resources/app package() { diff --git a/pkgbuilds/dropbox-cli/PKGBUILD b/pkgbuilds/dropbox-cli/PKGBUILD index 96f1777b..1372a2cf 100644 --- a/pkgbuilds/dropbox-cli/PKGBUILD +++ b/pkgbuilds/dropbox-cli/PKGBUILD @@ -36,3 +36,4 @@ package() { cd "nautilus-dropbox-${pkgver}" install -m 755 -D "${pkgname}" "${pkgdir}/usr/bin/${pkgname}" } + diff --git a/pkgbuilds/omazed/.omarchy/package.json b/pkgbuilds/omazed/.omarchy/package.json index 9f738bb8..4d2f6593 100644 --- a/pkgbuilds/omazed/.omarchy/package.json +++ b/pkgbuilds/omazed/.omarchy/package.json @@ -1,4 +1,4 @@ { "source": "aur", - "upstream_commit": "e2a85e53e2a73cec9170a5fa631805f62b98bb36" + "upstream_commit": "cbc25a60a791ccda07ed5e8300e8acb267d23007" } diff --git a/pkgbuilds/omazed/PKGBUILD b/pkgbuilds/omazed/PKGBUILD index 8d821897..9557018d 100644 --- a/pkgbuilds/omazed/PKGBUILD +++ b/pkgbuilds/omazed/PKGBUILD @@ -1,5 +1,5 @@ pkgname=omazed -pkgver=2.1.1 +pkgver=2.1.2 pkgrel=1 pkgdesc="Live theme switching for Zed in Omarchy - automatically synchronize your Zed editor theme with your Omarchy system theme" arch=('any') @@ -10,7 +10,7 @@ makedepends=('git') backup=() install=omazed.install source=("$pkgname-$pkgver.tar.gz::https://github.com/aps6/$pkgname/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('083757c5385179f241ac0c6e9dde05ba0c7f465c1fed617779d1ebe2a6dfe036') +sha256sums=('ca0708c86969547b14958a307ae3df89f63d92ffa68af3b5c6bf79c036b8cab9') package() { cd "$srcdir/$pkgname-$pkgver" diff --git a/pkgbuilds/omazed/omazed.install b/pkgbuilds/omazed/omazed.install index 05164413..b0376c67 100644 --- a/pkgbuilds/omazed/omazed.install +++ b/pkgbuilds/omazed/omazed.install @@ -17,11 +17,6 @@ post_upgrade() { echo "" echo "==> Omazed updated to $(pacman -Q omazed | cut -d' ' -f2)" echo "" - echo " Complete the update!" - echo "" - echo " To complete this update, run:" - echo "" - echo " omazed setup" echo "" } diff --git a/pkgbuilds/openai-codex-bin/.omarchy/package.json b/pkgbuilds/openai-codex-bin/.omarchy/package.json index 627b4bdc..75046fbf 100644 --- a/pkgbuilds/openai-codex-bin/.omarchy/package.json +++ b/pkgbuilds/openai-codex-bin/.omarchy/package.json @@ -1,5 +1,5 @@ { "source": "aur", "release_ring": "fast", - "upstream_commit": "4ec833f5a082fc52c771a92c60746ce130234d1f" + "upstream_commit": "134a0e93624d39306f94dc4384dbd102b8d37bb2" } diff --git a/pkgbuilds/openai-codex-bin/PKGBUILD b/pkgbuilds/openai-codex-bin/PKGBUILD index 42f5997f..ef7f8519 100644 --- a/pkgbuilds/openai-codex-bin/PKGBUILD +++ b/pkgbuilds/openai-codex-bin/PKGBUILD @@ -2,7 +2,7 @@ # shellcheck disable=SC2034 # Maintainer: Chmouel Boudjnah pkgname=openai-codex-bin -pkgver=0.153.0 +pkgver=0.153.4 pkgrel=1 pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated" arch=('x86_64' 'aarch64') @@ -21,10 +21,10 @@ source_x86_64=( "codex-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-x86_64-unknown-linux-musl.tar.gz" "codex-code-mode-host-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz" ) -sha256sums_x86_64=('35a82c153d83959de09c2cb84ac70ba69d05788aeeb08d4a95ca68e39f86680e' - '2b817a495e35a53333e94b35af9ed879e180f9b28fd57ef15ace9a857ba86f2c') -sha256sums_aarch64=('cc2c0c365d4d51c18163bba93cce5b922836900ca867f5bcf89b0e714a952a53' - '269d6006fb55c95f876454fc03ed3755c367c1c376b77a985d0029395a5a7736') +sha256sums_x86_64=('f479424eca092484dc40d87ae28c44f4cc40234a60045d6131e493800d814a30' + 'f95830a869590957664bbfc67bccb08773806b693670baf15908176f89b4cd31') +sha256sums_aarch64=('5cda6182bd94c3a30f2eb63a495489ebf7f691fddb14d70f48c6c1a5071b6cde' + 'd8047b8d33370d6090e729d27eb76de60a2686baa1c143c138c9b05dc70d813b') source_aarch64=( "codex-${pkgver}-aarch64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-aarch64-unknown-linux-musl.tar.gz" From ea9f615884a88eb5e979c9d727fa30d10fe2735b Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Mon, 7 Sep 2026 02:38:01 -0400 Subject: [PATCH 54/63] Bump strata to 0.11.2 ahead of its release-age hold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.11.2 is 14 hours old, inside the package's 24h min_release_age, so the upstream sync would not pick it up until tomorrow; ship it now by hand. Tarball commit b000b79 verified against the v0.11.2 tag, checksum from an independent download. The new Cargo dependencies (ashpd, zbus, gdk4-wayland) are vendored crates needing no new system libraries. 0.11.2 also ships FileManager1/portal integration files. Stage the FileManager1 service under /usr/share/strata the way upstream's own PKGBUILDs do — the app copies it per-user on opt-in. The portal files stay unpackaged, matching upstream: enablement is per-user and consent-gated in the app. --- pkgbuilds/strata/PKGBUILD | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/strata/PKGBUILD b/pkgbuilds/strata/PKGBUILD index fae541e9..a9313a45 100644 --- a/pkgbuilds/strata/PKGBUILD +++ b/pkgbuilds/strata/PKGBUILD @@ -1,5 +1,5 @@ pkgname=strata -pkgver=0.10.0 +pkgver=0.11.2 pkgrel=1 pkgdesc='Fast, keyboard-first file manager for modern Linux desktops' arch=('x86_64' 'aarch64') @@ -38,7 +38,7 @@ conflicts=('strata-git') options=('!debug' '!lto') source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz") -sha256sums=('01ea2676a0c59fac5daa0962ed6e721861bafd478b19cf5d9afaab6823175fe8') +sha256sums=('cfc8b1c3a9a5fde518c5862b58af7722e942b5f394c60fd53822dd642c1f30a0') prepare() { cd "$pkgname-$pkgver" @@ -84,6 +84,10 @@ package() { "$pkgdir/usr/share/applications/io.github.lgse.Strata.desktop" install -Dm644 data/icons/scalable/apps/io.github.lgse.Strata.svg \ "$pkgdir/usr/share/icons/hicolor/scalable/apps/io.github.lgse.Strata.svg" + # Payload for the app's per-user FileManager1 opt-in; upstream's own + # packaging stages it here, not in /usr/share/dbus-1. + install -Dm644 data/io.github.lgse.Strata.FileManager1.service \ + "$pkgdir/usr/share/strata/io.github.lgse.Strata.FileManager1.service" install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" install -Dm644 THIRD_PARTY_LICENSES.md \ "$pkgdir/usr/share/licenses/$pkgname/THIRD_PARTY_LICENSES.md" From 3356e8c04ce0f505a5f794fd213fcc3c25be5309 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Mon, 7 Sep 2026 02:01:52 -0500 Subject: [PATCH 55/63] Restore packaged Hermes Desktop at 2026.8.31-2 Revert the native installer and updater packaging introduced by #325. Keep the prebuilt desktop and existing launcher, updating only the release tag, commit and archive checksum from the previous recipe. --- .github/workflows/test.yml | 3 - pkgbuilds/hermes-desktop/PKGBUILD | 107 ++++-- .../hermes-desktop/hermes-desktop.desktop | 2 +- pkgbuilds/hermes-desktop/hermes-desktop.sh | 287 ++------------ test/hermes-desktop-test.py | 349 ------------------ 5 files changed, 107 insertions(+), 641 deletions(-) delete mode 100644 test/hermes-desktop-test.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c359c5c..9fc270f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,9 +16,6 @@ jobs: with: persist-credentials: false - - name: Test Hermes installation and launch - run: python3 test/hermes-desktop-test.py - # An Arch container for vercmp: version ordering has to be decided by # the same comparator pacman uses on users' machines. - name: Run self-tests diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 5783f0b5..e00398a0 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -1,24 +1,38 @@ # Maintainer: David Heinemeier Hansson +# Nous builds Hermes Desktop for macOS and Windows only -- their download page +# offers a .dmg and an .exe and tells Linux users to install from a terminal -- +# so there is no vendor binary to repackage. Their electron-builder config does +# carry a Linux target, though, and it works; this builds it. +# +# The app only runs against a Hermes runtime built from its own commit, so it +# provisions one itself under ~/.hermes on first launch and the package stays +# on the newest tag. Pairing it with the mise CLI instead was tried and does +# not work: PyPI trails the tags, and the version gap fails the readiness probe +# with a 401. Pinning back to the tag behind PyPI's release does not rescue it +# either -- v2026.7.20's desktop hangs after "backend is ready" without ever +# opening a window, against its own matching runtime. +# +# The app is only a shell: it runs `hermes serve` against a Hermes CLI it does +# not ship, and clones its own copy with the upstream install script when it +# finds none. /usr/bin/hermes-desktop heads that off. See hermes-desktop.sh. + pkgname=hermes-desktop pkgver=2026.8.31 -pkgrel=1 -pkgdesc='Installer and launcher for the self-updating Hermes desktop' +pkgrel=2 +pkgdesc='Native desktop shell for Hermes Agent' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' license=('MIT') -# Hermes builds its desktop under the user's runtime so both the app and CLI -# can update it. The compiler and Electron libraries must survive installation. depends=( 'alsa-lib' 'at-spi2-core' - 'bash' 'cairo' - 'curl' 'dbus' 'expat' - 'gcc' + 'curl' + 'gcc-libs' 'gdk-pixbuf2' 'git' 'glib2' @@ -38,49 +52,65 @@ depends=( 'libxfixes' 'libxkbcommon' 'libxrandr' - 'make' 'mesa' - 'nodejs' - 'npm' 'nspr' 'nss' 'pango' - 'python' 'systemd-libs' - 'util-linux' - 'xdg-terminal-exec' 'xdg-utils' ) -makedepends=('imagemagick') +optdepends=('omarchy: installs the Hermes CLI the app needs on first launch') -# Snapshot the bootstrap installer, including upstream's Linux sandbox fixes. -# Only installation uses it; updates belong to the native runtime on main. -_commit=245e48008fa814b3251f50755eb656bd9fb86cb1 -source=("install-${_commit}.sh::https://raw.githubusercontent.com/NousResearch/hermes-agent/${_commit}/scripts/install.sh" - "LICENSE-${_commit}::https://raw.githubusercontent.com/NousResearch/hermes-agent/${_commit}/LICENSE" +# The build runs the repo's own npm workspace install, which fetches Electron +# and rebuilds node-pty against it. +makedepends=('git' 'imagemagick' 'nodejs' 'npm' 'python') + +# Electron bundles prebuilt binaries that stripping corrupts. +options=('!strip' '!debug') + +# The tag's commit. apps/desktop/scripts/write-build-stamp.mjs pins the app's +# first-launch bootstrap to a Hermes commit, and resolves it from $GITHUB_SHA +# before falling back to `git rev-parse`. That fallback is wrong here: makepkg +# builds inside this repository, so git ascends out of srcdir and stamps the +# app with an omarchy-pkgs commit that means nothing upstream. +_commit=29112bef099274229cadff79cdff7bf7b99c4b77 + +_srcdir="hermes-agent-${pkgver}" +source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz" 'hermes-desktop.sh' 'hermes-desktop.desktop' 'hermes-desktop.png') -sha256sums=('5854b15670b51a8daae8f59ddfa917062de9f74be261eb73b4b8d719710f8968' - '821556e6336796450ab852d375117b48a4887e71d255794fd6318d99982a5ab6' - '27040084e691421dc1748e7646ae7ee79d5777e8ee1e1ec7581a68432d4a29c4' - 'c37d4cfa4801eccbd769fddaebb2115d54faa49a5b7bc0f305f563e3cefd9bd8' +sha256sums=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217' + 'f5833b969ce451aadee9f08d92db55cce3c7c9213175080590bf37444854d676' + '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') +build() { + cd "${srcdir}/${_srcdir}" + + export GITHUB_SHA="${_commit}" + export GITHUB_REF_NAME="v${pkgver}" + + # The desktop workspace resolves against the repo root, so the install has to + # happen there rather than in apps/desktop. + npm ci + + cd apps/desktop + npm run pack +} + package() { - install -Dm644 "${srcdir}/install-${_commit}.sh" \ - "${pkgdir}/usr/share/${pkgname}/install.sh" - install -Dm644 "${srcdir}/LICENSE-${_commit}" \ - "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" + cd "${srcdir}/${_srcdir}/apps/desktop/release/linux-unpacked" + + install -dm755 "${pkgdir}/opt/${pkgname}" + cp -a . "${pkgdir}/opt/${pkgname}/" + install -Dm755 "${srcdir}/hermes-desktop.sh" "${pkgdir}/usr/bin/${pkgname}" - # Native registration uses hermes.desktop but omits URI handling. Keep the - # original handler ID hidden, and let the native entry replace the visible one. install -Dm644 "${srcdir}/hermes-desktop.desktop" \ - "${pkgdir}/usr/share/applications/hermes-desktop.desktop" - printf 'NoDisplay=true\n' >>"${pkgdir}/usr/share/applications/hermes-desktop.desktop" - sed '/^MimeType=/d' "${srcdir}/hermes-desktop.desktop" >"${pkgdir}/usr/share/applications/hermes.desktop" + "${pkgdir}/usr/share/applications/${pkgname}.desktop" + install -Dm644 "${srcdir}/hermes-desktop.png" \ "${pkgdir}/usr/share/icons/hicolor/1024x1024/apps/${pkgname}.png" local size @@ -89,4 +119,17 @@ package() { install -Dm644 "${srcdir}/icon-${size}.png" \ "${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/${pkgname}.png" done + + install -Dm644 "${srcdir}/${_srcdir}/LICENSE" \ + "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" + install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \ + "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt" + + # Chromium's setuid sandbox is only needed where unprivileged user namespaces + # are unavailable; where they work, setuid root is the worse of the two. + if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then + chmod 4755 "${pkgdir}/opt/${pkgname}/chrome-sandbox" + else + chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox" + fi } diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.desktop b/pkgbuilds/hermes-desktop/hermes-desktop.desktop index b156fcbb..8186b658 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.desktop +++ b/pkgbuilds/hermes-desktop/hermes-desktop.desktop @@ -4,7 +4,7 @@ Type=Application Name=Hermes GenericName=AI Agent Comment=The self-improving AI agent that grows with you -Exec=/usr/bin/hermes-desktop %U +Exec=hermes-desktop %U Icon=hermes-desktop Terminal=false Categories=Development; diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index fe00939c..5c4d35ef 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -1,266 +1,41 @@ #!/bin/bash set -euo pipefail -die() { - echo "Hermes: $*" >&2 - exit 1 -} +# Hermes Desktop is a shell around a Hermes runtime, and it only works against +# one built from its own commit. A CLI from PyPI is always a different release +# -- PyPI trails the tags -- and the mismatch fails the app's readiness probe +# with 401 Unauthorized. So keep it away from whatever `hermes` is on PATH, +# which on Omarchy is the mise CLI installed for the terminal agent, and let +# the app provision and manage its own runtime under ~/.hermes. That is the +# arrangement upstream ships, and the only one that starts. +export HERMES_DESKTOP_IGNORE_EXISTING=1 -HERMES_HOME=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}") -# A profile session shares its parent home's installation and launchers. -home_parent=${HERMES_HOME%/*} -if [[ ${home_parent##*/} == "profiles" ]]; then - HERMES_HOME=${home_parent%/*} -fi -[[ $HERMES_HOME == /* && $HERMES_HOME != "/" ]] || die "Use a Hermes data directory other than /." -export HERMES_HOME -root="$HERMES_HOME/hermes-agent" -marker="$root/.omarchy-hermes-desktop" -installer=/usr/share/hermes-desktop/install.sh -cli=("$root/venv/bin/python" "$root/hermes") - -desktop_executable() { - local executable - for executable in "$root/apps/desktop/release/linux-unpacked/"{Hermes,hermes}; do - if [[ -f $executable && -x $executable ]]; then - printf '%s\n' "$executable" - return 0 - fi - done - return 1 -} - -native_wrapper() { - local wrapper=$1 entry=$2 suffix=${3:-} - [[ -f $wrapper && ! -L $wrapper ]] && cmp -s "$wrapper" <( - printf '#!/usr/bin/env bash\nunset PYTHONPATH\nunset PYTHONHOME\nexec "%s/venv/bin/python" "%s/%s"%s "$@"\n' "$root" "$root" "$entry" "$suffix" - ) -} - -runtime_ready() { - [[ -f $root/.hermes-bootstrap-complete && -x ${cli[0]} ]] && - [[ -x $HOME/.local/bin/hermes ]] && - native_wrapper "$HOME/.local/bin/hermes" hermes && - desktop_executable >/dev/null && - timeout 15 env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" --version >/dev/null 2>&1 -} - -ready() { - if [[ -f $marker ]]; then - grep -qxF ready "$marker" || return 1 - fi - runtime_ready -} - -restore_launchers() { - local command wrapper entry - for command in hermes hermes-agent hermes-acp; do - [[ -f $install_backup/$command ]] || continue - wrapper="$HOME/.local/bin/$command" - entry=hermes - [[ $command == "hermes-agent" ]] && entry=run_agent.py - if [[ ! -e $wrapper && ! -L $wrapper ]] || native_wrapper "$wrapper" "$entry" || - { [[ -f $wrapper && ! -L $wrapper ]] && grep -qxF '# Written by omarchy-install-hermes-cli.' "$wrapper"; } || - { [[ $command == "hermes-acp" ]] && native_wrapper "$wrapper" hermes ' acp'; }; then - cp -p "$install_backup/$command" "$wrapper" - else - echo "Keeping changed $wrapper; its original is in $install_backup." >&2 - return - fi - done - rm -rf "$install_backup" -} - -stage() { - bash "$installer" --dir "${2:-$root}" --branch main --non-interactive --stage "$1" -} - -install_desktop() { - (( EUID != 0 )) || die "Run this installer as your desktop user, without sudo." - mkdir -p "$HERMES_HOME" - exec 9>"$HERMES_HOME/.omarchy-hermes-desktop.lock" - flock -n 9 || die "Another Hermes installation is already running." - if ready; then - exec 9>&- - return - fi - - # Upstream replaces these launchers. Admit its exact shims or Omarchy's - # marked predecessor before starting work, never a user's custom command. - local command wrapper entry - for command in hermes hermes-agent hermes-acp; do - wrapper="$HOME/.local/bin/$command" - [[ -e $wrapper || -L $wrapper ]] || continue - entry=hermes - [[ $command == "hermes-agent" ]] && entry=run_agent.py - if native_wrapper "$wrapper" "$entry"; then continue; fi - if [[ $command == "hermes-acp" ]] && native_wrapper "$wrapper" hermes ' acp'; then continue; fi - if [[ $command == "hermes" && -f $wrapper && ! -L $wrapper ]] && - grep -qxF '# Written by omarchy-install-hermes-cli.' "$wrapper"; then continue; fi - die "Keeping the existing $wrapper. Move or update that installation before installing Hermes Desktop." - done - - # The native prerequisite stage may install Node's three user symlinks. - for command in node npm npx; do - wrapper="$HOME/.local/bin/$command" - if [[ -e $wrapper || -L $wrapper ]] && - [[ ! -L $wrapper || $(readlink "$wrapper") != "$HERMES_HOME/node/bin/$command" ]]; then - die "Keeping the existing $wrapper. Move it before installing Hermes Desktop." - fi - done - - local legacy=false - if [[ -e $root || -L $root ]]; then - [[ -d $root/.git && ! -L $root && ! -L $root/.git ]] || die "Keeping $root; it is not a managed checkout." - if [[ -f $marker ]] && grep -qxF updating "$marker"; then - legacy=true - elif [[ ! -f $marker ]]; then - python -I - "$root/.hermes-bootstrap-complete" <<'PY' || die "Keeping the existing checkout; install its desktop with 'hermes desktop'." -import json, sys -try: - stamp = json.load(open(sys.argv[1])) - assert stamp.get('desktopVersion') and stamp.get('pinnedCommit') in { - 'e624e9fde561e1add9388384012b295fde669ade', - '29112bef099274229cadff79cdff7bf7b99c4b77', - } -except (OSError, ValueError, AssertionError, AttributeError): - sys.exit(1) -PY - legacy=true - fi - [[ -z $(git -C "$root" status --porcelain) ]] || die "Keeping local changes in $root. Save them before installing the desktop." - case "$(git -C "$root" remote get-url origin)" in - https://github.com/NousResearch/hermes-agent.git | git@github.com:NousResearch/hermes-agent.git) ;; - *) die "Keeping the checkout's custom origin. Install its desktop with 'hermes desktop'." ;; - esac - if [[ $legacy == true ]]; then - case "$(git -C "$root" rev-parse HEAD)" in - e624e9fde561e1add9388384012b295fde669ade | 29112bef099274229cadff79cdff7bf7b99c4b77) ;; - *) - if [[ $(git -C "$root" symbolic-ref --short -q HEAD) != "main" ]] || - ! git -C "$root" merge-base --is-ancestor HEAD refs/remotes/origin/main; then - die "The legacy checkout has moved. Run 'hermes update', then 'hermes desktop'." - fi - ;; - esac - fi - fi - - install_backup=$(mktemp -d "$HERMES_HOME/.omarchy-hermes-launchers.XXXXXX") - for command in hermes hermes-agent hermes-acp; do - wrapper="$HOME/.local/bin/$command" - if [[ -f $wrapper ]]; then cp -p "$wrapper" "$install_backup/$command"; fi - done - trap restore_launchers EXIT - trap 'exit 130' INT - trap 'exit 143' TERM - - local mise_predecessor=false - if [[ -f $install_backup/hermes ]] && grep -qxF '# Written by omarchy-install-hermes-cli.' "$install_backup/hermes"; then - mise_predecessor=true - fi - - if [[ $legacy == true ]]; then - if [[ $mise_predecessor == true ]]; then - printf '%s\n' 'pipx:hermes-agent[extras=all]' >"$root/.git/omarchy-mise-predecessor" - fi - if ! grep -qxF '/.omarchy-hermes-desktop' "$root/.git/info/exclude"; then - printf '/.omarchy-hermes-desktop\n' >>"$root/.git/info/exclude" - fi - printf 'updating\n' >"$marker" - local refspec='+refs/heads/main:refs/remotes/origin/main' - if ! git -C "$root" config --get-all remote.origin.fetch | grep -qxF "$refspec"; then - git -C "$root" config --add remote.origin.fetch "$refspec" - fi - git -C "$root" fetch origin "$refspec" - env -u PYTHONPATH -u PYTHONHOME "${cli[@]}" update --yes --branch main - elif [[ ! -e $root ]]; then - stage prerequisites - # Publish only a complete clone with its ownership marker. An interrupted - # clone stays aside for inspection and cannot block the next installation. - local repository_dir - repository_dir=$(mktemp -d "$HERMES_HOME/.omarchy-hermes-repository.XXXXXX") - echo "Preparing the Hermes checkout in $repository_dir" - stage repository "$repository_dir/hermes-agent" - printf '/.omarchy-hermes-desktop\n' >>"$repository_dir/hermes-agent/.git/info/exclude" - printf 'pending\n' >"$repository_dir/hermes-agent/.omarchy-hermes-desktop" - mv -T --no-clobber "$repository_dir/hermes-agent" "$root" - [[ ! -e $repository_dir/hermes-agent ]] || die "Keeping the checkout that appeared at $root during installation." - rmdir "$repository_dir" - fi - - if [[ $mise_predecessor == true ]]; then - printf '%s\n' 'pipx:hermes-agent[extras=all]' >"$root/.git/omarchy-mise-predecessor" - fi - - if [[ $legacy == false ]]; then - for command in venv python-deps node-deps config; do stage "$command"; done - fi - # The native builder also handles updates and supports the namespace - # sandbox. The shell installer's desktop stage still requires a sudo chown. - # The build registers a desktop entry, too. Keep it private until the new - # CLI is published so a different hermes on PATH cannot become its target. - env -u PYTHONPATH -u PYTHONHOME XDG_DATA_HOME="$install_backup/desktop" "${cli[@]}" desktop --build-only - - # Build first: a failed download leaves the old terminal CLI usable. - stage complete - stage path - runtime_ready || die "The CLI or desktop is not ready. See the installer output above." - env -u PYTHONPATH -u PYTHONHOME PATH="$HOME/.local/bin:$PATH" "${cli[@]}" desktop --skip-build --build-only - if ! grep -qxF '/.omarchy-hermes-desktop' "$root/.git/info/exclude"; then - printf '/.omarchy-hermes-desktop\n' >>"$root/.git/info/exclude" - fi - printf 'ready\n' >"$marker" - trap - EXIT INT TERM - rm -rf "$install_backup" - exec 9>&- - if [[ -f $root/.git/omarchy-mise-predecessor ]] && command -v omarchy-install-hermes-cli >/dev/null 2>&1; then - omarchy-install-hermes-cli || echo 'Run omarchy-install-hermes-cli again to finish the old CLI cleanup.' >&2 - fi - echo "Hermes is ready. Update it in the app or with 'hermes update'." -} - -case "${1:-}" in - --check) ready; exit ;; - --install) install_desktop; exit ;; - --setup) - shift - if /usr/bin/hermes-desktop --install; then - setsid --fork /usr/bin/hermes-desktop "$@" >/dev/null 2>&1 - exit - else - status=$? - echo "Hermes installation failed. Retry with: hermes-desktop --install" >&2 - if [[ -t 0 ]]; then read -r -p 'Press Enter to close.'; fi - exit "$status" - fi - ;; -esac - -if ! ready; then - if [[ -t 0 && -t 1 ]]; then - install_desktop - else - exec xdg-terminal-exec /usr/bin/hermes-desktop --setup "$@" - fi -fi - -export HERMES_DESKTOP_HERMES_ROOT="$root" +# Chromium cannot reliably infer the Secret Service password-store backend +# from a Hyprland session, even when GNOME Keyring is already providing it. +# Keep an explicit user choice (such as KWallet), otherwise select the +# libsecret backend that this package depends on. export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}" -# Upstream desktop registration resolves its launcher through PATH. -export PATH="$HOME/.local/bin:$PATH" -unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME -flags=() -if [[ -n ${WAYLAND_DISPLAY:-} || ${XDG_SESSION_TYPE:-} == "wayland" ]]; then - flags=(--ozone-platform=wayland) +# Reconcile every launch rather than trusting whatever installed us. A plain +# `pacman -S hermes-desktop`, or an install interrupted partway, leaves any +# Hermes the terminal agent had built still sitting there, and by then the +# menu entry that would have tidied it up is disabled because we are present. +if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then + omarchy-install-hermes-cli >/dev/null 2>&1 || true +fi + +# Chromium's own Ozone detection falls back to XWayland often enough to matter, +# and the result is a blurry window on every scaled display. Ask for Wayland +# directly, unless the user has already picked a platform themselves. +platform_flags=() +if [[ -n "${WAYLAND_DISPLAY:-}" || ${XDG_SESSION_TYPE:-} == wayland ]]; then + platform_flags=(--ozone-platform=wayland) + for flag in "$@"; do - case "$flag" in --ozone-platform=* | --ozone-platform-hint=*) flags=() ;; esac + case "$flag" in + --ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; + esac done fi -# Disable only the unusable setuid helper; Chromium keeps its namespace sandbox. -if unshare --user --map-root-user true 2>/dev/null; then - flags+=(--disable-setuid-sandbox) -fi -exec "$(desktop_executable)" "${flags[@]}" "$@" +exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@" diff --git a/test/hermes-desktop-test.py b/test/hermes-desktop-test.py deleted file mode 100644 index d4f60c28..00000000 --- a/test/hermes-desktop-test.py +++ /dev/null @@ -1,349 +0,0 @@ -#!/usr/bin/env python3 -"""Exercise the packaged entry point with a disposable HOME and installer.""" -import json -import os -import pty -import signal -import time -from pathlib import Path -import subprocess -import tempfile -import unittest - -SOURCE = Path(__file__).resolve().parents[1] / 'pkgbuilds/hermes-desktop/hermes-desktop.sh' -INSTALLER = r'''#!/bin/bash -set -eu -while (( $# )); do - case $1 in --dir) root=$2; shift;; --stage) stage=$2; shift;; esac - shift -done -printf '%s\n' "$stage" >> "$TEST_LOG" -[[ ${FAIL_STAGE:-} != "$stage" ]] || exit 42 -case $stage in -repository) - mkdir -p "$root/venv/bin" - git init -q "$root" - [[ ${FAIL_PARTIAL_CLONE:-} != 1 ]] || exit 44 - git -C "$root" remote add origin https://github.com/NousResearch/hermes-agent.git - touch "$root/hermes" - cp "$TEST_PYTHON" "$root/venv/bin/python" - git -C "$root" add hermes venv - git -C "$root" -c user.email=test@example.com -c user.name=Test commit -qm initial - if [[ ${RACE_CLONE:-} == 1 ]]; then - mkdir -p "$HOME/.hermes/hermes-agent" - printf 'foreign checkout\n' > "$HOME/.hermes/hermes-agent/keep" - fi - ;; -complete) touch "$root/.hermes-bootstrap-complete";; -path) - mkdir -p "$HOME/.local/bin" - for command in hermes hermes-agent hermes-acp; do - entry=hermes; suffix='' - [[ $command != hermes-agent ]] || entry=run_agent.py - [[ $command != hermes-acp ]] || suffix=' acp' - printf '#!/usr/bin/env bash\nunset PYTHONPATH\nunset PYTHONHOME\nexec "%s/venv/bin/python" "%s/%s"%s "$@"\n' "$root" "$root" "$entry" "$suffix" > "$HOME/.local/bin/$command" - chmod +x "$HOME/.local/bin/$command" - done - [[ ${FAIL_AFTER_PATH:-} != 1 ]] || exit 43 - ;; -esac -''' - -class LauncherTests(unittest.TestCase): - def setUp(self): - self.temp = tempfile.TemporaryDirectory(prefix='hermes-package-') - self.addCleanup(self.temp.cleanup) - self.base = Path(self.temp.name) - self.home = self.base / 'home with spaces' - self.home.mkdir() - self.root = self.home / '.hermes/hermes-agent' - self.bin = self.base / 'bin' - self.bin.mkdir() - self.log = self.base / 'stages' - self.output = self.base / 'launch.json' - self.env = dict(os.environ, HOME=str(self.home), HERMES_HOME=str(self.home / '.hermes'), - XDG_DATA_HOME=str(self.home / '.local/share'), - XDG_CONFIG_HOME=str(self.home / '.config'), XDG_CACHE_HOME=str(self.home / '.cache'), - PATH=f'{self.bin}:/usr/bin:/bin', TEST_LOG=str(self.log), - TEST_GUI=str(self.base / 'gui'), TEST_PYTHON=str(self.base / 'python'), - TEST_OUTPUT=str(self.output), GIT_CONFIG_NOSYSTEM='1', - GIT_CONFIG_GLOBAL='/dev/null') - for k in ['FAIL_STAGE', 'FAIL_AFTER_PATH', 'PYTHONHOME', 'PYTHONPATH']: - self.env.pop(k, None) - self.write(self.base / 'installer', INSTALLER) - self.write(self.base / 'python', '''#!/bin/bash -set -eu -if [[ ${2:-} == desktop ]]; then - root=$(dirname "$1") - if [[ ${3:-} == --skip-build ]]; then - [[ ${4:-} == --build-only && -x $root/apps/desktop/release/linux-unpacked/Hermes ]] - [[ ${FAIL_FINAL_REGISTER:-} != 1 ]] || exit 45 - else - [[ ${3:-} == --build-only ]] - printf 'desktop\\n' >> "$TEST_LOG" - [[ ${FAIL_STAGE:-} != desktop ]] || exit 42 - mkdir -p "$root/apps/desktop/release/linux-unpacked" - cp "$TEST_GUI" "$root/apps/desktop/release/linux-unpacked/Hermes" - printf '/apps/\\n/.hermes-bootstrap-complete\\n' >> "$root/.git/info/exclude" - fi - entry_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications" - mkdir -p "$entry_dir" - printf 'Exec=%s desktop\\n' "$(command -v hermes || printf '%s/venv/bin/python -m hermes_cli.main' "$root")" > "$entry_dir/hermes.desktop" -else - [[ ${FAIL_READY:-} != 1 ]] -fi -''') - self.write(self.base / 'gui', '''#!/usr/bin/env python3 -import json,os,sys,time -json.dump({'pid':os.getpid(),'args':sys.argv[1:],'root':os.environ['HERMES_DESKTOP_HERMES_ROOT'],'home':os.environ['HERMES_HOME'],'node':os.environ.get('ELECTRON_RUN_AS_NODE'),'password':os.environ['HERMES_DESKTOP_PASSWORD_STORE']},open(os.environ['TEST_OUTPUT'],'w')) -if os.environ.get('TEST_GUI_WAIT'): time.sleep(30) -''') - self.write(self.bin / 'unshare', '#!/bin/bash\nexit 0\n') - self.write(self.bin / 'xdg-terminal-exec', '#!/bin/bash\nprintf "%s\\n" "$@" > "$TEST_OUTPUT"\n') - source = SOURCE.read_text().replace('/usr/share/hermes-desktop/install.sh', str(self.base / 'installer')) - source = source.replace('/usr/bin/hermes-desktop', str(self.base / 'launcher')) - self.write(self.base / 'launcher', source) - - def write(self, path, text): - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(text) - path.chmod(0o755) - - def run_launcher(self, *args, ok=True, **env): - result = subprocess.run(['bash', str(self.base / 'launcher'), *args], env=self.env | env, - text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - self.assertEqual(result.returncode == 0, ok, result.stdout) - return result - - def install(self): - self.run_launcher('--install') - self.run_launcher('--check') - - def test_fresh_install_publishes_cli_after_desktop(self): - self.install() - stages = self.log.read_text().splitlines() - self.assertLess(stages.index('desktop'), stages.index('path')) - self.assertEqual((self.root / '.omarchy-hermes-desktop').read_text(), 'ready\n') - self.assertEqual(subprocess.check_output(['git','-C',str(self.root),'status','--porcelain'],env=self.env),b'') - - def test_profile_setup_installs_in_machine_home(self): - profile = self.home / '.hermes/profiles/coder' - self.write(profile / 'config.yaml', 'profile data\n') - self.env['HERMES_HOME'] = str(profile) + '/./' - self.install() - self.assertEqual((self.root / '.omarchy-hermes-desktop').read_text(), 'ready\n') - self.assertEqual(list(profile.iterdir()), [profile / 'config.yaml']) - self.assertEqual((profile / 'config.yaml').read_text(), 'profile data\n') - - def test_profile_warm_check_install_and_launch_share_machine_runtime(self): - self.install() - before = self.log.read_bytes() - self.env['HERMES_HOME'] = str(self.home / '.hermes/profiles/coder') - self.run_launcher('--check') - self.run_launcher('--install') - self.run_launcher('hermes://profile') - observed = json.loads(self.output.read_text()) - self.assertEqual(observed['root'], str(self.root)) - self.assertEqual(observed['home'], str(self.root.parent)) - self.assertEqual(self.log.read_bytes(), before) - - def test_custom_home_profile_uses_its_own_parent_root(self): - custom_home = self.base / 'custom data' - self.root = custom_home / 'hermes-agent' - self.env['HERMES_HOME'] = str(custom_home / 'profiles/coder') - self.install() - self.run_launcher() - observed = json.loads(self.output.read_text()) - self.assertEqual(observed['root'], str(self.root)) - self.assertEqual(observed['home'], str(custom_home)) - self.assertFalse((self.home / '.hermes').exists()) - self.assertFalse((custom_home / 'profiles').exists()) - - def test_custom_home_with_profiles_ancestor_is_preserved(self): - custom_home = self.base / 'profiles/team/custom data' - self.root = custom_home / 'hermes-agent' - self.env['HERMES_HOME'] = str(custom_home) - self.install() - self.run_launcher() - observed = json.loads(self.output.read_text()) - self.assertEqual(observed['root'], str(self.root)) - self.assertEqual(observed['home'], str(custom_home)) - - def test_profile_resolution_preserves_symlink_home_spelling(self): - target = self.base / 'physical data' - target.mkdir() - alias = self.base / 'data alias' - alias.symlink_to(target, target_is_directory=True) - self.root = alias / 'hermes-agent' - self.env['HERMES_HOME'] = str(alias / 'profiles/coder') - self.install() - self.run_launcher() - observed = json.loads(self.output.read_text()) - self.assertEqual(observed['root'], str(self.root)) - self.assertEqual(observed['home'], str(alias)) - self.assertTrue(alias.is_symlink()) - - def test_filesystem_root_home_is_rejected_before_mutation(self): - self.write(self.bin / 'mkdir', '#!/bin/bash\nprintf called >> "$TEST_LOG"\nexit 42\n') - for home in ['/', '/profiles/coder', '/profiles/coder/./']: - with self.subTest(home=home): - result = self.run_launcher('--install', ok=False, HERMES_HOME=home) - self.assertIn('Use a Hermes data directory other than /.', result.stdout) - self.assertFalse(self.log.exists()) - - def test_interrupted_clone_stays_aside_and_retry_succeeds(self): - self.run_launcher('--install', ok=False, FAIL_PARTIAL_CLONE='1') - self.assertFalse(self.root.exists()) - partials=list((self.home/'.hermes').glob('.omarchy-hermes-repository.*/hermes-agent/.git')) - self.assertEqual(len(partials),1) - self.install() - self.assertTrue(partials[0].is_dir()) - - def test_clone_publication_preserves_concurrent_checkout(self): - self.run_launcher('--install', ok=False, RACE_CLONE='1') - self.assertEqual((self.root/'keep').read_text(),'foreign checkout\n') - self.assertFalse((self.root/'.omarchy-hermes-desktop').exists()) - self.assertFalse((self.home/'.local/bin/hermes').exists()) - - def test_desktop_registration_uses_published_cli(self): - self.write(self.bin/'hermes', '#!/bin/bash\necho foreign\n') - self.install() - entry=self.home/'.local/share/applications/hermes.desktop' - self.assertEqual(entry.read_text(),f'Exec={self.home}/.local/bin/hermes desktop\n') - - def test_failed_registration_preserves_previous_launcher(self): - wrapper=self.home/'.local/bin/hermes' - self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') - entry=self.home/'.local/share/applications/hermes.desktop' - self.write(entry,'previous desktop entry\n') - before=wrapper.read_bytes() - self.run_launcher('--install',ok=False,FAIL_FINAL_REGISTER='1') - self.assertEqual(wrapper.read_bytes(),before) - self.assertEqual(entry.read_text(),'previous desktop entry\n') - self.run_launcher('--check',ok=False) - self.install() - - def test_warm_launch_and_package_reinstall_leave_runtime_untouched(self): - self.install() - before = self.log.read_bytes() - self.run_launcher('--install') - self.run_launcher('hermes://test?a=b', 'two words', WAYLAND_DISPLAY='wayland-1', ELECTRON_RUN_AS_NODE='1') - observed = json.loads(self.output.read_text()) - self.assertEqual(observed['args'], ['--ozone-platform=wayland','--disable-setuid-sandbox','hermes://test?a=b','two words']) - self.assertEqual(observed['root'],str(self.root)) - self.assertIsNone(observed['node']) - self.assertEqual(observed['password'],'gnome-libsecret') - self.assertEqual(before,self.log.read_bytes()) - - def test_explicit_platform_and_password_are_preserved(self): - self.install() - self.run_launcher('--ozone-platform=x11', WAYLAND_DISPLAY='wayland-1', HERMES_DESKTOP_PASSWORD_STORE='kwallet6') - result=json.loads(self.output.read_text()) - self.assertNotIn('--ozone-platform=wayland',result['args']) - self.assertEqual(result['password'],'kwallet6') - - def test_foreign_command_and_symlink_are_preserved(self): - wrapper=self.home/'.local/bin/hermes' - self.write(wrapper,'#!/bin/bash\necho custom\n') - before=wrapper.read_bytes() - self.run_launcher('--install',ok=False) - self.assertEqual(before,wrapper.read_bytes()) - self.assertFalse(self.log.exists()) - wrapper.unlink() - wrapper.symlink_to(self.base/'missing') - self.run_launcher('--install',ok=False) - self.assertTrue(wrapper.is_symlink()) - - def test_customized_native_wrapper_is_preserved(self): - self.install() - wrapper=self.home/'.local/bin/hermes' - wrapper.write_text(wrapper.read_text().replace('unset PYTHONPATH','export CUSTOM=yes\nunset PYTHONPATH')) - before=wrapper.read_bytes() - self.run_launcher('--install',ok=False) - self.assertEqual(wrapper.read_bytes(),before) - - def test_failed_build_preserves_old_cli_and_retry_succeeds(self): - wrapper=self.home/'.local/bin/hermes' - self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') - before=wrapper.read_bytes() - self.run_launcher('--install',ok=False,FAIL_STAGE='desktop') - self.assertEqual(wrapper.read_bytes(),before) - self.run_launcher('--check',ok=False) - self.install() - self.assertNotEqual(wrapper.read_bytes(),before) - - def test_late_path_failure_restores_old_cli(self): - wrapper=self.home/'.local/bin/hermes' - self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') - before=wrapper.read_bytes() - self.run_launcher('--install',ok=False,FAIL_AFTER_PATH='1') - self.assertEqual(wrapper.read_bytes(),before) - self.run_launcher('--check',ok=False) - self.install() - - def test_dirty_pending_checkout_is_preserved(self): - self.run_launcher('--install',ok=False,FAIL_STAGE='desktop') - (self.root/'hermes').write_text('user changes') - before=self.log.read_bytes() - self.run_launcher('--install',ok=False) - self.assertEqual((self.root/'hermes').read_text(),'user changes') - self.assertEqual(self.log.read_bytes(),before) - - def test_cold_graphical_launch_opens_visible_setup(self): - self.run_launcher('hermes://open') - self.assertEqual(self.output.read_text().splitlines(),[str(self.base/'launcher'),'--setup','hermes://open']) - self.assertFalse(self.log.exists()) - - def test_unexecutable_native_cli_is_repaired(self): - self.install() - wrapper=self.home/'.local/bin/hermes' - wrapper.chmod(0o644) - self.run_launcher('--check',ok=False) - self.install() - self.assertTrue(os.access(wrapper,os.X_OK)) - - def test_old_cli_ownership_survives_package_only_setup(self): - wrapper=self.home/'.local/bin/hermes' - self.write(wrapper,'#!/bin/bash\n# Written by omarchy-install-hermes-cli.\necho old\n') - self.install() - receipt=self.root/'.git/omarchy-mise-predecessor' - self.assertEqual(receipt.read_text(),'pipx:hermes-agent[extras=all]\n') - - def test_graphical_setup_detaches_and_releases_install_lock(self): - started=time.monotonic() - self.run_launcher('--setup', 'hermes://open', TEST_GUI_WAIT='1') - self.assertLess(time.monotonic()-started,5) - for _ in range(100): - if self.output.exists(): break - time.sleep(.02) - observed=json.loads(self.output.read_text()) - self.addCleanup(lambda: os.kill(observed['pid'],signal.SIGTERM)) - self.assertIn('hermes://open',observed['args']) - self.run_launcher('--install') - lock=self.home/'.hermes/.omarchy-hermes-desktop.lock' - self.assertEqual(subprocess.run(['flock','--nonblock',str(lock),'true']).returncode,0) - - def test_cold_terminal_launch_releases_lock_before_exec(self): - master,slave=pty.openpty() - process=subprocess.Popen(['bash',str(self.base/'launcher')],stdin=slave,stdout=slave,stderr=slave,env=self.env | {'TEST_GUI_WAIT':'1'}) - os.close(slave) - def cleanup(): - if process.poll() is None: process.terminate() - process.wait(timeout=5) - os.close(master) - self.addCleanup(cleanup) - for _ in range(100): - if self.output.exists(): break - time.sleep(.02) - self.assertTrue(self.output.exists()) - self.assertIsNone(process.poll()) - lock=self.home/'.hermes/.omarchy-hermes-desktop.lock' - self.assertEqual(subprocess.run(['flock','--nonblock',str(lock),'true']).returncode,0) - - def test_pending_marker_overrides_old_completion(self): - self.install() - (self.root/'.omarchy-hermes-desktop').write_text('pending\n') - self.run_launcher('--check',ok=False) - -if __name__=='__main__': - unittest.main() From 9588b28cf6459e65f2e4d930e641bac95031ae1a Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Mon, 7 Sep 2026 02:55:06 -0500 Subject: [PATCH 56/63] Prepare packaged Hermes for native in-app updates Track main while pinning the initial release commit. Ship the matching upstream installer and Linux namespace sandbox backport, and launch the native user build prepared by Omarchy. Co-Authored-By: GPT-6 Codex (xhigh) --- pkgbuilds/hermes-desktop/PKGBUILD | 53 ++++++++------- pkgbuilds/hermes-desktop/hermes-desktop.sh | 35 +++++++--- pkgbuilds/hermes-desktop/runtime-test.py | 71 +++++++++++++++++++ pkgbuilds/hermes-desktop/runtime.patch | 79 ++++++++++++++++++++++ 4 files changed, 206 insertions(+), 32 deletions(-) create mode 100644 pkgbuilds/hermes-desktop/runtime-test.py create mode 100644 pkgbuilds/hermes-desktop/runtime.patch diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index e00398a0..29c9c28c 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -1,25 +1,11 @@ # Maintainer: David Heinemeier Hansson -# Nous builds Hermes Desktop for macOS and Windows only -- their download page -# offers a .dmg and an .exe and tells Linux users to install from a terminal -- -# so there is no vendor binary to repackage. Their electron-builder config does -# carry a Linux target, though, and it works; this builds it. -# -# The app only runs against a Hermes runtime built from its own commit, so it -# provisions one itself under ~/.hermes on first launch and the package stays -# on the newest tag. Pairing it with the mise CLI instead was tried and does -# not work: PyPI trails the tags, and the version gap fails the readiness probe -# with a 401. Pinning back to the tag behind PyPI's release does not rescue it -# either -- v2026.7.20's desktop hangs after "backend is ready" without ever -# opening a window, against its own matching runtime. -# -# The app is only a shell: it runs `hermes serve` against a Hermes CLI it does -# not ship, and clones its own copy with the upstream install script when it -# finds none. /usr/bin/hermes-desktop heads that off. See hermes-desktop.sh. +# Build a prebuilt release; Omarchy seeds it into the user's Hermes checkout +# so the upstream updater can rebuild and relaunch it in place. pkgname=hermes-desktop pkgver=2026.8.31 -pkgrel=2 +pkgrel=3 pkgdesc='Native desktop shell for Hermes Agent' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' @@ -32,7 +18,7 @@ depends=( 'dbus' 'expat' 'curl' - 'gcc-libs' + 'gcc' 'gdk-pixbuf2' 'git' 'glib2' @@ -52,19 +38,24 @@ depends=( 'libxfixes' 'libxkbcommon' 'libxrandr' + 'make' 'mesa' + 'nodejs' + 'npm' 'nspr' 'nss' 'pango' + 'python' 'systemd-libs' + 'util-linux' 'xdg-utils' ) -optdepends=('omarchy: installs the Hermes CLI the app needs on first launch') +optdepends=('omarchy: sets up the user installation for in-app updates') # The build runs the repo's own npm workspace install, which fetches Electron # and rebuilds node-pty against it. -makedepends=('git' 'imagemagick' 'nodejs' 'npm' 'python') +makedepends=('imagemagick') # Electron bundles prebuilt binaries that stripping corrupts. options=('!strip' '!debug') @@ -80,17 +71,21 @@ _srcdir="hermes-agent-${pkgver}" source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz" 'hermes-desktop.sh' 'hermes-desktop.desktop' - 'hermes-desktop.png') + 'hermes-desktop.png' + 'runtime.patch' + 'runtime-test.py') sha256sums=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217' - 'f5833b969ce451aadee9f08d92db55cce3c7c9213175080590bf37444854d676' + '93540bbd8e3fccd132546c3e8f7da16eb04e67c20bbfe84034a2736c00e6d49d' '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' - 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') + 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52' + 'cd544605f3b6ed397a829244e4f4d08f2d28a3a2f56edff7d3287b55bdf606b2' + '99caf1ef09c3ac88892ee674ebe92faa916c225fb938fe6796cf04b4e4ac397f') build() { cd "${srcdir}/${_srcdir}" export GITHUB_SHA="${_commit}" - export GITHUB_REF_NAME="v${pkgver}" + export GITHUB_REF_NAME="main" # The desktop workspace resolves against the repo root, so the install has to # happen there rather than in apps/desktop. @@ -100,6 +95,10 @@ build() { npm run pack } +check() { + python "${srcdir}/runtime-test.py" "${srcdir}/${_srcdir}" "${srcdir}/runtime.patch" +} + package() { cd "${srcdir}/${_srcdir}/apps/desktop/release/linux-unpacked" @@ -108,6 +107,12 @@ package() { install -Dm755 "${srcdir}/hermes-desktop.sh" "${pkgdir}/usr/bin/${pkgname}" + install -Dm644 "${srcdir}/${_srcdir}/scripts/install.sh" \ + "${pkgdir}/usr/share/${pkgname}/install.sh" + # Backport the upstream user-namespace fixes to this release's user runtime. + install -Dm644 "${srcdir}/runtime.patch" "${pkgdir}/usr/share/${pkgname}/runtime.patch" + + install -Dm644 "${srcdir}/hermes-desktop.desktop" \ "${pkgdir}/usr/share/applications/${pkgname}.desktop" diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 5c4d35ef..611b04f5 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -1,13 +1,7 @@ #!/bin/bash set -euo pipefail -# Hermes Desktop is a shell around a Hermes runtime, and it only works against -# one built from its own commit. A CLI from PyPI is always a different release -# -- PyPI trails the tags -- and the mismatch fails the app's readiness probe -# with 401 Unauthorized. So keep it away from whatever `hermes` is on PATH, -# which on Omarchy is the mise CLI installed for the terminal agent, and let -# the app provision and manage its own runtime under ~/.hermes. That is the -# arrangement upstream ships, and the only one that starts. +# Use the runtime prepared by Omarchy rather than a separate CLI on PATH. export HERMES_DESKTOP_IGNORE_EXISTING=1 # Chromium cannot reliably infer the Secret Service password-store backend @@ -38,4 +32,29 @@ if [[ -n "${WAYLAND_DISPLAY:-}" || ${XDG_SESSION_TYPE:-} == wayland ]]; then done fi -exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@" +hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}") +parent=${hermes_home%/*} +if [[ ${parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then + hermes_home=${parent%/*} + hermes_home=${hermes_home:-/} +fi +export HERMES_HOME="$hermes_home" +runtime="$hermes_home/hermes-agent" +native="$runtime/apps/desktop/release/linux-unpacked/Hermes" + +if [[ -x $native && -x $runtime/venv/bin/hermes ]]; then + if (( $# == 0 )); then + if (( ${#platform_flags[@]} )); then + export ELECTRON_OZONE_PLATFORM_HINT="${ELECTRON_OZONE_PLATFORM_HINT:-wayland}" + fi + exec "$runtime/venv/bin/hermes" desktop --skip-build + else + # The upstream CLI does not accept Electron arguments or hermes:// URLs. + if unshare --user --map-root-user true 2>/dev/null; then + platform_flags+=(--disable-setuid-sandbox) + fi + exec "$native" "${platform_flags[@]}" "$@" + fi +else + exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@" +fi diff --git a/pkgbuilds/hermes-desktop/runtime-test.py b/pkgbuilds/hermes-desktop/runtime-test.py new file mode 100644 index 00000000..d4987bfb --- /dev/null +++ b/pkgbuilds/hermes-desktop/runtime-test.py @@ -0,0 +1,71 @@ +"""Check the release backport against the actual pinned upstream source.""" +import ast +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +from typing import Optional +from unittest.mock import patch + +source, patch_file = map(Path, sys.argv[1:]) +with tempfile.TemporaryDirectory(prefix="hermes-runtime-check-") as temporary: + root = Path(temporary) + for filename in ("hermes_cli/main.py", "scripts/desktop-update/posix.sh"): + destination = root / filename + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(source / filename, destination) + subprocess.run(["git", "apply", str(patch_file.resolve())], cwd=root, check=True) + text = (root / "hermes_cli/main.py").read_text() + functions = {node.name: node for node in ast.parse(text).body if isinstance(node, ast.FunctionDef)} + names = [ + "_desktop_linux_userns_sandbox_available", "_sandbox_helper_lstat", + "_sandbox_helper_is_setuid_root", "_desktop_linux_needs_disable_setuid_sandbox", + "_desktop_linux_sandbox_fixup", + ] + scope = dict(Path=Path, Optional=Optional, os=os, sys=sys, shutil=shutil, + subprocess=subprocess, stat=__import__("stat")) + for name in names: + exec(compile(ast.Module(body=[functions[name]], type_ignores=[]), str(source), "exec"), scope) + + native = root / "apps/desktop/release/linux-unpacked" + native.mkdir(parents=True) + executable = native / "Hermes" + sandbox = native / "chrome-sandbox" + sandbox.write_text("fixture") + sandbox.chmod(0o755) + gui = ast.get_source_segment(text, functions["cmd_gui"]) + start = gui.index(" launch_command = [str(packaged_executable)]") + end = gui.index(" launch_command.extend(config_electron_flags)", start) + exec("def launch(packaged_executable):\n" + gui[start:end] + " return launch_command\n", scope) + scope["_desktop_linux_needs_no_sandbox"] = lambda: False + with patch.object(shutil, "which", side_effect=lambda name: "/fixture/unshare" if name == "unshare" else None): + with patch.object(subprocess, "run", return_value=subprocess.CompletedProcess([], 0)) as run: + assert scope["launch"](executable) == [str(executable), "--disable-setuid-sandbox"] + assert all(call.args[0][0] == "/fixture/unshare" for call in run.call_args_list) + with patch.object(subprocess, "run", return_value=subprocess.CompletedProcess([], 1)): + assert not scope["_desktop_linux_sandbox_fixup"](executable) + sandbox.unlink() + sandbox.symlink_to(root / "unrelated") + (root / "unrelated").write_text("keep") + with patch.object(subprocess, "run") as run: + assert not scope["_desktop_linux_sandbox_fixup"](executable) + run.assert_not_called() + sandbox.unlink() + sandbox.write_text("fixture") + + mock_bin = root / "bin" + mock_bin.mkdir() + unshare = mock_bin / "unshare" + unshare.write_text('#!/bin/bash\nexit "${TEST_NAMESPACE_RESULT:-0}"\n') + unshare.chmod(0o755) + env = {**os.environ, "PATH": f"{mock_bin}:/usr/bin:/bin"} + env.pop("ELECTRON_DISABLE_SANDBOX", None) + gate = ["bash", str(root / "scripts/desktop-update/posix.sh"), "--self-test-gate", + "--install-root", str(root), "--relaunch-target", str(executable)] + assert subprocess.check_output(gate, env=env, text=True).strip() == "relaunch" + assert subprocess.check_output(gate, env={**env, "TEST_NAMESPACE_RESULT": "1"}, text=True).startswith("manual:") + gate[-1] = "/opt/hermes-desktop/Hermes" + assert subprocess.check_output(gate, env=env, text=True).startswith("skew:") +print("PASS: native launch and release update handoff retain the user-namespace sandbox") diff --git a/pkgbuilds/hermes-desktop/runtime.patch b/pkgbuilds/hermes-desktop/runtime.patch new file mode 100644 index 00000000..06db82d5 --- /dev/null +++ b/pkgbuilds/hermes-desktop/runtime.patch @@ -0,0 +1,79 @@ +--- a/hermes_cli/main.py ++++ b/hermes_cli/main.py +@@ -8146,6 +8146,44 @@ + return False + + ++def _desktop_linux_userns_sandbox_available() -> bool: ++ """True when the unprivileged userns sandbox works (probed with ``unshare``, fails closed) — then ++ the setuid ``chrome-sandbox`` helper is never consulted and no sudo prompt is needed.""" ++ if sys.platform != "linux": ++ return False ++ unshare = shutil.which("unshare") ++ if not unshare: ++ return False ++ try: ++ return ( ++ subprocess.run( ++ [unshare, "--user", "--map-root-user", "true"], ++ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5, check=False, ++ ).returncode ++ == 0) ++ except (OSError, subprocess.TimeoutExpired): ++ return False ++ ++def _sandbox_helper_lstat(packaged_executable: Path) -> tuple[Path, Optional[os.stat_result]]: ++ """``(chrome-sandbox path, lstat or None)`` — lstat so a symlink is inspected, not followed.""" ++ sandbox = packaged_executable.parent / "chrome-sandbox" ++ try: ++ return sandbox, sandbox.lstat() ++ except OSError: ++ return sandbox, None ++ ++def _sandbox_helper_is_setuid_root(st: os.stat_result) -> bool: ++ return st.st_uid == 0 and stat.S_IMODE(st.st_mode) == 0o4755 ++ ++def _desktop_linux_needs_disable_setuid_sandbox(packaged_executable: Path) -> bool: ++ """True when a present, non-setuid ``chrome-sandbox`` would make Chromium abort with ++ ``setuid_sandbox_host`` despite a working userns sandbox (call after the fixup's userns path).""" ++ if sys.platform != "linux": ++ return False ++ _sandbox, st = _sandbox_helper_lstat(packaged_executable) ++ return st is not None and stat.S_ISREG(st.st_mode) and not _sandbox_helper_is_setuid_root(st) ++ ++ + def _desktop_linux_sandbox_helper_is_regular_file(packaged_executable: Path) -> bool: + """Return True when ``chrome-sandbox`` exists as a regular file.""" + if sys.platform != "linux": +@@ -8182,6 +8220,10 @@ + return False + + if sandbox_lstat.st_uid == 0 and stat.S_IMODE(sandbox_lstat.st_mode) == 0o4755: ++ return True ++ ++ if _desktop_linux_userns_sandbox_available(): ++ print("✓ Using Chromium's user-namespace sandbox (setuid helper not needed).") + return True + + sudo = shutil.which("sudo") +@@ -8591,6 +8633,9 @@ + launch_command.append("--no-sandbox") + else: + sys.exit(1) ++ ++ elif _desktop_linux_needs_disable_setuid_sandbox(packaged_executable): ++ launch_command.append("--disable-setuid-sandbox") + + launch_command.extend(config_electron_flags) + print(f"→ Launching packaged Hermes Desktop: {' '.join(launch_command)}") +--- a/scripts/desktop-update/posix.sh ++++ b/scripts/desktop-update/posix.sh +@@ -317,6 +317,8 @@ + if [ ! -e "$sb" ]; then GATE=relaunch; return; fi + if [ -u "$sb" ] && [ "$(stat -c %u "$sb" 2>/dev/null)" = "0" ]; then GATE=relaunch; return; fi + ++ if unshare --user --map-root-user true 2>/dev/null; then GATE=relaunch; return; fi ++ + case "${ELECTRON_DISABLE_SANDBOX:-}" in 1|true|TRUE|True) GATE=relaunch; return ;; esac + [ "$SANDBOX_FALLBACK" -eq 1 ] && { GATE=relaunch; return; } + for arg in ${RELAUNCH_ARGS[@]+"${RELAUNCH_ARGS[@]}"}; do From 2fb9ab2ba9ab2a7e4bc150fe52534d117cc9c2a0 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Mon, 7 Sep 2026 03:17:42 -0500 Subject: [PATCH 57/63] Launch native Hermes without privileged sandbox setup Use the same direct executable path for menu launches and URLs, require working user namespaces, and retain only the first-update relaunch gate backport. This avoids the upstream CLI fallback that makes a helper in the user runtime setuid-root. Co-Authored-By: GPT-6 Codex (xhigh) --- pkgbuilds/hermes-desktop/PKGBUILD | 12 +-- pkgbuilds/hermes-desktop/hermes-desktop.sh | 17 ++-- pkgbuilds/hermes-desktop/runtime-test.py | 104 ++++++++++++--------- pkgbuilds/hermes-desktop/runtime.patch | 68 -------------- 4 files changed, 71 insertions(+), 130 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 29c9c28c..441c9961 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -5,7 +5,7 @@ pkgname=hermes-desktop pkgver=2026.8.31 -pkgrel=3 +pkgrel=4 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=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217' - '93540bbd8e3fccd132546c3e8f7da16eb04e67c20bbfe84034a2736c00e6d49d' + '7324b1d5e7db16c6169dd9e685f4093925c4a8c0568cb7ce416ac734f3336606' '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52' - 'cd544605f3b6ed397a829244e4f4d08f2d28a3a2f56edff7d3287b55bdf606b2' - '99caf1ef09c3ac88892ee674ebe92faa916c225fb938fe6796cf04b4e4ac397f') + '03b67e26c234c797a6b1d4c9f34a57502dba37f462540e47ce6c88f6ea79302a' + '69bc57d04610567eca35679328668619fcd3f129ed099bf5e2a1f24d1f197de9') build() { cd "${srcdir}/${_srcdir}" @@ -96,7 +96,7 @@ build() { } check() { - python "${srcdir}/runtime-test.py" "${srcdir}/${_srcdir}" "${srcdir}/runtime.patch" + python "${srcdir}/runtime-test.py" "${srcdir}/${_srcdir}" "${srcdir}/runtime.patch" "${srcdir}/hermes-desktop.sh" } package() { @@ -109,7 +109,7 @@ package() { install -Dm644 "${srcdir}/${_srcdir}/scripts/install.sh" \ "${pkgdir}/usr/share/${pkgname}/install.sh" - # Backport the upstream user-namespace fixes to this release's user runtime. + # Let the release's first updater relaunch with the user-namespace sandbox. install -Dm644 "${srcdir}/runtime.patch" "${pkgdir}/usr/share/${pkgname}/runtime.patch" diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 611b04f5..3f96199e 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -43,18 +43,13 @@ runtime="$hermes_home/hermes-agent" native="$runtime/apps/desktop/release/linux-unpacked/Hermes" if [[ -x $native && -x $runtime/venv/bin/hermes ]]; then - if (( $# == 0 )); then - if (( ${#platform_flags[@]} )); then - export ELECTRON_OZONE_PLATFORM_HINT="${ELECTRON_OZONE_PLATFORM_HINT:-wayland}" - fi - exec "$runtime/venv/bin/hermes" desktop --skip-build - else - # The upstream CLI does not accept Electron arguments or hermes:// URLs. - if unshare --user --map-root-user true 2>/dev/null; then - platform_flags+=(--disable-setuid-sandbox) - fi - exec "$native" "${platform_flags[@]}" "$@" + # Use the namespace sandbox without asking the CLI to make a user-writable + # helper setuid-root. Keep the same launch path for menu entries and URLs. + if ! timeout 5 unshare --user --map-root-user true 2>/dev/null; then + echo "Hermes Desktop requires working unprivileged user namespaces for its sandbox." >&2 + exit 1 fi + exec "$native" --disable-setuid-sandbox "${platform_flags[@]}" "$@" else exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@" fi diff --git a/pkgbuilds/hermes-desktop/runtime-test.py b/pkgbuilds/hermes-desktop/runtime-test.py index d4987bfb..0aa40d54 100644 --- a/pkgbuilds/hermes-desktop/runtime-test.py +++ b/pkgbuilds/hermes-desktop/runtime-test.py @@ -1,71 +1,85 @@ -"""Check the release backport against the actual pinned upstream source.""" -import ast +"""Check native launch and the release updater without running Hermes or sudo.""" +import json import os from pathlib import Path import shutil import subprocess import sys import tempfile -from typing import Optional -from unittest.mock import patch -source, patch_file = map(Path, sys.argv[1:]) +source, patch_file, launcher = map(Path, sys.argv[1:]) with tempfile.TemporaryDirectory(prefix="hermes-runtime-check-") as temporary: root = Path(temporary) - for filename in ("hermes_cli/main.py", "scripts/desktop-update/posix.sh"): - destination = root / filename - destination.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(source / filename, destination) + destination = root / "scripts/desktop-update/posix.sh" + destination.parent.mkdir(parents=True) + shutil.copyfile(source / "scripts/desktop-update/posix.sh", destination) subprocess.run(["git", "apply", str(patch_file.resolve())], cwd=root, check=True) - text = (root / "hermes_cli/main.py").read_text() - functions = {node.name: node for node in ast.parse(text).body if isinstance(node, ast.FunctionDef)} - names = [ - "_desktop_linux_userns_sandbox_available", "_sandbox_helper_lstat", - "_sandbox_helper_is_setuid_root", "_desktop_linux_needs_disable_setuid_sandbox", - "_desktop_linux_sandbox_fixup", - ] - scope = dict(Path=Path, Optional=Optional, os=os, sys=sys, shutil=shutil, - subprocess=subprocess, stat=__import__("stat")) - for name in names: - exec(compile(ast.Module(body=[functions[name]], type_ignores=[]), str(source), "exec"), scope) - native = root / "apps/desktop/release/linux-unpacked" + home = root / "home with spaces" + runtime = home / ".hermes/hermes-agent" + native = runtime / "apps/desktop/release/linux-unpacked" native.mkdir(parents=True) executable = native / "Hermes" + executable.write_text(f"#!{sys.executable}\n" + '''import json, os, sys +from pathlib import Path +Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({ + "args": sys.argv[1:], "home": os.environ["HERMES_HOME"], + "store": os.environ["HERMES_DESKTOP_PASSWORD_STORE"], + "gpu": os.environ.get("HERMES_DESKTOP_DISABLE_GPU"), +})) +''') + executable.chmod(0o755) sandbox = native / "chrome-sandbox" sandbox.write_text("fixture") sandbox.chmod(0o755) - gui = ast.get_source_segment(text, functions["cmd_gui"]) - start = gui.index(" launch_command = [str(packaged_executable)]") - end = gui.index(" launch_command.extend(config_electron_flags)", start) - exec("def launch(packaged_executable):\n" + gui[start:end] + " return launch_command\n", scope) - scope["_desktop_linux_needs_no_sandbox"] = lambda: False - with patch.object(shutil, "which", side_effect=lambda name: "/fixture/unshare" if name == "unshare" else None): - with patch.object(subprocess, "run", return_value=subprocess.CompletedProcess([], 0)) as run: - assert scope["launch"](executable) == [str(executable), "--disable-setuid-sandbox"] - assert all(call.args[0][0] == "/fixture/unshare" for call in run.call_args_list) - with patch.object(subprocess, "run", return_value=subprocess.CompletedProcess([], 1)): - assert not scope["_desktop_linux_sandbox_fixup"](executable) - sandbox.unlink() - sandbox.symlink_to(root / "unrelated") - (root / "unrelated").write_text("keep") - with patch.object(subprocess, "run") as run: - assert not scope["_desktop_linux_sandbox_fixup"](executable) - run.assert_not_called() - sandbox.unlink() - sandbox.write_text("fixture") mock_bin = root / "bin" mock_bin.mkdir() unshare = mock_bin / "unshare" unshare.write_text('#!/bin/bash\nexit "${TEST_NAMESPACE_RESULT:-0}"\n') unshare.chmod(0o755) - env = {**os.environ, "PATH": f"{mock_bin}:/usr/bin:/bin"} - env.pop("ELECTRON_DISABLE_SANDBOX", None) - gate = ["bash", str(root / "scripts/desktop-update/posix.sh"), "--self-test-gate", - "--install-root", str(root), "--relaunch-target", str(executable)] + forbidden = '#!/bin/bash\ntouch "$TEST_FORBIDDEN"\nexit 99\n' + cli = runtime / "venv/bin/hermes" + cli.parent.mkdir(parents=True) + cli.write_text(forbidden) + cli.chmod(0o755) + 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.chmod(0o755) + + output = root / "launch.json" + forbidden_output = root / "forbidden" + env = {"HOME": str(home), "PATH": f"{mock_bin}:/usr/bin:/bin", + "TEST_OUTPUT": str(output), "TEST_FORBIDDEN": str(forbidden_output)} + launch = ["bash", str(launcher.resolve())] + for args, overrides, expected_args, expected_store in ( + ([], {"WAYLAND_DISPLAY": "wayland-1"}, ["--ozone-platform=wayland"], "gnome-libsecret"), + (["--ozone-platform=x11", "hermes://open?text=a%20b"], + {"WAYLAND_DISPLAY": "wayland-1", "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"}, + ["--ozone-platform=x11", "hermes://open?text=a%20b"], "kwallet6"), + ([], {"HERMES_HOME": str(home / ".hermes/profiles/work"), "HERMES_DESKTOP_DISABLE_GPU": "1"}, + [], "gnome-libsecret"), + ): + subprocess.run(launch + args, env={**env, **overrides}, check=True) + result = json.loads(output.read_text()) + assert result["args"] == ["--disable-setuid-sandbox", *expected_args], result + assert result["home"] == str(home / ".hermes"), result + assert result["store"] == expected_store, result + assert result["gpu"] == overrides.get("HERMES_DESKTOP_DISABLE_GPU"), result + assert not forbidden_output.exists(), "launcher invoked CLI or sudo" + output.unlink() + for code in ("1", "127"): + for args in ([], ["hermes://open"]): + result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code}, + capture_output=True, text=True) + assert result.returncode != 0 and "user namespaces" in result.stderr, result + assert not output.exists() and not forbidden_output.exists() + + gate = ["bash", str(destination), "--self-test-gate", "--install-root", str(runtime), + "--relaunch-target", str(executable)] assert subprocess.check_output(gate, env=env, text=True).strip() == "relaunch" assert subprocess.check_output(gate, env={**env, "TEST_NAMESPACE_RESULT": "1"}, text=True).startswith("manual:") gate[-1] = "/opt/hermes-desktop/Hermes" assert subprocess.check_output(gate, env=env, text=True).startswith("skew:") -print("PASS: native launch and release update handoff retain the user-namespace sandbox") +print("PASS: direct native launches fail closed; release updater accepts the namespace sandbox") diff --git a/pkgbuilds/hermes-desktop/runtime.patch b/pkgbuilds/hermes-desktop/runtime.patch index 06db82d5..8b7ed51e 100644 --- a/pkgbuilds/hermes-desktop/runtime.patch +++ b/pkgbuilds/hermes-desktop/runtime.patch @@ -1,71 +1,3 @@ ---- a/hermes_cli/main.py -+++ b/hermes_cli/main.py -@@ -8146,6 +8146,44 @@ - return False - - -+def _desktop_linux_userns_sandbox_available() -> bool: -+ """True when the unprivileged userns sandbox works (probed with ``unshare``, fails closed) — then -+ the setuid ``chrome-sandbox`` helper is never consulted and no sudo prompt is needed.""" -+ if sys.platform != "linux": -+ return False -+ unshare = shutil.which("unshare") -+ if not unshare: -+ return False -+ try: -+ return ( -+ subprocess.run( -+ [unshare, "--user", "--map-root-user", "true"], -+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5, check=False, -+ ).returncode -+ == 0) -+ except (OSError, subprocess.TimeoutExpired): -+ return False -+ -+def _sandbox_helper_lstat(packaged_executable: Path) -> tuple[Path, Optional[os.stat_result]]: -+ """``(chrome-sandbox path, lstat or None)`` — lstat so a symlink is inspected, not followed.""" -+ sandbox = packaged_executable.parent / "chrome-sandbox" -+ try: -+ return sandbox, sandbox.lstat() -+ except OSError: -+ return sandbox, None -+ -+def _sandbox_helper_is_setuid_root(st: os.stat_result) -> bool: -+ return st.st_uid == 0 and stat.S_IMODE(st.st_mode) == 0o4755 -+ -+def _desktop_linux_needs_disable_setuid_sandbox(packaged_executable: Path) -> bool: -+ """True when a present, non-setuid ``chrome-sandbox`` would make Chromium abort with -+ ``setuid_sandbox_host`` despite a working userns sandbox (call after the fixup's userns path).""" -+ if sys.platform != "linux": -+ return False -+ _sandbox, st = _sandbox_helper_lstat(packaged_executable) -+ return st is not None and stat.S_ISREG(st.st_mode) and not _sandbox_helper_is_setuid_root(st) -+ -+ - def _desktop_linux_sandbox_helper_is_regular_file(packaged_executable: Path) -> bool: - """Return True when ``chrome-sandbox`` exists as a regular file.""" - if sys.platform != "linux": -@@ -8182,6 +8220,10 @@ - return False - - if sandbox_lstat.st_uid == 0 and stat.S_IMODE(sandbox_lstat.st_mode) == 0o4755: -+ return True -+ -+ if _desktop_linux_userns_sandbox_available(): -+ print("✓ Using Chromium's user-namespace sandbox (setuid helper not needed).") - return True - - sudo = shutil.which("sudo") -@@ -8591,6 +8633,9 @@ - launch_command.append("--no-sandbox") - else: - sys.exit(1) -+ -+ elif _desktop_linux_needs_disable_setuid_sandbox(packaged_executable): -+ launch_command.append("--disable-setuid-sandbox") - - launch_command.extend(config_electron_flags) - print(f"→ Launching packaged Hermes Desktop: {' '.join(launch_command)}") --- a/scripts/desktop-update/posix.sh +++ b/scripts/desktop-update/posix.sh @@ -317,6 +317,8 @@ From 0c2fa676f6de4f91abb5820edfb915b8de72a913 Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Mon, 7 Sep 2026 03:35:15 -0500 Subject: [PATCH 58/63] Use the next Hermes package release number --- pkgbuilds/hermes-desktop/PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 441c9961..45eb61ec 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -5,7 +5,7 @@ pkgname=hermes-desktop pkgver=2026.8.31 -pkgrel=4 +pkgrel=2 pkgdesc='Native desktop shell for Hermes Agent' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' From f69f6ce36433b915b6894ed2e17950f3a6dec50f Mon Sep 17 00:00:00 2001 From: Spencer Bull Date: Mon, 7 Sep 2026 04:10:07 -0500 Subject: [PATCH 59/63] Fix Hermes desktop launch settings and sandbox consistency Release 2026.8.31-3 preserves upstream desktop settings and explicit launch environment without invoking the CLI sandbox setup. Keep incomplete runtimes from blocking the packaged fallback, and use the namespace sandbox consistently in both locations. Co-Authored-By: Codex XHigh --- pkgbuilds/hermes-desktop/PKGBUILD | 15 +-- pkgbuilds/hermes-desktop/hermes-desktop.sh | 91 +++++++++------- pkgbuilds/hermes-desktop/runtime-test.py | 119 +++++++++++++++++---- 3 files changed, 158 insertions(+), 67 deletions(-) diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index 45eb61ec..1f48dd9d 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -5,7 +5,7 @@ pkgname=hermes-desktop pkgver=2026.8.31 -pkgrel=2 +pkgrel=3 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=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217' - '7324b1d5e7db16c6169dd9e685f4093925c4a8c0568cb7ce416ac734f3336606' + '094d5f3191109a80eea9f23053b78a2e00dbecf90d62d1ca04c8e48866251469' '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52' '03b67e26c234c797a6b1d4c9f34a57502dba37f462540e47ce6c88f6ea79302a' - '69bc57d04610567eca35679328668619fcd3f129ed099bf5e2a1f24d1f197de9') + '461e1120e7e6779f531c114d9926479e47fab79113d1b4646efea36bc771b3c5') build() { cd "${srcdir}/${_srcdir}" @@ -130,11 +130,6 @@ package() { install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \ "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt" - # Chromium's setuid sandbox is only needed where unprivileged user namespaces - # are unavailable; where they work, setuid root is the worse of the two. - if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then - chmod 4755 "${pkgdir}/opt/${pkgname}/chrome-sandbox" - else - chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox" - fi + # The launcher requires the namespace sandbox on the target host. + chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox" } diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh index 3f96199e..40822b88 100644 --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -1,37 +1,14 @@ #!/bin/bash set -euo pipefail -# Use the runtime prepared by Omarchy rather than a separate CLI on PATH. +unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME export HERMES_DESKTOP_IGNORE_EXISTING=1 -# Chromium cannot reliably infer the Secret Service password-store backend -# from a Hyprland session, even when GNOME Keyring is already providing it. -# Keep an explicit user choice (such as KWallet), otherwise select the -# libsecret backend that this package depends on. -export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}" - -# Reconcile every launch rather than trusting whatever installed us. A plain -# `pacman -S hermes-desktop`, or an install interrupted partway, leaves any -# Hermes the terminal agent had built still sitting there, and by then the -# menu entry that would have tidied it up is disabled because we are present. +# 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 -# Chromium's own Ozone detection falls back to XWayland often enough to matter, -# and the result is a blurry window on every scaled display. Ask for Wayland -# directly, unless the user has already picked a platform themselves. -platform_flags=() -if [[ -n "${WAYLAND_DISPLAY:-}" || ${XDG_SESSION_TYPE:-} == wayland ]]; then - platform_flags=(--ozone-platform=wayland) - - for flag in "$@"; do - case "$flag" in - --ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; - esac - done -fi - hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}") parent=${hermes_home%/*} if [[ ${parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then @@ -41,15 +18,57 @@ fi export HERMES_HOME="$hermes_home" runtime="$hermes_home/hermes-agent" native="$runtime/apps/desktop/release/linux-unpacked/Hermes" - -if [[ -x $native && -x $runtime/venv/bin/hermes ]]; then - # Use the namespace sandbox without asking the CLI to make a user-writable - # helper setuid-root. Keep the same launch path for menu entries and URLs. - if ! timeout 5 unshare --user --map-root-user true 2>/dev/null; then - echo "Hermes Desktop requires working unprivileged user namespaces for its sandbox." >&2 - exit 1 - fi - exec "$native" --disable-setuid-sandbox "${platform_flags[@]}" "$@" -else - exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@" +if [[ ! -x $native || ! -x $runtime/venv/bin/hermes ]]; then + native=/opt/hermes-desktop/Hermes fi + +# Both app locations use namespaces, never a user-writable setuid helper. +if ! timeout 5 unshare --user --map-root-user true 2>/dev/null; then + echo "Hermes Desktop requires working unprivileged user namespaces for its sandbox." >&2 + exit 1 +fi + +python=/usr/bin/python +if [[ -x $runtime/venv/bin/python && -f $runtime/hermes_cli/main.py ]]; then + python="$runtime/venv/bin/python" +else + runtime="" +fi +exec "$python" - "$native" "$runtime" "$@" <<'PY' +import os +from pathlib import Path +import sys + +native, runtime, *args = sys.argv[1:] +env = os.environ.copy() +flags, gpu, store, ozone = [], "auto", "auto", "auto" +if runtime: + sys.path.insert(0, runtime) + try: + # Upstream moved the helper out of main after the packaged release. + if Path(runtime, "hermes_cli/main_desktop.py").is_file(): + from hermes_cli.main_desktop import _desktop_launch_options + else: + from hermes_cli.main import _desktop_launch_options + from hermes_constants import with_hermes_node_path + + flags, gpu, store, ozone = _desktop_launch_options() + env = with_hermes_node_path(env) + except ImportError: + print("Could not load Hermes desktop settings; using launch defaults.", file=sys.stderr) + +env["HERMES_DESKTOP_CWD"] = os.getcwd() +if gpu != "auto": + env.setdefault("HERMES_DESKTOP_DISABLE_GPU", gpu) +if ozone != "auto": + env.setdefault("ELECTRON_OZONE_PLATFORM_HINT", ozone) +env.setdefault("HERMES_DESKTOP_PASSWORD_STORE", store if store != "auto" else "gnome-libsecret") + +# Explicit config, environment and command-line choices override the Wayland default. +if (env.get("WAYLAND_DISPLAY") or env.get("XDG_SESSION_TYPE") == "wayland") and ( + "ELECTRON_OZONE_PLATFORM_HINT" not in env + and not any(arg.startswith(("--ozone-platform=", "--ozone-platform-hint=")) for arg in flags + args) +): + flags.insert(0, "--ozone-platform=wayland") +os.execve(native, [native, "--disable-setuid-sandbox", *flags, *args], env) +PY diff --git a/pkgbuilds/hermes-desktop/runtime-test.py b/pkgbuilds/hermes-desktop/runtime-test.py index 0aa40d54..79f0a43d 100644 --- a/pkgbuilds/hermes-desktop/runtime-test.py +++ b/pkgbuilds/hermes-desktop/runtime-test.py @@ -1,4 +1,5 @@ -"""Check native launch and the release updater without running Hermes or sudo.""" +"""Check launch settings and sandbox gates without running Hermes or sudo.""" +import ast import json import os from pathlib import Path @@ -26,6 +27,9 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({ "args": sys.argv[1:], "home": os.environ["HERMES_HOME"], "store": os.environ["HERMES_DESKTOP_PASSWORD_STORE"], "gpu": os.environ.get("HERMES_DESKTOP_DISABLE_GPU"), + "ozone": os.environ.get("ELECTRON_OZONE_PLATFORM_HINT"), + "cwd": os.environ.get("HERMES_DESKTOP_CWD"), + "inherited": [name for name in ("ELECTRON_RUN_AS_NODE", "PYTHONPATH", "PYTHONHOME") if name in os.environ], })) ''') executable.chmod(0o755) @@ -33,6 +37,30 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({ sandbox.write_text("fixture") sandbox.chmod(0o755) + # Execute the release's real option parser, isolating config I/O and avoiding + # unrelated CLI imports, startup hooks and third-party dependencies. + module = runtime / "hermes_cli" + module.mkdir() + (module / "__init__.py").touch() + upstream = ast.parse((source / "hermes_cli/main.py").read_text()) + option_parser = next(node for node in upstream.body + if isinstance(node, ast.FunctionDef) and node.name == "_desktop_launch_options") + stores = next(node for node in upstream.body if isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == "_LINUX_PASSWORD_STORES" + for target in node.targets)) + helper = "import os, shlex\n" + ast.unparse(stores) + "\n" + ast.unparse(option_parser) + "\n" + (module / "main.py").write_text(helper) + (module / "config.py").write_text('''import json, os +from pathlib import Path +def load_config(): + path = Path(os.environ["HERMES_HOME"], "config.yaml") + return json.loads(path.read_text()) if path.exists() else {} +''') + (runtime / "hermes_constants.py").write_text('''import os +def with_hermes_node_path(env=None): + return (os.environ if env is None else env).copy() +''') + mock_bin = root / "bin" mock_bin.mkdir() unshare = mock_bin / "unshare" @@ -43,6 +71,7 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({ cli.parent.mkdir(parents=True) cli.write_text(forbidden) cli.chmod(0o755) + (cli.parent / "python").symlink_to(sys.executable) 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') @@ -53,33 +82,81 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({ env = {"HOME": str(home), "PATH": f"{mock_bin}:/usr/bin:/bin", "TEST_OUTPUT": str(output), "TEST_FORBIDDEN": str(forbidden_output)} launch = ["bash", str(launcher.resolve())] - for args, overrides, expected_args, expected_store in ( - ([], {"WAYLAND_DISPLAY": "wayland-1"}, ["--ozone-platform=wayland"], "gnome-libsecret"), - (["--ozone-platform=x11", "hermes://open?text=a%20b"], - {"WAYLAND_DISPLAY": "wayland-1", "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"}, - ["--ozone-platform=x11", "hermes://open?text=a%20b"], "kwallet6"), - ([], {"HERMES_HOME": str(home / ".hermes/profiles/work"), "HERMES_DESKTOP_DISABLE_GPU": "1"}, - [], "gnome-libsecret"), - ): - subprocess.run(launch + args, env={**env, **overrides}, check=True) + + def check_launch(args=(), overrides=None, expected_args=(), store="gnome-libsecret", gpu=None, ozone=None): + subprocess.run(launch + list(args), env={**env, **(overrides or {})}, cwd=home, check=True) result = json.loads(output.read_text()) - assert result["args"] == ["--disable-setuid-sandbox", *expected_args], result - assert result["home"] == str(home / ".hermes"), result - assert result["store"] == expected_store, result - assert result["gpu"] == overrides.get("HERMES_DESKTOP_DISABLE_GPU"), result + 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" output.unlink() - for code in ("1", "127"): - for args in ([], ["hermes://open"]): - result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code}, - capture_output=True, text=True) - assert result.returncode != 0 and "user namespaces" in result.stderr, result - assert not output.exists() and not forbidden_output.exists() + + wayland = {"WAYLAND_DISPLAY": "wayland-1"} + check_launch(overrides=wayland, expected_args=["--ozone-platform=wayland"]) + url = "hermes://open?text=a%20b" + check_launch(["--ozone-platform=x11", url], {**wayland, "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"}, + ["--ozone-platform=x11", url], store="kwallet6") + check_launch(overrides={"HERMES_HOME": str(home / ".hermes/profiles/work"), + "HERMES_DESKTOP_DISABLE_GPU": "1"}, gpu="1") + check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"}) + + config = home / ".hermes/config.yaml" + config.write_text(json.dumps({"desktop": {"disable_gpu": True, "password_store": "kwallet5", + "ozone_platform_hint": "x11", + "electron_flags": '--force-device-scale-factor=1.5 "--test=a b"'}})) + config_flags = ["--force-device-scale-factor=1.5", "--test=a b"] + for args in ([], [url]): + check_launch(args, wayland, config_flags + args, store="kwallet5", gpu="1", ozone="x11") + check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0", + "HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "wayland"}, + expected_args=config_flags, store="basic", gpu="0", ozone="wayland") + config.write_text(json.dumps({"desktop": {"electron_flags": ["--ozone-platform=x11"], "disable_gpu": False}})) + check_launch(overrides=wayland, expected_args=["--ozone-platform=x11"], gpu="0") + # New upstream revisions expose the same helper from main_desktop instead. + (module / "main_desktop.py").write_text(helper) + (module / "main.py").write_text('raise AssertionError("old helper import after update")\n') + check_launch([url], wayland, ["--ozone-platform=x11", url], gpu="0") + config.unlink() + (module / "main_desktop.py").write_text(helper + '\nimport os\nos.environ.update(' + repr({ + "ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid", + "HERMES_HOME": "/invalid", "HERMES_DESKTOP_DISABLE_GPU": "1", + "HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "x11", + }) + ')\n') + check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0"}, + expected_args=["--ozone-platform=wayland"], gpu="0") + (module / "main_desktop.py").write_text(helper) + + def check_namespace_failure(): + for code in ("1", "127"): + for args in ([], [url]): + result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code}, + capture_output=True, text=True) + assert result.returncode != 0 and "user namespaces" in result.stderr, result + assert not output.exists() and not forbidden_output.exists() + + check_namespace_failure() + # Exercise the real fallback branch with only its absolute app path redirected. + fallback = root / "packaged-Hermes" + shutil.copyfile(executable, fallback) + fallback.chmod(0o755) + fallback_launcher = root / "fallback-launcher" + fallback_launcher.write_text(launcher.read_text().replace("/opt/hermes-desktop/Hermes", str(fallback))) + launch = ["bash", str(fallback_launcher)] + executable.unlink() + check_launch([url], wayland, ["--ozone-platform=wayland", url]) + (module / "main_desktop.py").write_text('raise ImportError("incomplete Python dependencies")\n') + check_launch([url], wayland, ["--ozone-platform=wayland", url]) + shutil.rmtree(runtime / "venv") + check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"}) + check_namespace_failure() gate = ["bash", str(destination), "--self-test-gate", "--install-root", str(runtime), "--relaunch-target", str(executable)] + executable.touch() + executable.chmod(0o755) assert subprocess.check_output(gate, env=env, text=True).strip() == "relaunch" assert subprocess.check_output(gate, env={**env, "TEST_NAMESPACE_RESULT": "1"}, text=True).startswith("manual:") gate[-1] = "/opt/hermes-desktop/Hermes" assert subprocess.check_output(gate, env=env, text=True).startswith("skew:") -print("PASS: direct native launches fail closed; release updater accepts the namespace sandbox") +print("PASS: desktop settings, environment, native/fallback namespace sandbox and updater gate") From b7f71ec9e25601e52781dc296e1b3e62b5f83a8c Mon Sep 17 00:00:00 2001 From: dhh <2741+dhh@users.noreply.github.com> Date: Mon, 7 Sep 2026 12:47:14 +0000 Subject: [PATCH 60/63] chore: sync upstream releases --- pkgbuilds/omakade/PKGBUILD | 6 +++--- pkgbuilds/schist-bin/PKGBUILD | 6 +++--- pkgbuilds/t3code-bin/PKGBUILD | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 4dc090e0..9645b144 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,6 +1,6 @@ pkgname=omakade -pkgver=1.6.1 -pkgrel=2 +pkgver=1.7.0 +pkgrel=1 pkgdesc='A beautiful, local-first game library for Omarchy' arch=('x86_64' 'aarch64') url='https://btsouth.github.io/omakade/' @@ -9,7 +9,7 @@ depends=('glib2' 'hicolor-icon-theme' 'libsecret' 'libzip' 'qt6-base' 'qt6-decla makedepends=('cmake' 'ninja' 'pkgconf') options=('!debug') source=("$pkgname-$pkgver.tar.gz::https://github.com/btsouth/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") -sha256sums=('7de0575fb6a7c16f7e8f88e15b6fa15e8486e924e92d07bf6029c0d94a0887e5') +sha256sums=('715cd5e0cb860514276b1af57fe08ced71c1c69a6aeebd93aa51f1ca367eefa8') build() { cmake -S "$pkgname-$pkgver" -B build -G Ninja \ diff --git a/pkgbuilds/schist-bin/PKGBUILD b/pkgbuilds/schist-bin/PKGBUILD index a67a1191..06ee0b3c 100644 --- a/pkgbuilds/schist-bin/PKGBUILD +++ b/pkgbuilds/schist-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Infrawrench LLC pkgname=schist-bin -pkgver=0.11.0 +pkgver=0.12.0 pkgrel=1 # Upstream's own package release, embedded in the asset name. It is # packages.sh's "release=" and only moves when the packaging changes under @@ -32,8 +32,8 @@ options=(!strip !debug) # script. source_x86_64=("$url/releases/download/v$pkgver/schist-$pkgver-$_relver-x86_64.pkg.tar.zst") source_aarch64=("$url/releases/download/v$pkgver/schist-$pkgver-$_relver-aarch64.pkg.tar.zst") -sha256sums_x86_64=('0fb0a13e579a9bd74880bf49dd69690cc6a5f830db3423f1e448fef516c1bc5f') -sha256sums_aarch64=('1dedd50295ac251206dcc271ce59a918258975a7e87facde2dcee1b03fef265b') +sha256sums_x86_64=('baff8848274f0121911a6408fd3dd082c5ab023c0ae5c9e5be35968c80beb069') +sha256sums_aarch64=('a7a4f062289a140ff0aa5c0c9c4b3cc9f386759e3bfe929a0382de2d20c04eda') package() { # makepkg has already extracted the payload into srcdir; its .PKGINFO diff --git a/pkgbuilds/t3code-bin/PKGBUILD b/pkgbuilds/t3code-bin/PKGBUILD index 7a3206fc..fdff11f7 100644 --- a/pkgbuilds/t3code-bin/PKGBUILD +++ b/pkgbuilds/t3code-bin/PKGBUILD @@ -7,7 +7,7 @@ # from the release feed the app updates itself from. pkgname=t3code-bin -pkgver=0.0.38 +pkgver=0.0.39 pkgrel=1 pkgdesc="Open-source control plane for coding agents" arch=('x86_64') @@ -61,7 +61,7 @@ noextract=("${_appimage}") sha256sums=('cb905ff341372ef2ef6e402cf485959f8bd1df8f0efebee4cda1afdd5e6abc0a' 'c5b3f2a9f0b14b12cfd973b79319f0f018b7ae49a1d43d8ca346100c3f7de28f' '935d8f2af0c703f9c39517ee57cc4930b19d02d533be930b63f0e82f93614b43') -sha256sums_x86_64=('1f1ccd922b2efaffd50447b028ee0d6e2525502905087cf8ae3fe41efebe346f') +sha256sums_x86_64=('4f78c9939c0386f9be5a2d8e9baf9644af497344015817a36fea8be84981bda8') prepare() { chmod +x "${srcdir}/${_appimage}" From 6d56c03c7a8004eb981cdf43c006c2b72a967e1e Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Mon, 7 Sep 2026 11:27:08 -0400 Subject: [PATCH 61/63] Bump t3code-bin to 0.0.39 ahead of its release-age hold Released 06:46 UTC today, inside the quarantine window; shipped by hand per BYPASS_MIN_RELEASE_AGE's intent. The feed's asset name matches the one the PKGBUILD builds, the download's size and SHA-512 match the electron-builder feed exactly (byte-identical to what the app's own updater installs), and the SHA-256 here is from that verified download. --- pkgbuilds/t3code-bin/PKGBUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgbuilds/t3code-bin/PKGBUILD b/pkgbuilds/t3code-bin/PKGBUILD index 7a3206fc..fdff11f7 100644 --- a/pkgbuilds/t3code-bin/PKGBUILD +++ b/pkgbuilds/t3code-bin/PKGBUILD @@ -7,7 +7,7 @@ # from the release feed the app updates itself from. pkgname=t3code-bin -pkgver=0.0.38 +pkgver=0.0.39 pkgrel=1 pkgdesc="Open-source control plane for coding agents" arch=('x86_64') @@ -61,7 +61,7 @@ noextract=("${_appimage}") sha256sums=('cb905ff341372ef2ef6e402cf485959f8bd1df8f0efebee4cda1afdd5e6abc0a' 'c5b3f2a9f0b14b12cfd973b79319f0f018b7ae49a1d43d8ca346100c3f7de28f' '935d8f2af0c703f9c39517ee57cc4930b19d02d533be930b63f0e82f93614b43') -sha256sums_x86_64=('1f1ccd922b2efaffd50447b028ee0d6e2525502905087cf8ae3fe41efebe346f') +sha256sums_x86_64=('4f78c9939c0386f9be5a2d8e9baf9644af497344015817a36fea8be84981bda8') prepare() { chmod +x "${srcdir}/${_appimage}" From 44607ecfe78eafa0be2720a6c75995341f288398 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Mon, 7 Sep 2026 11:38:13 -0400 Subject: [PATCH 62/63] Bump omakade to 1.7.0 with its new build requirements 1.7.0 makes OpenSSL and libzstd hard CMake requirements; both are in every build chroot transitively, so the sync's version-only bump in auto/sync-upstream would have built and shipped them as undeclared linkage rather than failing. Declare them, and pin the new optional Wayland idle-inhibit feature on by adding wayland-protocols to makedepends instead of leaving it to what the chroot happens to carry. Checksum matches the release's SHA256SUMS manifest and an independent download. --- pkgbuilds/omakade/PKGBUILD | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkgbuilds/omakade/PKGBUILD b/pkgbuilds/omakade/PKGBUILD index 4dc090e0..5186e465 100644 --- a/pkgbuilds/omakade/PKGBUILD +++ b/pkgbuilds/omakade/PKGBUILD @@ -1,15 +1,17 @@ pkgname=omakade -pkgver=1.6.1 -pkgrel=2 +pkgver=1.7.0 +pkgrel=1 pkgdesc='A beautiful, local-first game library for Omarchy' arch=('x86_64' 'aarch64') url='https://btsouth.github.io/omakade/' license=('GPL-3.0-or-later') -depends=('glib2' 'hicolor-icon-theme' 'libsecret' 'libzip' 'qt6-base' 'qt6-declarative' 'qt6-imageformats' 'qt6-svg' 'qt6-wayland' 'sdl3') -makedepends=('cmake' 'ninja' 'pkgconf') +depends=('glib2' 'hicolor-icon-theme' 'libsecret' 'libzip' 'openssl' 'qt6-base' 'qt6-declarative' 'qt6-imageformats' 'qt6-svg' 'qt6-wayland' 'sdl3' 'wayland' 'zstd') +# wayland-protocols turns the optional idle-inhibit feature on +# deterministically instead of leaving it to what the chroot happens to carry. +makedepends=('cmake' 'ninja' 'pkgconf' 'wayland-protocols') options=('!debug') source=("$pkgname-$pkgver.tar.gz::https://github.com/btsouth/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") -sha256sums=('7de0575fb6a7c16f7e8f88e15b6fa15e8486e924e92d07bf6029c0d94a0887e5') +sha256sums=('715cd5e0cb860514276b1af57fe08ced71c1c69a6aeebd93aa51f1ca367eefa8') build() { cmake -S "$pkgname-$pkgver" -B build -G Ninja \ From 8b3ac7de1a60ba9af1deb26df34fbfac06fe1f1d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 7 Sep 2026 19:56:25 +0200 Subject: [PATCH 63/63] 1password: start at a fixed device scale factor (#342) 1Password reads the display scale itself, the way Electron apps do, so on a scaled monitor it comes up oversized next to every other window. Pin it in the .desktop we install and let the compositor scale it. Rewriting Exec on the way in is how spotify, perplexity and sublime-text already fix up their entries here. Only the stable package: 1password-beta syncs from the AUR, so a patch there would be overwritten. Claude-Session: https://claude.ai/code/session_01JB9phxP56gnP7qSidkkUJE Co-authored-by: Claude Opus 5 (1M context) --- pkgbuilds/1password/PKGBUILD | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkgbuilds/1password/PKGBUILD b/pkgbuilds/1password/PKGBUILD index bfb2dae7..2479352a 100644 --- a/pkgbuilds/1password/PKGBUILD +++ b/pkgbuilds/1password/PKGBUILD @@ -1,6 +1,6 @@ pkgname=1password pkgver=8.12.34 -pkgrel=35 +pkgrel=36 conflicts=('1password-beta' '1password-beta-bin') pkgdesc="Password manager and secure wallet" arch=('x86_64' 'aarch64') @@ -51,6 +51,12 @@ package() { # Install desktop file install -Dm0644 resources/1password.desktop -t "${pkgdir}"/usr/share/applications/ + # 1Password reads the display scale itself, the way Electron apps do, and + # comes up oversized next to every other window on a scaled monitor. Pin it + # and let the compositor do the scaling. + sed -i 's|^Exec=.*|Exec=/opt/1Password/1password --force-device-scale-factor=1 %U|' \ + "${pkgdir}/usr/share/applications/1password.desktop" + # Fill in policy kit file with a list of (the first 10) human users of the system. export POLICY_OWNERS POLICY_OWNERS="$(cut -d: -f1,3 /etc/passwd | grep -E ':[0-9]{4}$' | cut -d: -f1 | head -n 10 | sed 's/^/unix-user:/' | tr '\n' ' ')"