diff --git a/build-inputs/omarchy-mac-keyring/PKGBUILD b/build-inputs/omarchy-mac-keyring/PKGBUILD index 31e730e6f5c..de4de9a1f1d 100644 --- a/build-inputs/omarchy-mac-keyring/PKGBUILD +++ b/build-inputs/omarchy-mac-keyring/PKGBUILD @@ -1,16 +1,16 @@ # Maintainer: Omarchy Mac pkgname=omarchy-mac-keyring -pkgver=20260913 -pkgrel=1 +pkgver=20260914 +pkgrel=2 pkgdesc='Omarchy Mac package signing keyring' arch=(any) url='https://github.com/omarchy-mac/omarchy-mac' license=('GPL-3.0-or-later') install=$pkgname.install source=('omarchy-mac.gpg' 'omarchy-mac-trusted' 'omarchy-mac-revoked') -sha512sums=('0e411148bf58cb1cf3c1ef544ea0936d82b0d7b3996c3ef3d20ac909959129c1d7cb58dc84ac466cd3e2d75db228aff896640899b4bbbef19e7636f3033a7ea8' - '2fb4497f96fd9f446e0177f9dbf8eb34f0ad3dce5fc7fb687312c5e20a1127ceb68f0d6022424f35b37e2a6a91fb097b2212595375fa4d37d7547697b17de86e' +sha512sums=('3b09ea4dcae2e7957c8543fc926522cb35f19915d5855b998db78761f4d35b04a1409f53726e47267fcfecfd31e66b7d16a8894603dbb20886dd000e4ef984fc' + '5ff1f0c04c98963d89a941fe160bb4a3010ee541f1a013931230eae40e1c7f9ea59b867d4241b4d8c1f39387655c8e1794db3912e5c369f9e98883df074d46a7' 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e') package() { diff --git a/build-packages.sh b/build-packages.sh index a622b946a6a..016b600bd6c 100755 --- a/build-packages.sh +++ b/build-packages.sh @@ -122,14 +122,53 @@ ensure_snapper_dependency() { } ensure_omarchy_mac_keyring_dependency() { - local pkgbuild="$1" + local pkgbuild="$1" minimum="20260914-2" + local index in_depends=0 found=0 insert_index=0 line scan token suffix operator version replacement + local pattern="['\"]omarchy-mac-keyring((>=|<=|=|>|<)([^'\"]+))?['\"]" + local -a lines=() grep -qx 'depends=(' "$pkgbuild" || fail "omarchy PKGBUILD no longer has the expected depends array: $pkgbuild" - if ! sed -n '/^depends=(/,/^)/p' "$pkgbuild" | - grep -qE "^[[:space:]]*['\"]omarchy-mac-keyring([<>=][^'\"]*)?['\"]([[:space:]]|$)"; then - sed -i "/^depends=(/a\\ 'omarchy-mac-keyring'" "$pkgbuild" + mapfile -t lines <"$pkgbuild" + for index in "${!lines[@]}"; do + line=${lines[$index]} + if [[ $line == 'depends=(' ]]; then + in_depends=1 + insert_index=$index + elif [[ $line == ')' ]]; then + in_depends=0 + elif (( in_depends )); then + scan=${line%%#*} + while [[ $scan =~ $pattern ]]; do + token=${BASH_REMATCH[0]} + suffix=${BASH_REMATCH[1]} + operator=${BASH_REMATCH[2]} + version=${BASH_REMATCH[3]} + replacement="'omarchy-mac-keyring>=$minimum'" + if [[ -n $suffix ]]; then + case "$operator" in + '>='|'>'|'=') + if (( $(vercmp "$version" "$minimum") >= 0 )); then + replacement=$token + elif [[ $operator == '=' ]]; then + fail "Keyring pin predates the required trust transition: $token" + fi + ;; + *) fail "Keyring upper bound needs review for the trust transition: $token" ;; + esac + fi + # Replace only this dependency token, preserving adjacent dependencies. + line=${line//"$token"/"$replacement"} + scan=${scan#*"$token"} + found=1 + done + lines[$index]=$line + fi + done + if (( ! found )); then + lines[$insert_index]+=$'\n'" 'omarchy-mac-keyring>=$minimum'" fi + printf '%s\n' "${lines[@]}" >"$pkgbuild" } # Upstream's package() deletes /etc/mkinitcpio.conf.d wholesale on aarch64, diff --git a/default/pacman/keyrings/omarchy-mac-trusted b/default/pacman/keyrings/omarchy-mac-trusted index 557aa18eee6..56f9bf5360b 100644 --- a/default/pacman/keyrings/omarchy-mac-trusted +++ b/default/pacman/keyrings/omarchy-mac-trusted @@ -1 +1 @@ -F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7:4: +FBD6874D423C418DDB6D143EECE19CDDE306DBD2:4: diff --git a/default/pacman/keyrings/omarchy-mac.gpg b/default/pacman/keyrings/omarchy-mac.gpg index 24fbc026b91..86b8ecb319f 100644 Binary files a/default/pacman/keyrings/omarchy-mac.gpg and b/default/pacman/keyrings/omarchy-mac.gpg differ diff --git a/docs/arm-package-sources.md b/docs/arm-package-sources.md index 7a5fc381e7b..a0c9ee8b49e 100644 --- a/docs/arm-package-sources.md +++ b/docs/arm-package-sources.md @@ -12,7 +12,7 @@ Use `omarchy update` for system upgrades. A bare `pacman -Syu` does not update t The fork-owned repository uses distinct `stable`, `rc`, and `edge` release coordinates under `https://github.com/omarchy-mac/omarchy-pkgs-aarch64/releases/download/`. All three lanes provide `omarchy` and `omarchy-settings`; ARM does not request the x86 `omarchy-dev` pair. Channel reporting reads the managed ARM server, so an older installation pointing at `/edge` reports edge even when its installed package names are `omarchy` and `omarchy-settings`. -Omarchy Mac has a separate package-signing primary key, `F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7`. Its exact public bytes ship in `omarchy-mac-keyring`; upstream `omarchy-keyring` remains installed for upstream packages. The 4.0.3rc4 bootstrap is the final unsigned fork transaction and retains the old repository policy only long enough to deliver and populate this trust. The following RC is signed by subkey `6C2597C6E69FC4898D3D560E331307696030285E` and changes the fork policy to `PackageRequired DatabaseRequired TrustedOnly`. Against that signed lane, a client that skipped the bootstrap cannot verify the candidate and stops before changing packages. +Omarchy Mac's new package-signing primary is `FBD6874D423C418DDB6D143EECE19CDDE306DBD2`, with signing subkey `D791ED0C72439D9F8757421258043B2770A25762`. Only the new fork primary ships in `omarchy-mac-keyring` version `20260914-2`; upstream keyrings remain installed. This local RC4 bootstrap candidate delivers and populates the keyring without changing the existing fork signature policy. New bootstrap configurations use the explicitly disclosed `Optional TrustAll` policy; stricter existing policies are preserved. After actual client delivery and upgrade validation, a separate signed RC5 may activate `PackageRequired DatabaseRequired TrustedOnly`. This candidate contains no strict-signing activation migration, and preparing it does not publish a release. An explicit channel switch goes through the normal update lock, snapshot and migration pipeline. It stages the current pacman configuration, changing only the managed ARM lane and reapplying the existing explicit upstream graphics policy. Other repository ordering, options and mirror Includes are preserved. Custom or ambiguous ARM server/Include layouts are rejected rather than guessed. ARM refresh uses this same path and no longer runs the reset-only `pre-refresh-pacman` hook, because it does not discard and recreate the user's configuration. The x86 reset path retains that hook; normal update hooks still run after successful migrations. @@ -22,6 +22,16 @@ The persistent configuration is committed only after successful package installa This freezes one switch transaction, not future distribution upgrades. Arch Linux ARM, Asahi and the explicitly selected upstream graphics stack still resolve according to their rolling policies on the next update. Record their resolved versions when qualifying an RC; a different resolved stack needs new compatibility evidence. Captured repositories use a task directory beneath `${XDG_CACHE_HOME:-$HOME/.cache}/omarchy/channels`, require disk-backed storage with sufficient free space, and are removed after the transaction. Existing package caches are reused without deleting their archives. +## RC4 trust bootstrap qualification + +The primary changed before signing activation to finalize signing-key management; this is housekeeping, not a compromise response. The package ships only the new primary and an empty `omarchy-mac-revoked` file. Population does not remove previously installed keys: a host that already trusted the old primary retains that trust, while a fresh host receives only the new fork primary. No migration deletes or revokes old or unrelated trust. + +Publish the reviewed `omarchy-mac-keyring` package at `20260914-2` or newer with the RC4 package pair before asking clients to run this source's migrations. Both the pending bootstrap migration and its successor require that installed version. A source/manual update ahead of package publication stops with an explanatory error and remains pending; it must not skip the failed migration or weaken signature policy to continue. + +Before RC4 publication, qualify the actual artifacts on a fresh base and an existing installation. Include a real prior-keyring upgrade and verify that the new primary becomes trusted, old and unrelated installed trust survives, completed migration markers stay skipped, and pending markers appear only after successful trust population. The opt-in native package test covers disposable package hooks and trust when run directly on Arch Linux ARM; it is intentionally separate from the hosted Ubuntu/nspawn install workflow and does not replace physical Mac installation and upgrade qualification. + +A development host that already ran the later strict-signing migration cannot install unsigned RC4 under `PackageRequired DatabaseRequired TrustedOnly`. A migration-marker fixture does not prove that transaction works. Use a clean disposable baseline with the supported RC4 policy for the unsigned canary; do not automatically reset a host's stricter policy or delete its migration markers. Qualify strict signing separately with the signed candidate. + ## Fresh Apple Silicon installation `./install.sh --channel rc` (or `OMARCHY_MIRROR=rc ./install.sh`) installs the published lane's captured `omarchy`/`omarchy-settings` pair. It verifies availability, resolves dependencies and downloads under the configured signature policy before changing locale, packages or active repository configuration. If the base has no managed ARM section, preflight adds one only to its candidate; custom or hidden managed sections must be configured explicitly. The rc4 bootstrap retains the existing `Optional TrustAll` fork policy because its archive and database are intentionally unsigned. This is the final use of that trust model. The following signed RC requires trusted package and database signatures. Required upstream graphics signatures remain required throughout. diff --git a/install.sh b/install.sh index d8de1f8aaeb..f8463b5db1b 100755 --- a/install.sh +++ b/install.sh @@ -11,7 +11,7 @@ set -euo pipefail readonly checkout="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly package_output="$checkout/build-output" readonly asahi_alarm_key="12CE6799A94A3F1B5DDFFE88F576553597FB8FEB" -readonly omarchy_mac_key="F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7" +readonly omarchy_mac_key="FBD6874D423C418DDB6D143EECE19CDDE306DBD2" source "$checkout/install/helpers/arm-package-sources.sh" source "$checkout/install/helpers/arm-channel.sh" install_channel="${OMARCHY_MIRROR:-}" diff --git a/install/helpers/arm-channel.sh b/install/helpers/arm-channel.sh index 93e48e787da..c6c5c085c3d 100644 --- a/install/helpers/arm-channel.sh +++ b/install/helpers/arm-channel.sh @@ -211,6 +211,22 @@ omarchy_arm_channel_key_fingerprints() { awk -F: '$1 == "fpr" { print $10 }' | sort } +# Trust the pinned new primary only inside the private transaction keyring. +omarchy_arm_channel_trust_fork() { + local keyring="$1" + local active_key="FBD6874D423C418DDB6D143EECE19CDDE306DBD2" + local fork_keyfile="${OMARCHY_SIGNING_SOURCE:-$OMARCHY_PATH}/default/pacman/keyrings/omarchy-mac.gpg" + if ! sudo gpg --homedir "$keyring" --batch --list-keys "$active_key" >/dev/null 2>&1; then + [[ -f $fork_keyfile && ! -L $fork_keyfile ]] || { + echo "Pinned Omarchy Mac signing key is missing or unsafe: $fork_keyfile" >&2 + return 1 + } + sudo pacman-key --gpgdir "$keyring" --add "$fork_keyfile" || return 1 + fi + omarchy_arm_channel_key_fingerprints "$keyring" | grep -qxF "$active_key" || return 1 + sudo pacman-key --gpgdir "$keyring" --lsign-key "$active_key" || return 1 +} + # Preflight has no installed-package/config/keyring side effects. The caller # retains this directory until applying or abandoning the captured transaction. omarchy_arm_channel_prepare() { @@ -250,19 +266,7 @@ omarchy_arm_channel_prepare() { if [[ $allow_new == "fresh" ]]; then sudo pacman-key --gpgdir "$stage/keyring" --lsign-key "$key" fi - # The fork key is source-pinned. Import exactly those committed bytes rather - # than consulting a keyserver, then verify the full primary fingerprint. - local fork_key="F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7" - local fork_keyfile="${OMARCHY_SIGNING_SOURCE:-$OMARCHY_PATH}/default/pacman/keyrings/omarchy-mac.gpg" - if ! sudo gpg --homedir "$stage/keyring" --batch --list-keys "$fork_key" >/dev/null 2>&1; then - [[ -f $fork_keyfile && ! -L $fork_keyfile ]] || { - echo "Pinned Omarchy Mac signing key is missing or unsafe: $fork_keyfile" >&2 - return 1 - } - sudo pacman-key --gpgdir "$stage/keyring" --add "$fork_keyfile" - omarchy_arm_channel_key_fingerprints "$stage/keyring" | grep -qxF "$fork_key" || return 1 - fi - sudo pacman-key --gpgdir "$stage/keyring" --lsign-key "$fork_key" + omarchy_arm_channel_trust_fork "$stage/keyring" || return 1 omarchy_arm_channel_key_fingerprints "$stage/keyring" >"$stage/keys-before" local -a probe=(--config "$stage/resolved.conf" --dbpath "$stage/db" --cachedir "$stage/cache" --gpgdir "$stage/keyring" --logfile "$stage/preflight.log") sudo env OMARCHY_UPDATE_PACMAN=1 pacman "${probe[@]}" -Sy --noconfirm diff --git a/migrations/1789316115.sh b/migrations/1789316115.sh index b81f5978f2f..22a80f4b665 100644 --- a/migrations/1789316115.sh +++ b/migrations/1789316115.sh @@ -1,6 +1,6 @@ echo "Trust packages signed by Omarchy Mac" -readonly omarchy_mac_signing_key='F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7' +readonly omarchy_mac_signing_key='FBD6874D423C418DDB6D143EECE19CDDE306DBD2' # The package is a dependency of omarchy, but keep this self-repairing for a # partial/manual upgrade. Do not weaken the repository policy to fetch it. @@ -8,8 +8,29 @@ if omarchy-pkg-missing omarchy-mac-keyring; then omarchy-pkg-add omarchy-mac-keyring fi -sudo pacman-key --populate omarchy-mac -sudo pacman-key --finger "$omarchy_mac_signing_key" | tr -d '[:space:]' | grep -qF "$omarchy_mac_signing_key" +# The helper may skip an unavailable package, and an installed older package +# does not satisfy the bootstrap. Check before explicit trust population or +# completing this marker so a checkout ahead of publication fails clearly. +# If the helper installs a package, that package's own scriptlets still run. +installed_keyring=$(pacman -Q omarchy-mac-keyring) || { + echo "Omarchy Mac keyring 20260914-2 or newer is required; install the reviewed RC4 keyring package before retrying this migration." >&2 + exit 1 +} +installed_version=${installed_keyring#* } +version_comparison=$(vercmp "$installed_version" 20260914-2) || exit 1 +if (( version_comparison < 0 )); then + echo "Omarchy Mac keyring 20260914-2 or newer is required (installed: $installed_version); complete the reviewed RC4 package upgrade before retrying this migration." >&2 + exit 1 +fi + +sudo pacman-key --populate omarchy-mac || { + echo "Could not populate Omarchy Mac signing trust; this migration remains pending." >&2 + exit 1 +} +if ! sudo pacman-key --finger "$omarchy_mac_signing_key" | tr -d '[:space:]' | grep -qF "$omarchy_mac_signing_key"; then + echo "The required Omarchy Mac signing primary $omarchy_mac_signing_key is missing after keyring population." >&2 + exit 1 +fi # Policy remains unchanged for this one disclosed bootstrap transaction. The # next, signed RC carries a successor migration that requires both package and diff --git a/migrations/1789407944.sh b/migrations/1789407944.sh new file mode 100644 index 00000000000..b1ac8801a83 --- /dev/null +++ b/migrations/1789407944.sh @@ -0,0 +1,24 @@ +echo "Populate the finalized Omarchy Mac signing keyring" + +# A successor is required for clients that already marked earlier migrations +# complete. Never remove existing keys or change repository signature policy. +installed=$(pacman -Q omarchy-mac-keyring) || { + echo "Install the reviewed Omarchy Mac keyring package first." >&2 + exit 1 +} +version=${installed#* } +comparison=$(vercmp "$version" 20260914-2) || exit 1 +if (( comparison < 0 )); then + echo "Omarchy Mac keyring 20260914-2 or newer is required." >&2 + exit 1 +fi + +sudo pacman-key --populate omarchy-mac || { + echo "Could not populate Omarchy Mac signing trust; this migration remains pending." >&2 + exit 1 +} +omarchy_mac_signing_key=FBD6874D423C418DDB6D143EECE19CDDE306DBD2 +if ! sudo pacman-key --finger "$omarchy_mac_signing_key" | tr -d '[:space:]' | grep -qF "$omarchy_mac_signing_key"; then + echo "The required Omarchy Mac signing primary $omarchy_mac_signing_key is missing after keyring population." >&2 + exit 1 +fi diff --git a/test/fixtures/omarchy-mac-keyring-20260913-1/PKGBUILD b/test/fixtures/omarchy-mac-keyring-20260913-1/PKGBUILD new file mode 100644 index 00000000000..31e730e6f5c --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260913-1/PKGBUILD @@ -0,0 +1,19 @@ +# Maintainer: Omarchy Mac + +pkgname=omarchy-mac-keyring +pkgver=20260913 +pkgrel=1 +pkgdesc='Omarchy Mac package signing keyring' +arch=(any) +url='https://github.com/omarchy-mac/omarchy-mac' +license=('GPL-3.0-or-later') +install=$pkgname.install +source=('omarchy-mac.gpg' 'omarchy-mac-trusted' 'omarchy-mac-revoked') +sha512sums=('0e411148bf58cb1cf3c1ef544ea0936d82b0d7b3996c3ef3d20ac909959129c1d7cb58dc84ac466cd3e2d75db228aff896640899b4bbbef19e7636f3033a7ea8' + '2fb4497f96fd9f446e0177f9dbf8eb34f0ad3dce5fc7fb687312c5e20a1127ceb68f0d6022424f35b37e2a6a91fb097b2212595375fa4d37d7547697b17de86e' + 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e') + +package() { + install -D -m0644 -t "$pkgdir/usr/share/pacman/keyrings/" \ + omarchy-mac.gpg omarchy-mac-trusted omarchy-mac-revoked +} diff --git a/test/fixtures/omarchy-mac-keyring-20260913-1/README.md b/test/fixtures/omarchy-mac-keyring-20260913-1/README.md new file mode 100644 index 00000000000..4221bebc2d7 --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260913-1/README.md @@ -0,0 +1,5 @@ +# Shipped quattro keyring package fixture + +These five package sources are copied byte-for-byte from `origin/quattro` commit `04862a02c556b5c4305b2ea3430e9d2cfaab66c4`: `build-inputs/omarchy-mac-keyring/{PKGBUILD,omarchy-mac-keyring.install}` and `default/pacman/keyrings/{omarchy-mac.gpg,omarchy-mac-trusted,omarchy-mac-revoked}`. The package version is `20260913-1`. This provenance identifier is informational; running the test requires no Git history or remote access. + +The fixture contains only public certificates, public trust metadata, and package source. It models the old-primary-only package shipped on the quattro baseline, does not revoke keys, and is the real predecessor for upgrade testing. `SHA256SUMS` binds the five original files. The native test verifies this manifest before building and checks that upgrading adds the new primary without disturbing existing old or unrelated client trust. diff --git a/test/fixtures/omarchy-mac-keyring-20260913-1/SHA256SUMS b/test/fixtures/omarchy-mac-keyring-20260913-1/SHA256SUMS new file mode 100644 index 00000000000..5f5f1d0b7d5 --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260913-1/SHA256SUMS @@ -0,0 +1,5 @@ +c749142502810e7a0e002d1a01b34cfe43169c57cd1b820415eb1c6e4c123826 PKGBUILD +604333c2027bf219ad759d7c1e8f8238a6fc3405f8fdb7ab8757c51c27b1cf60 omarchy-mac-keyring.install +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 omarchy-mac-revoked +aad605b6a64277974467fe9c6c74c345d3e095cf19eb8853eec4803faf180268 omarchy-mac-trusted +6e2801dc5c790b817fa5fc99cb2f4b93063042cbc94b2eb9c0e80026d97c6ca0 omarchy-mac.gpg diff --git a/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-keyring.install b/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-keyring.install new file mode 100644 index 00000000000..40a474972ff --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-keyring.install @@ -0,0 +1,11 @@ +post_upgrade() { + if [ -x usr/bin/pacman-key ] && usr/bin/pacman-key -l >/dev/null 2>&1; then + usr/bin/pacman-key --populate omarchy-mac + else + echo ' >>> Initialize pacman-key, then run pacman-key --populate omarchy-mac.' + fi +} + +post_install() { + post_upgrade +} diff --git a/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-revoked b/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-revoked new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-trusted b/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-trusted new file mode 100644 index 00000000000..557aa18eee6 --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac-trusted @@ -0,0 +1 @@ +F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7:4: diff --git a/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac.gpg b/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac.gpg new file mode 100644 index 00000000000..24fbc026b91 --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260913-1/omarchy-mac.gpg @@ -0,0 +1,16 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaqbKtRYJKwYBBAHaRw8BAQdAI7gUCEOd14QplbZ12KYIV7XkM+/wyPPG9QBz +mPvV5bW0G09tYXJjaHkgTWFjIFBhY2thZ2UgU2lnbmluZ4iWBBMWCgA+FiEE88Wu +P8/8c4wwHjCo8MVIwNJyefcFAmqmyrUCGwEFCQlmAYAFCwkIBwIGFQoJCAsCBBYC +AwECHgECF4AACgkQ8MVIwNJyefc4HgEArUgsUks8G6WRTBV5Ef52/djRyIGu7Uai +e5Ih8U3XB9gA/RjtlKVqC3E00ZhN5m4Oc2IbfPNZr8Oh7ZlYdvFq148BuDMEaqbK +3xYJKwYBBAHaRw8BAQdAWA4H8/FHdnZP1Z9NeFkFNmMNIJyDT66oSHcGCxBjLIOI +9AQYFgoAJhYhBPPFrj/P/HOMMB4wqPDFSMDScnn3BQJqpsrfAhsCBQkB4TOAAIAJ +EPDFSMDScnn3dSAEGRYKAB0WIQRsJZfG5p/EiY09Vg4zEwdpYDAoXgUCaqbK3wAK +CRAzEwdpYDAoXvBSAPMGvUcm9mi64yMqX3nkMsi1H7HuvdXmBaRgyllBrqYRAQDA +xFiDGYOtJqPZwpcIupeEn0Z62ScAwjBS5ONqXNfdAjvBAP41jIblDA42gFKa/G8b +xL9fy7HGsg/zpEFBnGsA/EB03wEA02k2hYO0FuV7qo3pmqVqR0MIVygZLEA2sGWB +Jyvh8wU= +=iEoD +-----END PGP PUBLIC KEY BLOCK----- diff --git a/test/fixtures/omarchy-mac-keyring-20260914-1/PKGBUILD b/test/fixtures/omarchy-mac-keyring-20260914-1/PKGBUILD new file mode 100644 index 00000000000..b6085f0a989 --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260914-1/PKGBUILD @@ -0,0 +1,19 @@ +# Maintainer: Omarchy Mac + +pkgname=omarchy-mac-keyring +pkgver=20260914 +pkgrel=1 +pkgdesc='Omarchy Mac package signing keyring' +arch=(any) +url='https://github.com/omarchy-mac/omarchy-mac' +license=('GPL-3.0-or-later') +install=$pkgname.install +source=('omarchy-mac.gpg' 'omarchy-mac-trusted' 'omarchy-mac-revoked') +sha512sums=('1372202bdde5a3f8a79caceed4cff537012f7c1000a63e3cde808842fa913248b5d139641e9dcde378713b18658df8ace30b315a7193415402f614c70ad267a2' + '9414cc6dfca81c1eb098e23908c4e9bfb4bf5ab75ddb75e8b538c204e64ab2e3869f6e91f59886625d6fd0c53c1837db9184b12e4e4450c25175f221ffeca9f9' + 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e') + +package() { + install -D -m0644 -t "$pkgdir/usr/share/pacman/keyrings/" \ + omarchy-mac.gpg omarchy-mac-trusted omarchy-mac-revoked +} diff --git a/test/fixtures/omarchy-mac-keyring-20260914-1/README.md b/test/fixtures/omarchy-mac-keyring-20260914-1/README.md new file mode 100644 index 00000000000..7611f7ed54e --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260914-1/README.md @@ -0,0 +1,5 @@ +# Previous public keyring package fixture + +These five package sources are copied byte-for-byte from local reviewed commit `61e7f31a3b47608325998cad5a1e8912f559e7cb`: `build-inputs/omarchy-mac-keyring/{PKGBUILD,omarchy-mac-keyring.install}` and `default/pacman/keyrings/{omarchy-mac.gpg,omarchy-mac-trusted,omarchy-mac-revoked}`. The package version is `20260914-1`. This provenance identifier is informational; running the test requires no Git history or remote access. + +The fixture contains only public certificates, public trust metadata, and package source. It models the prior two-primary package for upgrade testing; it is not shipped as the current keyring, does not revoke keys, and does not assert this package was publicly released. `SHA256SUMS` binds the five original files. The native test verifies this manifest before building and checks that upgrading preserves preexisting client trust. diff --git a/test/fixtures/omarchy-mac-keyring-20260914-1/SHA256SUMS b/test/fixtures/omarchy-mac-keyring-20260914-1/SHA256SUMS new file mode 100644 index 00000000000..ee818776786 --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260914-1/SHA256SUMS @@ -0,0 +1,5 @@ +edd499ac6fd3b6a0341df630e57a9529e3ce20afb7d3cebc83bd55ee00e5789b PKGBUILD +604333c2027bf219ad759d7c1e8f8238a6fc3405f8fdb7ab8757c51c27b1cf60 omarchy-mac-keyring.install +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 omarchy-mac-revoked +857ab8f968749b38b54e6e821e3c8e8de2b9ac5aae148bf7bb554f5df6664eac omarchy-mac-trusted +0ba3d0465b7583095723dd30c5933a9c6b6f61b0f0daf84ee4315e6724c3de31 omarchy-mac.gpg diff --git a/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-keyring.install b/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-keyring.install new file mode 100644 index 00000000000..40a474972ff --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-keyring.install @@ -0,0 +1,11 @@ +post_upgrade() { + if [ -x usr/bin/pacman-key ] && usr/bin/pacman-key -l >/dev/null 2>&1; then + usr/bin/pacman-key --populate omarchy-mac + else + echo ' >>> Initialize pacman-key, then run pacman-key --populate omarchy-mac.' + fi +} + +post_install() { + post_upgrade +} diff --git a/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-revoked b/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-revoked new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-trusted b/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-trusted new file mode 100644 index 00000000000..87441e0dd8e --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac-trusted @@ -0,0 +1,2 @@ +F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7:4: +FBD6874D423C418DDB6D143EECE19CDDE306DBD2:4: diff --git a/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac.gpg b/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac.gpg new file mode 100644 index 00000000000..68e0bb89c7d --- /dev/null +++ b/test/fixtures/omarchy-mac-keyring-20260914-1/omarchy-mac.gpg @@ -0,0 +1,32 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaqbKtRYJKwYBBAHaRw8BAQdAI7gUCEOd14QplbZ12KYIV7XkM+/wyPPG9QBz +mPvV5bW0G09tYXJjaHkgTWFjIFBhY2thZ2UgU2lnbmluZ4iWBBMWCgA+FiEE88Wu +P8/8c4wwHjCo8MVIwNJyefcFAmqmyrUCGwEFCQlmAYAFCwkIBwIGFQoJCAsCBBYC +AwECHgECF4AACgkQ8MVIwNJyefc4HgEArUgsUks8G6WRTBV5Ef52/djRyIGu7Uai +e5Ih8U3XB9gA/RjtlKVqC3E00ZhN5m4Oc2IbfPNZr8Oh7ZlYdvFq148BuDMEaqbK +3xYJKwYBBAHaRw8BAQdAWA4H8/FHdnZP1Z9NeFkFNmMNIJyDT66oSHcGCxBjLIOI +9AQYFgoAJhYhBPPFrj/P/HOMMB4wqPDFSMDScnn3BQJqpsrfAhsCBQkB4TOAAIAJ +EPDFSMDScnn3dSAEGRYKAB0WIQRsJZfG5p/EiY09Vg4zEwdpYDAoXgUCaqbK3wAK +CRAzEwdpYDAoXvBSAPMGvUcm9mi64yMqX3nkMsi1H7HuvdXmBaRgyllBrqYRAQDA +xFiDGYOtJqPZwpcIupeEn0Z62ScAwjBS5ONqXNfdAjvBAP41jIblDA42gFKa/G8b +xL9fy7HGsg/zpEFBnGsA/EB03wEA02k2hYO0FuV7qo3pmqVqR0MIVygZLEA2sGWB +Jyvh8wU= +=iEoD +-----END PGP PUBLIC KEY BLOCK----- +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaqfthRYJKwYBBAHaRw8BAQdAjGj2EinezISkcb9sk6UuBP0YZ4Vlt74y5GHw +cQZx8Ia0G09tYXJjaHkgTWFjIFBhY2thZ2UgU2lnbmluZ4iWBBMWCgA+FiEE+9aH +TUI8QY3bbRQ+7OGc3eMG29IFAmqn7YUCGwEFCQWjmoAFCwkIBwIGFQoJCAsCBBYC +AwECHgECF4AACgkQ7OGc3eMG29JErQD/ddeOpL7W6Izh3DYgugD/wsI4nYegFaGs +fuXloY5/kKUBAMu5AxRdzYAvhxes12PUgOp6PeJQh5osqq+zx50XN4YDuDMEaqft +hRYJKwYBBAHaRw8BAQdAIZKUzkbLfQifuQAk92YFPtbb+lwFeIQEDICFvvWBkXGI +9QQYFgoAJhYhBPvWh01CPEGN220UPuzhnN3jBtvSBQJqp+2FAhsCBQkB4TOAAIEJ +EOzhnN3jBtvSdiAEGRYKAB0WIQTXke0MckOdn4dXQhJYBDsncKJXYgUCaqfthQAK +CRBYBDsncKJXYoJ4AQCpBxN9ssf+IppBDWJ1DX15UdsBrKbheuXBUdYhzxlvhQD/ +bKa/fXRxGYmh8pTxSCDOH6fy7+Mx/yisU12tHrGAfgLxQAD+MAsOKMW/HpA2T2hu +pFNajI8hd9EtynNy82mJ4pu9uvwA/jjQ8kApBbZaV6STdS2aX3h2J04rmsh0cZKo +7AxGkigO +=hN5B +-----END PGP PUBLIC KEY BLOCK----- diff --git a/test/shell.d/install-mac-snapper-dependency-test.sh b/test/shell.d/install-mac-snapper-dependency-test.sh index ecd2a3ea757..f88a2d01a4f 100644 --- a/test/shell.d/install-mac-snapper-dependency-test.sh +++ b/test/shell.d/install-mac-snapper-dependency-test.sh @@ -43,7 +43,7 @@ PKGBUILD expected_dependency=$dependency [[ $dependency != "absent" ]] || expected_dependency=snapper - expected=$(printf '%s\n' gum omarchy-mac-keyring "$expected_dependency" | sort) + expected=$(printf '%s\n' gum 'omarchy-mac-keyring>=20260914-2' "$expected_dependency" | sort) actual=$(sort "$case_dir/output/omarchy.pkg.tar.zst") [[ $actual == "$expected" ]] || fail "Mac build requires Snapper exactly once and preserves other dependencies ($dependency)" "$actual" diff --git a/test/shell.d/install-vm-workflow-test.sh b/test/shell.d/install-vm-workflow-test.sh index e7829028d1d..3936a80f473 100644 --- a/test/shell.d/install-vm-workflow-test.sh +++ b/test/shell.d/install-vm-workflow-test.sh @@ -31,13 +31,16 @@ checkout = next(step for step in job['steps'] if step.get('uses', '').startswith assert checkout['with'] == {'persist-credentials': 'false'}, 'use the default merge ref without stored credentials' assert not any('actions/cache@' in step.get('uses', '') for step in job['steps']) assert not (root / '.github/workflows/install-vm-selective-edge.yml').exists() +harness = (root / 'test/vm/run-selective-edge').read_text() +assert 'OMARCHY_INSTALL_VM_KEYRING' not in str(workflow), 'hosted nspawn must not claim nested native keyring coverage' +assert 'omarchy-mac-keyring-package-install-test.sh' not in harness, 'hosted nspawn must not run the bwrap test' install_step = next(step for step in job['steps'] if 'bash ./test/vm/run-selective-edge' in step.get('run', '')) install_env = {**job['env'], **install_step.get('env', {})} for key in ('OMARCHY_INSTALL_VM_PACKAGE_SOURCES', 'OMARCHY_INSTALL_VM_IDEMPOTENCY'): assert install_env[key] == '1', f'{key} must be enabled' for key in ('OMARCHY_INSTALL_VM_WORK', 'OMARCHY_INSTALL_VM_CACHE'): assert 'github.run_id' in install_env[key] and 'github.run_attempt' in install_env[key] -print('ok - every PR gets isolated ARM install coverage with both validation modes') +print('ok - every PR gets isolated ARM install coverage with package-source and repeat-install validation') run = install_step['run'] preserved = re.search(r'--preserve-env=([^\s]+)', run).group(1).split(',') diff --git a/test/shell.d/omarchy-mac-installer-trust-test.sh b/test/shell.d/omarchy-mac-installer-trust-test.sh new file mode 100644 index 00000000000..5c23d10f705 --- /dev/null +++ b/test/shell.d/omarchy-mac-installer-trust-test.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -euo pipefail +source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh" +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT +checkout="$ROOT" +new_key=FBD6874D423C418DDB6D143EECE19CDDE306DBD2 +old_key=F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7 +# Extract just the production helper and its pin; never run the installer. +eval "$(sed -n '/^readonly omarchy_mac_key=/p' "$ROOT/install.sh")" +eval "$(sed -n '/^ensure_omarchy_mac_keyring() {$/,/^}$/p' "$ROOT/install.sh")" +[[ $omarchy_mac_key == "$new_key" ]] || fail 'installer pins the wrong primary' +log() { :; } +sudo() { + printf '%s\n' "$*" >>"$test_tmp/calls" + case "$*" in + "pacman-key --list-keys $new_key") [[ $has_new == 1 ]] ;; + "pacman-key --add $checkout/default/pacman/keyrings/omarchy-mac.gpg") + [[ ${reject_import:-0} == 0 ]] || return 1 + has_new=1 ;; + "pacman-key --finger $new_key") + [[ $has_new == 1 ]] || return 1 + printf '%s\n' "${returned_key:-$new_key}" ;; + "pacman-key --lsign-key $new_key") [[ $has_new == 1 ]] ;; + *) return 99 ;; + esac +} +for state in fresh old-only new-only both; do + has_new=0 + [[ $state != new-only && $state != both ]] || has_new=1 + expected_imports=$((1-has_new)) + : >"$test_tmp/calls" + ensure_omarchy_mac_keyring + [[ $(grep -c -- '--add ' "$test_tmp/calls" || true) == "$expected_imports" ]] || fail "$state import count" + grep -qxF "pacman-key --lsign-key $new_key" "$test_tmp/calls" || fail "$state missing new trust" + if grep -qF "$old_key" "$test_tmp/calls"; then fail "$state requested old key"; fi +done +has_new=0 +if (reject_import=1; ensure_omarchy_mac_keyring) >/dev/null 2>&1; then fail 'failed import accepted'; fi +has_new=1 +if (returned_key="$old_key"; ensure_omarchy_mac_keyring) >/dev/null 2>&1; then fail 'wrong fingerprint accepted'; fi +pass 'fresh installer establishes only the new pin and rejects missing or wrong trust' diff --git a/test/shell.d/omarchy-mac-keyring-migration-test.sh b/test/shell.d/omarchy-mac-keyring-migration-test.sh index f5b50472149..32fbf110e81 100644 --- a/test/shell.d/omarchy-mac-keyring-migration-test.sh +++ b/test/shell.d/omarchy-mac-keyring-migration-test.sh @@ -28,13 +28,17 @@ if [[ $1 == pacman-key && $2 == --populate ]]; then exit 0 fi if [[ $1 == pacman-key && $2 == --finger ]]; then - printf 'Key fingerprint = F3C5 AE3F CFFC 738C 301E 30A8 F0C5 48C0 D272 79F7\n' + printf 'Key fingerprint = FBD6 874D 423C 418D DB6D 143E ECE1 9CDD E306 DBD2\n' exit 0 fi exec "$@" SH chmod +x "$test_tmp/bin/sudo" +pacman() { + [[ $* == '-Q omarchy-mac-keyring' ]] || return 99 + printf 'omarchy-mac-keyring 20260914-2\n' +} omarchy-pkg-missing() { return 1; } omarchy-pkg-add() { echo "add $*" >>"$calls"; } export TEST_CALLS="$calls" @@ -67,3 +71,83 @@ if omarchy_arm_signature_policy_apply "$test_tmp/pacman-link.conf" 'PackageRequi fail 'policy application follows a pacman.conf symlink' fi pass 'policy rendering handles formatting and missing overrides while application rejects symlinks' + +# Execute the pending queue as production does, using the real package helpers. +# A source checkout can reach this migration before the required package is +# available. Stub pacman and sudo only; no host package or trust operation runs. +unset -f omarchy-pkg-missing omarchy-pkg-add +export PATH="$ROOT/bin:$PATH" +export TEST_INSTALLED_VERSION="$test_tmp/installed-version" +pacman() { + case "$*" in + '-Q omarchy-mac-keyring') + [[ -s $TEST_INSTALLED_VERSION ]] || return 1 + printf 'omarchy-mac-keyring %s\n' "$(cat "$TEST_INSTALLED_VERSION")" ;; + '-Si omarchy-mac-keyring') [[ $TEST_AVAILABLE == 1 ]] ;; + '-S --noconfirm --needed omarchy-mac-keyring') + printf '%s\n' "$*" >>"$TEST_CALLS" + [[ $TEST_AVAILABLE == 1 ]] || return 1 + printf '%s\n' "$TEST_REPO_VERSION" >"$TEST_INSTALLED_VERSION" ;; + *) return 99 ;; + esac +} +sudo() { + case "$*" in + 'pacman -S --noconfirm --needed omarchy-mac-keyring') pacman "${@:2}" ;; + 'pacman-key --populate omarchy-mac') + printf '%s\n' "$*" >>"$TEST_CALLS" + return "$TEST_POPULATE_FAILURE" ;; + 'pacman-key --finger FBD6874D423C418DDB6D143EECE19CDDE306DBD2') + printf '%s\n' "$*" >>"$TEST_CALLS" + if [[ $TEST_MISSING_KEY == 1 ]]; then + # Even a successful command returning the wrong key must fail the gate. + printf '%s\n' F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7 + else + printf '%s\n' FBD6874D423C418DDB6D143EECE19CDDE306DBD2 + fi ;; + *) return 99 ;; + esac +} +omarchy-notification-dismiss() { :; } +export -f pacman sudo omarchy-notification-dismiss +mkdir -p "$test_tmp/source/migrations" +for name in 1789316115 1789407944; do + cp "$ROOT/migrations/$name.sh" "$test_tmp/source/migrations/" +done +for scenario in current newer old-only combined-old missing install-current install-old populate fingerprint; do + markers="$test_tmp/markers-$scenario" + mkdir "$markers" + printf '20260914-2\n' >"$TEST_INSTALLED_VERSION" + export TEST_AVAILABLE=0 TEST_REPO_VERSION=20260914-2 TEST_POPULATE_FAILURE=0 TEST_MISSING_KEY=0 + case "$scenario" in + newer) printf '20260915-1\n' >"$TEST_INSTALLED_VERSION" ;; + old-only) printf '20260913-1\n' >"$TEST_INSTALLED_VERSION" ;; + combined-old) printf '20260914-1\n' >"$TEST_INSTALLED_VERSION" ;; + missing) : >"$TEST_INSTALLED_VERSION" ;; + install-current) : >"$TEST_INSTALLED_VERSION"; TEST_AVAILABLE=1 ;; + install-old) : >"$TEST_INSTALLED_VERSION"; TEST_AVAILABLE=1; TEST_REPO_VERSION=20260913-1 ;; + populate) TEST_POPULATE_FAILURE=1 ;; + fingerprint) TEST_MISSING_KEY=1 ;; + esac + : >"$TEST_CALLS" + if OMARCHY_PATH="$test_tmp/source" OMARCHY_MIGRATION_STATE="$markers" bash "$ROOT/bin/omarchy-migrate" >"$test_tmp/queue-result" 2>&1; then + case "$scenario" in current|newer|install-current) ;; *) fail "$scenario accepted" ;; esac + [[ -f $markers/1789316115.sh && -f $markers/1789407944.sh ]] || fail 'bootstrap queue did not complete' + : >"$TEST_CALLS" + OMARCHY_PATH="$test_tmp/source" OMARCHY_MIGRATION_STATE="$markers" bash "$ROOT/bin/omarchy-migrate" >/dev/null + [[ ! -s $TEST_CALLS ]] || fail 'completed bootstrap queue reran' + else + case "$scenario" in current|newer|install-current) fail "$scenario failed" "$(cat "$test_tmp/queue-result")" ;; esac + [[ ! -e $markers/1789316115.sh && ! -e $markers/1789407944.sh ]] || fail 'failed bootstrap advanced the queue' + case "$scenario" in + populate) expected_error='Could not populate Omarchy Mac signing trust' ;; + fingerprint) expected_error='is missing after keyring population' ;; + *) + expected_error='20260914-2 or newer is required' + if grep -qF 'pacman-key' "$TEST_CALLS"; then fail 'rejected package touched trust'; fi ;; + esac + grep -qF "$expected_error" "$test_tmp/queue-result" || fail "$scenario has no useful diagnostic" + if grep -qF 'can only `return' "$test_tmp/queue-result"; then fail 'top-level return error'; fi + fi +done +pass 'pending bootstrap checks real helper results and package version before trust, with explicit errors and no later markers on failure' diff --git a/test/shell.d/omarchy-mac-keyring-package-install-test.sh b/test/shell.d/omarchy-mac-keyring-package-install-test.sh new file mode 100644 index 00000000000..a5590f97f57 --- /dev/null +++ b/test/shell.d/omarchy-mac-keyring-package-install-test.sh @@ -0,0 +1,115 @@ +#!/bin/bash +set -euo pipefail +source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh" + +# Explicit opt-in: this builds a package and initializes disposable GPG trust. +if [[ ${OMARCHY_RUN_NATIVE_KEYRING_TEST:-0} != 1 ]]; then + printf 'ok - native keyring package install # SKIP set OMARCHY_RUN_NATIVE_KEYRING_TEST=1\n' + exit 0 +fi +for tool in bwrap makepkg pacman gpg gpgconf findmnt; do require_command "$tool"; done +scratch_parent=${OMARCHY_TEST_TMPDIR:-${TMPDIR:-/var/tmp}} +case $(findmnt -n -o FSTYPE -T "$scratch_parent") in + ''|tmpfs|ramfs) fail 'native keyring test requires disk-backed scratch' ;; +esac +work=$(mktemp -d "$scratch_parent/keyring-package-install-XXXXXX") +trap 'rm -rf -- "$work"' EXIT +export TMPDIR="$work/tmp" TMP="$work/tmp" TEMP="$work/tmp" +mkdir -p "$work"/{tmp,build,root/etc/pacman.d,root/var/lib/pacman,root/var/cache/pacman/pkg,root/usr/share/pacman/keyrings,root/home,root/dev} +cp "$ROOT/build-inputs/omarchy-mac-keyring/"* "$work/build/" +cp "$ROOT/default/pacman/keyrings/"* "$work/build/" +# Build through the real recipe, including its source checksum validation. +(cd "$work/build" && makepkg --nodeps --nosign --nocheck >"$work/build.log" 2>&1) || { + cat "$work/build.log" >&2; fail 'keyring package builds'; +} +packages=("$work/build/"*.pkg.tar.*) +[[ ${#packages[@]} == 1 ]] || fail 'exactly one keyring package was built' +cp "${packages[0]}" "$work/keyring.pkg.tar.zst" +# Exact public-only quattro fixture works in shallow checkouts and source archives. +baseline="$ROOT/test/fixtures/omarchy-mac-keyring-20260913-1" +(cd "$baseline" && sha256sum --check SHA256SUMS) || fail 'previous public fixture checksums' +mkdir "$work/build-previous" +for name in PKGBUILD omarchy-mac-keyring.install omarchy-mac.gpg omarchy-mac-trusted omarchy-mac-revoked; do + cp "$baseline/$name" "$work/build-previous/$name" +done +(cd "$work/build-previous" && makepkg --nodeps --nosign --nocheck >"$work/build-previous.log" 2>&1) || { + cat "$work/build-previous.log" >&2; fail 'previous committed keyring package builds'; +} +previous_packages=("$work/build-previous/"*.pkg.tar.*) +[[ ${#previous_packages[@]} == 1 ]] || fail 'exactly one previous keyring package was built' +cp "${previous_packages[0]}" "$work/previous.pkg.tar.zst" +cat >"$work/root/etc/pacman.conf" <<'CONF' +[options] +Architecture = auto +SigLevel = Never +LocalFileSigLevel = Never +CONF +cat >"$work/check.sh" <<'INNER' +set -euo pipefail +export HOME=/home TMPDIR=/tmp TMP=/tmp TEMP=/tmp +key=FBD6874D423C418DDB6D143EECE19CDDE306DBD2 +# The hook must install payload gracefully before pacman trust is initialized. +pacman -U --noconfirm /work/keyring.pkg.tar.zst >/work/uninitialized.log 2>&1 +grep -q 'Initialize pacman-key' /work/uninitialized.log +[[ ! -e /etc/pacman.d/gnupg/trustdb.gpg ]] +pacman -R --noconfirm omarchy-mac-keyring >/work/remove-uninitialized.log 2>&1 +printf 'ok - package hook leaves uninitialized pacman trust untouched and explains recovery\n' +pacman-key --init >/work/init.log 2>&1 +mkdir -m700 /work/unrelated +# Disposable independent trust must survive installation and upgrade. +gpg --homedir /work/unrelated --batch --passphrase '' --quick-generate-key 'Independent fixture' ed25519 cert 1d >/work/unrelated.log 2>&1 +other=$(gpg --homedir /work/unrelated --with-colons --list-keys 2>/dev/null | awk -F: '$1=="fpr" {print $10; exit}') +gpg --homedir /work/unrelated --export "$other" >/work/unrelated.gpg +add_unrelated() { + pacman-key --add /work/unrelated.gpg >"$1" 2>&1 + pacman-key --lsign-key "$other" >>"$1" 2>&1 +} +add_unrelated /work/add.log +trusted() { + gpg --homedir /etc/pacman.d/gnupg --batch --with-colons --list-keys "$1" 2>/dev/null | + awk -F: '$1=="pub" && ($2=="f" || $2=="u") {ok=1} END {exit !ok}' +} +trusted "$other" +pacman -U --noconfirm /work/keyring.pkg.tar.zst >/work/install.log 2>&1 +for name in omarchy-mac.gpg omarchy-mac-trusted omarchy-mac-revoked; do + cmp "/usr/share/pacman/keyrings/$name" "/work/build/$name" +done +trusted "$key" +trusted "$other" +printf 'ok - actual package post_install establishes new trust and preserves unrelated trust\n' +# Start the upgrade scenario with independent initialized trust. Removing a +# keyring package does not delete keys it previously populated, so reusing the +# fresh-install keyring would manufacture the shipped baseline incorrectly. +pacman -R --noconfirm omarchy-mac-keyring >/work/remove-fresh.log 2>&1 +gpgconf --homedir /etc/pacman.d/gnupg --kill all +rm -rf /etc/pacman.d/gnupg +pacman-key --init >/work/reinit.log 2>&1 +add_unrelated /work/readd.log +trusted "$other" +if gpg --homedir /etc/pacman.d/gnupg --list-keys "$key" >/dev/null 2>&1; then exit 1; fi +pacman -U --noconfirm /work/previous.pkg.tar.zst >/work/previous.log 2>&1 +[[ $(pacman -Q omarchy-mac-keyring) == 'omarchy-mac-keyring 20260913-1' ]] +previous_key=F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7 +trusted "$previous_key" +# The shipped predecessor contains only the old primary; the upgrade must add the new one. +if gpg --homedir /etc/pacman.d/gnupg --list-keys "$key" >/dev/null 2>&1; then exit 1; fi +pacman -U --noconfirm /work/keyring.pkg.tar.zst >/work/upgrade.log 2>&1 +[[ $(pacman -Q omarchy-mac-keyring) == 'omarchy-mac-keyring 20260914-2' ]] +grep -q 'upgrading omarchy-mac-keyring' /work/upgrade.log +trusted "$previous_key" +trusted "$key" +trusted "$other" +printf 'ok - actual shipped 20260913-1 to 20260914-2 upgrade adds new trust and preserves old and unrelated trust\n' +INNER +# No host home, /etc, /var, device tree, network, or sockets are exposed. +# All writable paths including /tmp live under verified disk-backed scratch. +bwrap --unshare-all --die-with-parent --new-session --uid 0 --gid 0 \ + --bind "$work/root" / --ro-bind /usr/bin /usr/bin --ro-bind /usr/lib /usr/lib \ + --ro-bind /usr/share/makepkg /usr/share/makepkg \ + --symlink usr/bin /bin --symlink usr/bin /sbin --symlink usr/lib /lib \ + --dev-bind /dev/null /dev/null --dev-bind /dev/random /dev/random --dev-bind /dev/urandom /dev/urandom \ + --bind "$work" /work --bind "$work/tmp" /tmp --proc /proc \ + /bin/bash /work/check.sh || { + for log in "$work"/*.log; do printf '%s\n' "${log##*/}" >&2; tail -20 "$log" >&2; done + fail 'isolated real package install/upgrade trust validation'; + } diff --git a/test/shell.d/omarchy-mac-keyring-successor-test.sh b/test/shell.d/omarchy-mac-keyring-successor-test.sh new file mode 100644 index 00000000000..c5b808c4fa9 --- /dev/null +++ b/test/shell.d/omarchy-mac-keyring-successor-test.sh @@ -0,0 +1,70 @@ +#!/bin/bash +set -euo pipefail +source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT +export TEST_CALLS="$test_tmp/calls" +new_key=FBD6874D423C418DDB6D143EECE19CDDE306DBD2 +export TEST_NEW_KEY="$new_key" +mkdir -p "$test_tmp/source/migrations" +for name in 1789316115 1789407944; do + cp "$ROOT/migrations/$name.sh" "$test_tmp/source/migrations/" +done +pacman() { + [[ $* == '-Q omarchy-mac-keyring' ]] || return 99 + [[ ${TEST_VERSION:-20260914-2} != missing ]] || return 1 + printf 'omarchy-mac-keyring %s\n' "${TEST_VERSION:-20260914-2}" +} +sudo() { + printf '%s\n' "$*" >>"$TEST_CALLS" + case "$*" in + 'pacman-key --populate omarchy-mac') return "${TEST_POPULATE_FAILURE:-0}" ;; + "pacman-key --finger $TEST_NEW_KEY") + [[ ${TEST_MISSING_KEY:-0} == 0 ]] || return 1 + printf '%s\n' "$TEST_NEW_KEY" ;; + *) return 99 ;; + esac +} +omarchy-notification-dismiss() { :; } +export -f pacman sudo omarchy-notification-dismiss + +for scenario in current newer missing older populate fingerprint; do + markers="$test_tmp/$scenario" + mkdir "$markers" + # Obsolete development markers must not prevent the retained successor. + # This models marker handling only, not installing unsigned RC4 under strict policy. + touch "$markers/1789316115.sh" "$markers/1789317000.sh" "$markers/1789390468.sh" + export TEST_VERSION=20260914-2 TEST_POPULATE_FAILURE=0 TEST_MISSING_KEY=0 + case "$scenario" in + newer) TEST_VERSION=20260915-1 ;; + missing) TEST_VERSION=missing ;; + older) TEST_VERSION=20260914-1 ;; + populate) TEST_POPULATE_FAILURE=1 ;; + fingerprint) TEST_MISSING_KEY=1 ;; + esac + : >"$TEST_CALLS" + if OMARCHY_PATH="$test_tmp/source" OMARCHY_MIGRATION_STATE="$markers" bash "$ROOT/bin/omarchy-migrate" >"$test_tmp/result" 2>&1; then + [[ $scenario == current || $scenario == newer ]] || fail "$scenario accepted" + [[ -f $markers/1789407944.sh ]] || fail 'successor marker missing' + [[ $(cat "$TEST_CALLS") == "pacman-key --populate omarchy-mac"$'\n'"pacman-key --finger $new_key" ]] || fail 'unexpected trust operations' + : >"$TEST_CALLS" + OMARCHY_PATH="$test_tmp/source" OMARCHY_MIGRATION_STATE="$markers" bash "$ROOT/bin/omarchy-migrate" >/dev/null + [[ ! -s $TEST_CALLS ]] || fail 'completed successor ran twice' + else + [[ $scenario != current && $scenario != newer ]] || fail "$scenario failed" "$(cat "$test_tmp/result")" + [[ ! -f $markers/1789407944.sh ]] || fail 'failed successor marked complete' + case "$scenario" in + older) expected_error='20260914-2 or newer is required' ;; + missing) expected_error='Install the reviewed Omarchy Mac keyring package first' ;; + populate) expected_error='Could not populate Omarchy Mac signing trust' ;; + fingerprint) expected_error='is missing after keyring population' ;; + esac + grep -qF "$expected_error" "$test_tmp/result" || fail "$scenario has no useful diagnostic" + if grep -qF 'can only `return' "$test_tmp/result"; then fail 'top-level return error'; fi + if [[ $scenario == older || $scenario == missing ]]; then + [[ ! -s $TEST_CALLS ]] || fail 'rejected package still touched trust' + fi + fi +done +pass 'successor repairs completed migrations, requires current package and remains pending on failure' diff --git a/test/shell.d/omarchy-mac-keyring-transition-test.sh b/test/shell.d/omarchy-mac-keyring-transition-test.sh new file mode 100644 index 00000000000..f73b221f115 --- /dev/null +++ b/test/shell.d/omarchy-mac-keyring-transition-test.sh @@ -0,0 +1,58 @@ +#!/bin/bash +set -euo pipefail +source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT +export TEST_TRUST_CALLS="$test_tmp/calls" OMARCHY_PATH="$ROOT" +old_key=F3C5AE3FCFFC738C301E30A8F0C548C0D27279F7 +new_key=FBD6874D423C418DDB6D143EECE19CDDE306DBD2 +export TEST_OLD_KEY="$old_key" TEST_NEW_KEY="$new_key" + +# Migration queue coverage lives in the migration and successor suites. +source "$ROOT/install/helpers/arm-channel.sh" +omarchy_arm_channel_key_fingerprints() { printf '%s\n' "${fixture_keys[@]}"; } +sudo() { + printf '%s\n' "$*" >>"$TEST_TRUST_CALLS" + if [[ $1 == gpg && $4 == --batch && $5 == --list-keys ]]; then + printf '%s\n' "${fixture_keys[@]}" | grep -qxF "$6" + elif [[ $1 == pacman-key && $4 == --add ]]; then + [[ $5 == "$ROOT/default/pacman/keyrings/omarchy-mac.gpg" ]] || return 99 + [[ ${TEST_INCOMPLETE_CERT:-0} == 1 ]] || fixture_keys+=("$new_key") + return 0 + elif [[ $1 == pacman-key && $4 == --lsign-key ]]; then + [[ $5 == "$new_key" ]] + else + return 99 + fi +} +for state in fresh old-only new-only both; do + fixture_keys=() + case "$state" in + old-only) fixture_keys=("$old_key") ;; + new-only) fixture_keys=("$new_key") ;; + both) fixture_keys=("$old_key" "$new_key") ;; + esac + : >"$TEST_TRUST_CALLS" + omarchy_arm_channel_trust_fork "$test_tmp/private-keyring" + if grep -qxF "pacman-key --gpgdir $test_tmp/private-keyring --lsign-key $old_key" "$TEST_TRUST_CALLS"; then fail "$state authorized old trust"; fi + grep -qxF "pacman-key --gpgdir $test_tmp/private-keyring --lsign-key $new_key" "$TEST_TRUST_CALLS" || fail "$state did not establish new trust" + expected=1 + [[ $state != both && $state != new-only ]] || expected=0 + [[ $(grep -c -- '--add ' "$TEST_TRUST_CALLS" || true) == "$expected" ]] || fail "$state certificate import count differs" +done +fixture_keys=() +if TEST_INCOMPLETE_CERT=1 omarchy_arm_channel_trust_fork "$test_tmp/private-keyring"; then + fail 'fresh trust accepts a certificate missing the replacement primary' +fi +pass 'fresh and channel trust stages require the new primary for fresh and old-only clients' + +[[ $(cat "$ROOT/default/pacman/keyrings/omarchy-mac-trusted") == "$new_key:4:" ]] || fail 'packaged trusted primaries differ from transition pins' +( + source "$ROOT/build-inputs/omarchy-mac-keyring/PKGBUILD" + [[ $pkgver == 20260914 && $pkgrel == 2 ]] + for index in "${!source[@]}"; do + [[ $(sha512sum "$ROOT/default/pacman/keyrings/${source[$index]}" | cut -d' ' -f1) == "${sha512sums[$index]}" ]] + done +) || fail 'versioned package recipe must bind exact public trust files' +pass 'packaged public trust and checksums match the versioned transition' diff --git a/test/shell.d/package-build-contract-test.sh b/test/shell.d/package-build-contract-test.sh index a44d72c9173..d7b7b6c4bea 100755 --- a/test/shell.d/package-build-contract-test.sh +++ b/test/shell.d/package-build-contract-test.sh @@ -30,15 +30,62 @@ pass 'source version replaces stale recipe version and preserves explicit pkgrel cat >"$work_dir/omarchy-PKGBUILD" <<'RECIPE' depends=( 'omarchy-settings' + 'omarchy-mac-keyring' ) RECIPE ensure_omarchy_mac_keyring_dependency "$work_dir/omarchy-PKGBUILD" ensure_omarchy_mac_keyring_dependency "$work_dir/omarchy-PKGBUILD" - [[ $(grep -c "^[[:space:]]*'omarchy-mac-keyring'$" "$work_dir/omarchy-PKGBUILD") == 1 ]] + [[ $(grep -c "^[[:space:]]*'omarchy-mac-keyring>=20260914-2'$" "$work_dir/omarchy-PKGBUILD") == 1 ]] [[ " ${packages[*]} " == *' omarchy-mac-keyring '* ]] + source "$work_dir/omarchy-PKGBUILD" + [[ $(vercmp 20260913-1 "${depends[1]#*>=}") == -1 ]] + [[ $(vercmp 20260914-2 "${depends[1]#*>=}") == 0 ]] ) || fail 'omarchy package depends exactly once on the fork keyring' pass 'build includes the fork keyring and makes it an Omarchy dependency' +( + source "$ROOT/build-packages.sh" || exit 1 + for requirement in \ + omarchy-mac-keyring \ + 'omarchy-mac-keyring>=20260913-1' \ + 'omarchy-mac-keyring>=20260915-1' \ + 'omarchy-mac-keyring>20260913-1' \ + 'omarchy-mac-keyring>20260915-1' \ + 'omarchy-mac-keyring=20260914-2' \ + 'omarchy-mac-keyring=20260915-1'; do + printf "depends=(\n 'before' '%s' 'after' # preserved comment\n)\n" "$requirement" >"$work_dir/shared-line" || exit 1 + ensure_omarchy_mac_keyring_dependency "$work_dir/shared-line" || exit 1 + cp "$work_dir/shared-line" "$work_dir/once" || exit 1 + ensure_omarchy_mac_keyring_dependency "$work_dir/shared-line" || exit 1 + cmp "$work_dir/once" "$work_dir/shared-line" || exit 1 + source "$work_dir/shared-line" || exit 1 + [[ ${depends[0]} == before && ${depends[2]} == after && ${#depends[@]} == 3 ]] || exit 1 + expected='omarchy-mac-keyring>=20260914-2' + case $requirement in + 'omarchy-mac-keyring>=20260915-1'|'omarchy-mac-keyring>20260915-1'|'omarchy-mac-keyring=20260914-2'|'omarchy-mac-keyring=20260915-1') + expected=$requirement + ;; + esac + [[ ${depends[1]} == "$expected" ]] || exit 1 + grep -qF '# preserved comment' "$work_dir/shared-line" || exit 1 + done +) || fail 'keyring transition must preserve adjacent dependencies and stronger existing bounds' +pass 'keyring dependency normalization preserves shared lines and stronger version requirements' + +( + source "$ROOT/build-packages.sh" + for requirement in \ + 'omarchy-mac-keyring=20260913-1' \ + 'omarchy-mac-keyring<20260914-2' \ + 'omarchy-mac-keyring<=20260914-2'; do + printf "depends=(\n '%s'\n)\n" "$requirement" >"$work_dir/rejected-keyring-bound" + if ( ensure_omarchy_mac_keyring_dependency "$work_dir/rejected-keyring-bound" 2>/dev/null ); then + exit 1 + fi + done +) || fail 'stale exact pins and upper bounds must fail closed' +pass 'keyring dependency normalization rejects stale exact pins and upper bounds' + # Reject dirty/wrong default sources before touching an existing output. ( source "$ROOT/build-inputs/prepare-recipes.sh" @@ -49,14 +96,14 @@ pass 'build includes the fork keyring and makes it an Omarchy dependency' pass 'release builds reject unversioned recipe inputs before staging' ( - source "$ROOT/build-inputs/prepare-recipes.sh" + source "$ROOT/build-inputs/prepare-recipes.sh" || exit 1 OMARCHY_ALLOW_CUSTOM_RECIPES=1 prepare_omarchy_recipes \ - "$recipe_source" "$work_dir/with-keyring" >/dev/null + "$recipe_source" "$work_dir/with-keyring" >/dev/null || exit 1 keyring="$work_dir/with-keyring/pkgbuilds/omarchy-mac-keyring" - [[ -f $keyring/PKGBUILD && -f $keyring/omarchy-mac-keyring.install ]] - cmp "$ROOT/default/pacman/keyrings/omarchy-mac.gpg" "$keyring/omarchy-mac.gpg" - cmp "$ROOT/default/pacman/keyrings/omarchy-mac-trusted" "$keyring/omarchy-mac-trusted" - [[ ! -s $keyring/omarchy-mac-revoked ]] + [[ -f $keyring/PKGBUILD && -f $keyring/omarchy-mac-keyring.install ]] || exit 1 + cmp "$ROOT/default/pacman/keyrings/omarchy-mac.gpg" "$keyring/omarchy-mac.gpg" || exit 1 + cmp "$ROOT/default/pacman/keyrings/omarchy-mac-trusted" "$keyring/omarchy-mac-trusted" || exit 1 + [[ ! -s $keyring/omarchy-mac-revoked ]] || exit 1 ) || fail 'prepared recipes contain the exact pinned fork keyring payload' pass 'prepared recipes carry exact fork-owned trust bytes' diff --git a/test/shell.d/rc4-bootstrap-boundary-test.sh b/test/shell.d/rc4-bootstrap-boundary-test.sh new file mode 100644 index 00000000000..47eceeb5161 --- /dev/null +++ b/test/shell.d/rc4-bootstrap-boundary-test.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -euo pipefail +source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh" +source "$ROOT/install/helpers/arm-channel.sh" +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +[[ $(cat "$ROOT/version") == 4.0.3rc4 ]] || fail 'bootstrap source version is not RC4' +[[ ! -e $ROOT/migrations/1789317000.sh ]] || fail 'RC5 strict migration leaked into RC4' +[[ ! -e $ROOT/migrations/1789390468.sh ]] || fail 'redundant transition migration remains in RC4' +for policy in 'Optional TrustAll' 'PackageRequired DatabaseRequired TrustedOnly'; do + cat >"$test_tmp/config" <>"$TEST_CALLS" + case "$*" in + 'pacman-key --populate omarchy-mac') return 0 ;; + 'pacman-key --finger FBD6874D423C418DDB6D143EECE19CDDE306DBD2') + printf '%s\n' FBD6874D423C418DDB6D143EECE19CDDE306DBD2 ;; + *) return 99 ;; + esac +} +pacman() { + [[ $* == '-Q omarchy-mac-keyring' ]] || return 99 + printf '%s\n' 'omarchy-mac-keyring 20260914-2' +} +omarchy-pkg-missing() { return 1; } +omarchy-notification-dismiss() { :; } +export -f sudo pacman omarchy-pkg-missing omarchy-notification-dismiss +mkdir -p "$test_tmp/source/migrations" "$test_tmp/markers" +for name in 1789316115 1789407944; do + cp "$ROOT/migrations/$name.sh" "$test_tmp/source/migrations/" +done +OMARCHY_PATH="$test_tmp/source" OMARCHY_MIGRATION_STATE="$test_tmp/markers" bash "$ROOT/bin/omarchy-migrate" >/dev/null +for name in 1789316115 1789407944; do + [[ -f $test_tmp/markers/$name.sh ]] || fail "$name did not complete" +done +[[ $(grep -c '^pacman-key --populate omarchy-mac$' "$TEST_CALLS") == 2 ]] || fail 'not all trust stages populated' +[[ $(wc -l <"$TEST_CALLS") == 4 ]] || fail 'unexpected privileged operation' +: >"$TEST_CALLS" +OMARCHY_PATH="$test_tmp/source" OMARCHY_MIGRATION_STATE="$test_tmp/markers" bash "$ROOT/bin/omarchy-migrate" >/dev/null +[[ ! -s $TEST_CALLS ]] || fail 'completed bootstrap reran' +pass 'all pending RC4 trust migrations complete without old-key or policy operations'