From 05ff6cb34fd70be994938250fbe1acb05339f488 Mon Sep 17 00:00:00 2001 From: Astrid Date: Fri, 4 Sep 2026 01:30:38 +0000 Subject: [PATCH 1/4] Add schist-bin, the Schist image editor, to the fast ring --- pkgbuilds/schist-bin/.omarchy/package.json | 4 + pkgbuilds/schist-bin/.omarchy/upstream.sh | 98 ++++++++++++++++++++++ pkgbuilds/schist-bin/PKGBUILD | 43 ++++++++++ 3 files changed, 145 insertions(+) create mode 100644 pkgbuilds/schist-bin/.omarchy/package.json create mode 100755 pkgbuilds/schist-bin/.omarchy/upstream.sh create mode 100644 pkgbuilds/schist-bin/PKGBUILD diff --git a/pkgbuilds/schist-bin/.omarchy/package.json b/pkgbuilds/schist-bin/.omarchy/package.json new file mode 100644 index 000000000..db153c3eb --- /dev/null +++ b/pkgbuilds/schist-bin/.omarchy/package.json @@ -0,0 +1,4 @@ +{ + "source": "local", + "release_ring": "fast" +} diff --git a/pkgbuilds/schist-bin/.omarchy/upstream.sh b/pkgbuilds/schist-bin/.omarchy/upstream.sh new file mode 100755 index 000000000..f3454c81e --- /dev/null +++ b/pkgbuilds/schist-bin/.omarchy/upstream.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Schist ships pacman-format payloads as GitHub release assets, and GitHub's +# release API reports a SHA-256 digest for every asset, so an update costs one +# small request and never downloads the 35 MB packages themselves. +# +# The asset name embeds upstream's own package release (the PKGBUILD's +# _relver), which only moves when the packaging is re-cut under a version +# that has already shipped. This hook can only rewrite pkgver and the +# checksum arrays, so a release whose payloads carry a different release +# number stops the sync rather than pinning checksums to an asset name the +# build would then fail to fetch. +set -euo pipefail + +REPO="Infrawrench/schist" +ARCHES=(x86_64 aarch64) + +current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +relver=$(grep -m1 '^_relver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") +if [[ ! $relver =~ ^[0-9]+$ ]]; then + echo "PKGBUILD carries no numeric _relver" >&2 + exit 1 +fi + +curl_args=(-fsSL -H 'Accept: application/vnd.github+json') +[[ -n ${GITHUB_TOKEN:-} ]] && curl_args+=(-H "Authorization: Bearer $GITHUB_TOKEN") +releases=$(curl "${curl_args[@]}" "https://api.github.com/repos/$REPO/releases?per_page=100") + +# Newest published, non-prerelease vX.Y.Z tag by vercmp's ordering, which is +# what bin/sync-upstream and pacman both use. A release younger than the +# quarantine window (MIN_RELEASE_AGE_SECONDS, when the package sets one) is +# passed over for the newest one that has cleared it. +min_age=${MIN_RELEASE_AGE_SECONDS:-0} +now=$(date -u +%s) +best_version="" best_release="" +while IFS=$'\t' read -r tag published_at; do + [[ $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue + version=${BASH_REMATCH[1]} + published_epoch=$(date --date="$published_at" +%s 2>/dev/null) || { + echo "Release $tag has an unusable published_at: '$published_at'" >&2 + exit 1 + } + (( now - published_epoch >= min_age )) || continue + if [[ -z $best_version ]] || (( $(vercmp "$version" "$best_version") > 0 )); then + best_version=$version + best_release=$tag + fi +done < <(jq -r '.[] | select(.draft == false and .prerelease == false) | [.tag_name, .published_at] | @tsv' <<<"$releases") + +if [[ -z $best_version ]]; then + echo "No usable release found for $REPO" >&2 + exit 1 +fi + +if [[ -n $current ]] && (( $(vercmp "$best_version" "$current") <= 0 )); then + echo '{}' + exit 0 +fi + +release=$(jq --arg tag "$best_release" '.[] | select(.tag_name == $tag)' <<<"$releases") +published_at=$(jq -r '.published_at' <<<"$release") + +declare -A checksums=() +for arch in "${ARCHES[@]}"; do + # Every payload for this arch, whatever its release number: a payload + # under another number means _relver has to be edited by hand first. + mapfile -t names < <(jq -r --arg prefix "schist-$best_version-" --arg suffix "-$arch.pkg.tar.zst" \ + '.assets[].name | select(startswith($prefix) and endswith($suffix))' <<<"$release") + expected="schist-$best_version-$relver-$arch.pkg.tar.zst" + found=false + for name in "${names[@]}"; do + if [[ $name == "$expected" ]]; then + found=true + else + echo "Release $best_release ships $name, but the PKGBUILD's _relver=$relver expects $expected" >&2 + exit 1 + fi + done + if [[ $found != true ]]; then + # The release workflow uploads one architecture at a time; report no + # update until both payloads have landed and the next run picks it up. + echo "Release $best_release has no $expected yet; skipping" >&2 + echo '{}' + exit 0 + fi + digest=$(jq -r --arg name "$expected" '.assets[] | select(.name == $name) | .digest // empty' <<<"$release") + if [[ ! $digest =~ ^sha256:([0-9a-f]{64})$ ]]; then + echo "Asset $expected reports no SHA-256 digest ('$digest')" >&2 + exit 1 + fi + checksums[$arch]=${BASH_REMATCH[1]} +done + +jq -n \ + --arg pkgver "$best_version" \ + --arg published_at "$published_at" \ + --arg x86_64 "${checksums[x86_64]}" \ + --arg aarch64 "${checksums[aarch64]}" \ + '{pkgver: $pkgver, published_at: $published_at, sha256sums: {x86_64: [$x86_64], aarch64: [$aarch64]}}' diff --git a/pkgbuilds/schist-bin/PKGBUILD b/pkgbuilds/schist-bin/PKGBUILD new file mode 100644 index 000000000..9a659ebae --- /dev/null +++ b/pkgbuilds/schist-bin/PKGBUILD @@ -0,0 +1,43 @@ +# 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. +_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 same list the source package (../schist/PKGBUILD) carries: +# fontconfig/wayland/vulkan-icd-loader are dlopen'd, so namcap flags them +# "may not be needed" — they are. vulkan-driver is the virtual package +# every ICD provides; without one the app gets as far as the window and no +# further, and vulkan-swrast satisfies it in software. +depends=(fontconfig freetype2 hicolor-icon-theme libxcb libxkbcommon + libxkbcommon-x11 vulkan-driver vulkan-icd-loader wayland) +# dlopen'd at run time; the app starts and opens everything else +# without it. +optdepends=('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 5ed1bd90e87d49983a47271c5c1d11079f6f2622 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 4 Sep 2026 03:30:30 -0700 Subject: [PATCH 2/4] Let the GitHub upstream provider read release API asset digests A vendor that publishes no checksum manifest can declare "digests": true instead of "checksums", and the provider takes each asset's SHA-256 from the digest GitHub's release API reports, so the sync still never downloads the artifacts. Exactly one of the two must be set. Co-Authored-By: Claude Fable 5.1 --- README.md | 12 +++-- bin/sync-upstream | 102 ++++++++++++++++++++++++++++++++++-- helpers/package-metadata.sh | 7 ++- helpers/upstream-github.sh | 55 ++++++++++++++----- 4 files changed, 152 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 34e761494..5e448da12 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 @@ -586,7 +592,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 a0da5f3cd..8a0f96602 100755 --- a/bin/sync-upstream +++ b/bin/sync-upstream @@ -18,9 +18,10 @@ 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 +A package whose upstream ships tagged GitHub releases opts in declaratively, +via "upstream" in .omarchy/package.json (see helpers/upstream-github.sh for the +schema): checksums come from a manifest asset the vendor publishes, or from the +per-asset digests GitHub's release API reports. 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: @@ -461,8 +462,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 @@ -562,6 +563,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: {}}') @@ -614,6 +685,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" 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" diff --git a/helpers/package-metadata.sh b/helpers/package-metadata.sh index c121b46a4..b8747a6c5 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" } } } # # bin/sync-aur also writes upstream_commit for AUR-backed packages, and # bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on. @@ -450,13 +451,15 @@ validate_package_metadata() { 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 (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) ))) 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"): needs github owner/repo, exactly one of a checksums asset name or digests: true, and an assets arch->name map" return 1 fi diff --git a/helpers/upstream-github.sh b/helpers/upstream-github.sh index e37e6ab4e..be0c296b7 100644 --- a/helpers/upstream-github.sh +++ b/helpers/upstream-github.sh @@ -1,8 +1,7 @@ # GitHub-releases upstream provider 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,6 +12,10 @@ # } # } # +# "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 @@ -51,7 +54,7 @@ github_fetch_checksums() { # 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") @@ -59,9 +62,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 @@ -129,15 +143,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 @@ -146,11 +161,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") From 132e120d6fcb2b983b60d87405fe3620821dc7f8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 4 Sep 2026 03:30:30 -0700 Subject: [PATCH 3/4] Track Schist releases declaratively with a 24h quarantine The provider now reads release API digests, which is all the bespoke hook did, so the package becomes a plain upstream declaration. Schist has cut fourteen releases in its first fourteen days, so fresh releases wait a day before the scheduled sync picks them up, as mise-bin already does. Co-Authored-By: Claude Fable 5.1 --- pkgbuilds/schist-bin/.omarchy/package.json | 11 ++- pkgbuilds/schist-bin/.omarchy/upstream.sh | 98 ---------------------- pkgbuilds/schist-bin/PKGBUILD | 1 + 3 files changed, 11 insertions(+), 99 deletions(-) delete mode 100755 pkgbuilds/schist-bin/.omarchy/upstream.sh diff --git a/pkgbuilds/schist-bin/.omarchy/package.json b/pkgbuilds/schist-bin/.omarchy/package.json index db153c3eb..8d64039ce 100644 --- a/pkgbuilds/schist-bin/.omarchy/package.json +++ b/pkgbuilds/schist-bin/.omarchy/package.json @@ -1,4 +1,13 @@ { "source": "local", - "release_ring": "fast" + "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/.omarchy/upstream.sh b/pkgbuilds/schist-bin/.omarchy/upstream.sh deleted file mode 100755 index f3454c81e..000000000 --- a/pkgbuilds/schist-bin/.omarchy/upstream.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# Schist ships pacman-format payloads as GitHub release assets, and GitHub's -# release API reports a SHA-256 digest for every asset, so an update costs one -# small request and never downloads the 35 MB packages themselves. -# -# The asset name embeds upstream's own package release (the PKGBUILD's -# _relver), which only moves when the packaging is re-cut under a version -# that has already shipped. This hook can only rewrite pkgver and the -# checksum arrays, so a release whose payloads carry a different release -# number stops the sync rather than pinning checksums to an asset name the -# build would then fail to fetch. -set -euo pipefail - -REPO="Infrawrench/schist" -ARCHES=(x86_64 aarch64) - -current=$(grep -m1 '^pkgver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -relver=$(grep -m1 '^_relver=' PKGBUILD | cut -d= -f2- | tr -d "\"'") -if [[ ! $relver =~ ^[0-9]+$ ]]; then - echo "PKGBUILD carries no numeric _relver" >&2 - exit 1 -fi - -curl_args=(-fsSL -H 'Accept: application/vnd.github+json') -[[ -n ${GITHUB_TOKEN:-} ]] && curl_args+=(-H "Authorization: Bearer $GITHUB_TOKEN") -releases=$(curl "${curl_args[@]}" "https://api.github.com/repos/$REPO/releases?per_page=100") - -# Newest published, non-prerelease vX.Y.Z tag by vercmp's ordering, which is -# what bin/sync-upstream and pacman both use. A release younger than the -# quarantine window (MIN_RELEASE_AGE_SECONDS, when the package sets one) is -# passed over for the newest one that has cleared it. -min_age=${MIN_RELEASE_AGE_SECONDS:-0} -now=$(date -u +%s) -best_version="" best_release="" -while IFS=$'\t' read -r tag published_at; do - [[ $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]] || continue - version=${BASH_REMATCH[1]} - published_epoch=$(date --date="$published_at" +%s 2>/dev/null) || { - echo "Release $tag has an unusable published_at: '$published_at'" >&2 - exit 1 - } - (( now - published_epoch >= min_age )) || continue - if [[ -z $best_version ]] || (( $(vercmp "$version" "$best_version") > 0 )); then - best_version=$version - best_release=$tag - fi -done < <(jq -r '.[] | select(.draft == false and .prerelease == false) | [.tag_name, .published_at] | @tsv' <<<"$releases") - -if [[ -z $best_version ]]; then - echo "No usable release found for $REPO" >&2 - exit 1 -fi - -if [[ -n $current ]] && (( $(vercmp "$best_version" "$current") <= 0 )); then - echo '{}' - exit 0 -fi - -release=$(jq --arg tag "$best_release" '.[] | select(.tag_name == $tag)' <<<"$releases") -published_at=$(jq -r '.published_at' <<<"$release") - -declare -A checksums=() -for arch in "${ARCHES[@]}"; do - # Every payload for this arch, whatever its release number: a payload - # under another number means _relver has to be edited by hand first. - mapfile -t names < <(jq -r --arg prefix "schist-$best_version-" --arg suffix "-$arch.pkg.tar.zst" \ - '.assets[].name | select(startswith($prefix) and endswith($suffix))' <<<"$release") - expected="schist-$best_version-$relver-$arch.pkg.tar.zst" - found=false - for name in "${names[@]}"; do - if [[ $name == "$expected" ]]; then - found=true - else - echo "Release $best_release ships $name, but the PKGBUILD's _relver=$relver expects $expected" >&2 - exit 1 - fi - done - if [[ $found != true ]]; then - # The release workflow uploads one architecture at a time; report no - # update until both payloads have landed and the next run picks it up. - echo "Release $best_release has no $expected yet; skipping" >&2 - echo '{}' - exit 0 - fi - digest=$(jq -r --arg name "$expected" '.assets[] | select(.name == $name) | .digest // empty' <<<"$release") - if [[ ! $digest =~ ^sha256:([0-9a-f]{64})$ ]]; then - echo "Asset $expected reports no SHA-256 digest ('$digest')" >&2 - exit 1 - fi - checksums[$arch]=${BASH_REMATCH[1]} -done - -jq -n \ - --arg pkgver "$best_version" \ - --arg published_at "$published_at" \ - --arg x86_64 "${checksums[x86_64]}" \ - --arg aarch64 "${checksums[aarch64]}" \ - '{pkgver: $pkgver, published_at: $published_at, sha256sums: {x86_64: [$x86_64], aarch64: [$aarch64]}}' diff --git a/pkgbuilds/schist-bin/PKGBUILD b/pkgbuilds/schist-bin/PKGBUILD index 9a659ebae..6934c1514 100644 --- a/pkgbuilds/schist-bin/PKGBUILD +++ b/pkgbuilds/schist-bin/PKGBUILD @@ -5,6 +5,7 @@ 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) From d52357a25811bb4820bd9556efcc7221a71bcea2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 4 Sep 2026 03:30:30 -0700 Subject: [PATCH 4/4] Make vulkan-driver an optional dependency of schist-bin The builder runs makepkg -s --noconfirm, and in a fresh Arch container pacman satisfies the vulkan-driver virtual package with nvidia-utils: a 315 MB download plus mesa and llvm-libs, for a package() that copies five files. The payload's own .PKGINFO does not list it either, and Omarchy installs a Vulkan driver per machine, so it moves to optdepends. Co-Authored-By: Claude Fable 5.1 --- pkgbuilds/schist-bin/PKGBUILD | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkgbuilds/schist-bin/PKGBUILD b/pkgbuilds/schist-bin/PKGBUILD index 6934c1514..a67a11919 100644 --- a/pkgbuilds/schist-bin/PKGBUILD +++ b/pkgbuilds/schist-bin/PKGBUILD @@ -11,16 +11,16 @@ pkgdesc="Layered image editor with PSD and Affinity support (binary release)" arch=(x86_64 aarch64) url="https://github.com/Infrawrench/schist" license=(MIT) -# The same list the source package (../schist/PKGBUILD) carries: -# fontconfig/wayland/vulkan-icd-loader are dlopen'd, so namcap flags them -# "may not be needed" — they are. vulkan-driver is the virtual package -# every ICD provides; without one the app gets as far as the window and no -# further, and vulkan-swrast satisfies it in software. +# 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-driver vulkan-icd-loader wayland) -# dlopen'd at run time; the app starts and opens everything else -# without it. -optdepends=('libheif: HEIC import') + 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