Files
omarchy-pkgs/bin/sync-upstream
T
99234a4fbb 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 <david@hey.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 13:09:12 +02:00

876 lines
34 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/package-metadata.sh"
source "$BUILD_ROOT/helpers/upstream-github.sh"
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
SPECIFIC_PACKAGES=()
usage() {
cat <<EOF
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 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/<package>/.omarchy/upstream.sh, a hook that reports JSON on stdout:
{
"pkgver": "1.2.3",
"sha256sums": { "x86_64": ["<sha256>"], "aarch64": ["<sha256>"] }
}
Architecture keys become sha256sums_<arch> in the PKGBUILD; the key "any" means
the unsuffixed sha256sums array. An empty object ({}) reports no update.
When the reported version is newer than the checked-in one, pkgver and the
listed checksum arrays are rewritten and pkgrel is reset to 1.
A package may declare "min_release_age" in .omarchy/package.json ("24h", "2d",
or bare seconds) to quarantine fresh releases until maintainers have had time
to pull a bad or compromised one. The window is exported to the hook as
MIN_RELEASE_AGE_SECONDS so it can select the newest release that has already
cleared it, and enforced here as a backstop: the hook must then report
"published_at" (ISO 8601), and a release younger than the window is treated
as no update. A maintainer shipping an emergency update inside the window
runs: BYPASS_MIN_RELEASE_AGE=1 $0 <package>. Scheduled automation never sets
the bypass, so the resulting change still goes through a reviewed PR.
Arguments:
PACKAGE One or more package names to update (optional)
Commands:
self-test Run the offline fixture tests for release selection, the
quarantine backstop, and metadata parsing
Examples:
$0 # Update every package with an upstream source
$0 openai-codex-desktop # Update specific packages
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--*)
print_error "Unknown option: $1"
exit 1
;;
*)
SPECIFIC_PACKAGES+=("$1")
shift
;;
esac
done
if ! command -v vercmp >/dev/null 2>&1; then
print_error "vercmp not found: this needs pacman to decide whether a release is an upgrade"
exit 1
fi
print_header "Upstream Package Sync"
UPDATED=0
SKIPPED=0
FAILED=0
SPECIFIC_MODE=false
get_pkgver() {
local package_dir="$1"
grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'"
}
assert_single_assignment() {
local pkgbuild="$1"
local pattern="$2"
local label="$3"
if [[ $(grep -c "$pattern" "$pkgbuild") -ne 1 ]]; then
print_error "Expected exactly one $label assignment in $pkgbuild"
return 1
fi
}
set_pkgbuild_scalar() {
local pkgbuild="$1"
local field="$2"
local value="$3"
assert_single_assignment "$pkgbuild" "^${field}=" "$field" || return 1
sed -i "s/^${field}=.*/${field}=${value}/" "$pkgbuild"
}
# Replace an array assignment, however many lines the original spans.
set_pkgbuild_array() {
local pkgbuild="$1"
local name="$2"
shift 2
local values=("$@")
assert_single_assignment "$pkgbuild" "^${name}=(" "$name" || return 1
if [[ ${#values[@]} -eq 0 ]]; then
print_error "No values to write for ${name}"
return 1
fi
local block="$TEMP_DIR/array-block"
if [[ ${#values[@]} -eq 1 ]]; then
printf "%s=('%s')\n" "$name" "${values[0]}" > "$block" || return 1
else
{
printf '%s=(\n' "$name"
printf " '%s'\n" "${values[@]}"
printf ')\n'
} > "$block" || return 1
fi
local rewritten="$TEMP_DIR/pkgbuild-rewritten"
if ! awk -v prefix="${name}=(" -v block="$block" '
!replaced && index($0, prefix) == 1 {
while ((getline line < block) > 0) print line
close(block)
replaced = 1
# A ")" anywhere past the opening closes the array; testing for one at end
# of line instead would treat a trailing comment as a continuation and eat
# every line up to the next ")".
if (index(substr($0, length(prefix) + 1), ")") == 0) skipping = 1
next
}
skipping { if ($0 ~ /\)/) skipping = 0; next }
{ print }
' "$pkgbuild" > "$rewritten"; then
print_error "Failed to rewrite ${name} in $pkgbuild"
rm -f "$rewritten"
return 1
fi
mv "$rewritten" "$pkgbuild"
}
# Backstop verdict for a reported release against min_release_age. Returns 0
# when old enough (or no policy is set, or the bypass is deliberate), 1 when
# the release is younger than the window, 2 when the report carries no usable
# published_at and the age cannot be established at all.
release_age_status() {
local release="$1" min_age="$2"
(( min_age > 0 )) || return 0
[[ "${BYPASS_MIN_RELEASE_AGE:-}" != "1" ]] || return 0
local published_at published_epoch
published_at=$(jq -r '.published_at // empty' <<<"$release")
# Strict ISO 8601 before GNU date sees it: date also accepts relative
# expressions like "2 days ago", which would let a buggy hook fabricate an
# age instead of failing closed.
if [[ ! "$published_at" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|[+-][0-9]{2}:?[0-9]{2})$ ]] \
|| ! published_epoch=$(date --date="$published_at" +%s 2>/dev/null); then
return 2
fi
(( $(date +%s) - published_epoch >= min_age )) || return 1
}
# pacman's own comparator, because nothing else agrees with it at the corners:
# sort -V calls 1.0a newer than 1.0, vercmp calls it older, and pacman is what
# decides whether a published package is an upgrade.
version_is_newer() {
local candidate="$1"
local current="$2"
[[ "$candidate" != "$current" ]] || return 1
[[ "$(vercmp "$candidate" "$current")" -gt 0 ]]
}
validate_release() {
local release="$1"
# pkgver is written into the PKGBUILD, which makepkg sources as shell, so it
# is held to pacman's own character set rather than merely being non-empty.
# The anchors are \A and \z, not ^ and $: jq's $ also matches before a
# trailing newline, which would let "1.0\n" through and break the rewrite.
jq -e '
(.pkgver | type == "string" and test("\\A[A-Za-z0-9._+]+\\z"))
and (.sha256sums | type == "object" and length > 0)
and (.sha256sums | to_entries | all(
.key | test("\\A[a-z0-9_]+\\z")
))
and (.sha256sums | to_entries | all(
.value | type == "array" and length > 0 and all(test("\\A[0-9a-f]{64}\\z"))
))
and (if has("published_at") then (.published_at | type == "string" and length > 0) else true end)
' <<<"$release" >/dev/null
}
# Confirm the rewritten PKGBUILD parses and actually holds what we meant to put
# in it. Editing shell with awk and sed can go wrong in ways no amount of
# pattern-matching anticipates -- an array element carrying a ")" in a comment,
# say -- so the result is checked rather than trusted.
verify_pkgbuild() {
local pkgbuild="$1"
local release="$2"
local pkgver="$3"
shift 3
local arrays=("$@")
if ! bash -n "$pkgbuild" 2>/dev/null; then
print_error "Rewritten PKGBUILD is not valid shell"
return 1
fi
local dump
if ! dump=$(CARCH=x86_64 bash -c '
source "$1" >/dev/null 2>&1 || exit 1
printf "pkgver\t%s\n" "$pkgver"
printf "pkgrel\t%s\n" "$pkgrel"
for name in "${@:2}"; do
declare -n array="$name"
printf "%s\t%s\n" "$name" "${array[*]}"
done
' _ "$pkgbuild" "${arrays[@]}" 2>/dev/null); then
print_error "Rewritten PKGBUILD could not be read back"
return 1
fi
local expected
expected=$(
printf 'pkgver\t%s\n' "$pkgver"
printf 'pkgrel\t1\n'
local array arch
for array in "${arrays[@]}"; do
arch="${array#sha256sums}"
arch="${arch#_}"
[[ -n "$arch" ]] || arch="any"
printf '%s\t%s\n' "$array" \
"$(jq -r --arg arch "$arch" '.sha256sums[$arch] | join(" ")' <<<"$release")"
done
)
if [[ "$dump" != "$expected" ]]; then
print_error "Rewritten PKGBUILD does not hold the reported release"
diff <(echo "$expected") <(echo "$dump") | sed 's/^/ /' >&2 || true
return 1
fi
}
apply_release() {
local package_dir="$1"
local release="$2"
local pkgver="$3"
local pkgbuild="$package_dir/PKGBUILD"
local arch array values
local arrays=()
while IFS= read -r arch; do
if [[ "$arch" == "any" ]]; then
array="sha256sums"
else
array="sha256sums_$arch"
fi
arrays+=("$array")
done < <(jq -r '.sha256sums | keys[]' <<<"$release")
# validate_release guarantees at least one entry, so an empty list here means
# jq died inside the process substitution rather than that there is nothing
# to do.
if [[ ${#arrays[@]} -eq 0 ]]; then
print_error "Could not read the checksum architectures from the reported release"
return 1
fi
# Every edit lands on a scratch copy that replaces the PKGBUILD in one rename
# at the end, so a failure part way through leaves the original untouched
# rather than half updated.
local scratch="$pkgbuild.sync-upstream"
cp "$pkgbuild" "$scratch" || return 1
if ! (
assert_single_assignment "$scratch" '^pkgver=' pkgver || exit 1
assert_single_assignment "$scratch" '^pkgrel=' pkgrel || exit 1
for array in "${arrays[@]}"; do
arch="${array#sha256sums}"
arch="${arch#_}"
[[ -n "$arch" ]] || arch="any"
mapfile -t values < <(jq -r --arg arch "$arch" '.sha256sums[$arch][]' <<<"$release")
set_pkgbuild_array "$scratch" "$array" "${values[@]}" || exit 1
done
set_pkgbuild_scalar "$scratch" pkgver "$pkgver" || exit 1
set_pkgbuild_scalar "$scratch" pkgrel 1 || exit 1
verify_pkgbuild "$scratch" "$release" "$pkgver" "${arrays[@]}" || exit 1
); then
rm -f "$scratch"
return 1
fi
chmod --reference="$pkgbuild" "$scratch"
mv "$scratch" "$pkgbuild"
}
sync_package() {
local package="$1"
local package_dir="$PKGBUILDS_DIR/$package"
local hook="$package_dir/.omarchy/upstream.sh"
if [[ ! -f "$package_dir/PKGBUILD" ]]; then
print_error "Package $package has no PKGBUILD"
((++FAILED))
return 0
fi
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 "$provider" ]]; then
print_error "Package $package has an unusable or ambiguous upstream declaration"
((++FAILED))
return 0
fi
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 "$provider" && ! -f "$hook" ]]; then
if [[ "$SPECIFIC_MODE" == true ]]; then
print_error "Package $package has neither an upstream declaration nor .omarchy/upstream.sh"
((++FAILED))
else
print_info "Skipping $package: no upstream source"
((++SKIPPED))
fi
return 0
fi
local min_age
if ! min_age=$(package_min_release_age_seconds "$package_dir"); then
print_error "Invalid min_release_age in $package_dir/.omarchy/package.json"
((++FAILED))
return 0
fi
print_info "Checking $package for upstream releases..."
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
elif ! release=$(cd "$package_dir" && PACKAGE_NAME="$package" \
MIN_RELEASE_AGE_SECONDS="$min_age" \
BYPASS_MIN_RELEASE_AGE="${BYPASS_MIN_RELEASE_AGE:-}" \
bash .omarchy/upstream.sh); then
print_error "Upstream hook failed for $package"
((++FAILED))
return 0
fi
if ! jq -e . >/dev/null 2>&1 <<<"$release"; then
print_error "Upstream hook for $package did not report valid JSON"
((++FAILED))
return 0
fi
if [[ "$(jq -r 'has("pkgver")' <<<"$release")" == "false" ]]; then
print_info " No upstream update reported"
((++SKIPPED))
return 0
fi
if ! validate_release "$release"; then
print_error "Upstream hook for $package reported a malformed release"
((++FAILED))
return 0
fi
# Backstop for min_release_age: the selection already honors the window,
# but a provider or hook bug must not be able to ship a release younger
# than the policy.
local age_status=0
release_age_status "$release" "$min_age" || age_status=$?
case "$age_status" in
1)
print_warning " Reported release is inside the ${min_age}s minimum release age; leaving it alone"
((++SKIPPED))
return 0
;;
2)
print_error "min_release_age is set for $package but its source reported no usable published_at; refusing an unverifiable release"
((++FAILED))
return 0
;;
esac
local pkgver current_pkgver
pkgver=$(jq -r '.pkgver' <<<"$release")
current_pkgver=$(get_pkgver "$package_dir")
if [[ -z "$current_pkgver" ]]; then
print_error "Could not read pkgver from $package_dir/PKGBUILD"
((++FAILED))
return 0
fi
if [[ "$pkgver" == "$current_pkgver" ]]; then
print_info " Already at $current_pkgver"
((++SKIPPED))
return 0
fi
if ! version_is_newer "$pkgver" "$current_pkgver"; then
print_warning " Upstream reports $pkgver, older than the checked-in $current_pkgver; leaving it alone"
((++SKIPPED))
return 0
fi
if ! apply_release "$package_dir" "$release" "$pkgver"; then
print_error "Failed to update $package"
((++FAILED))
return 0
fi
print_success " $current_pkgver -> $pkgver"
((++UPDATED))
}
# Offline fixture tests: the network fetches in helpers/upstream-github.sh
# are swapped for fixture readers, everything else runs the production code
# 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, release API digests, the min_release_age backstop,
# the duration parser, and manifest validation.
cmd_self_test() {
local failures=0
check() {
local desc="$1" expected="$2" got="$3"
if [[ "$expected" == "$got" ]]; then
echo " ok: $desc"
else
echo " FAIL: $desc (expected '$expected', got '$got')"
failures=$((failures + 1))
fi
}
local pkg="$TEMP_DIR/selftest-pkg"
mkdir -p "$pkg/.omarchy"
printf 'pkgver=1.0.0\npkgrel=1\n' > "$pkg/PKGBUILD"
cat > "$pkg/.omarchy/package.json" <<'EOF'
{
"source": "local",
"min_release_age": "24h",
"upstream": {
"github": "example/tool",
"checksums": "SHASUMS256.txt",
"assets": {
"x86_64": "tool-{tag}-x64.tar.xz",
"aarch64": "tool-v{pkgver}-arm64.tar.xz"
}
}
}
EOF
local young old2d old3d
young=$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)
old2d=$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%SZ)
old3d=$(date -u -d '3 days ago' +%Y-%m-%dT%H:%M:%SZ)
# v2.0.0 is inside the 24h window; v1.9.9/v1.9.8 are a prerelease and a
# draft that would outrank v1.9.0 if the filters failed.
local sum_x19 sum_a19 sum_x20 sum_a20
sum_x19=$(printf 'a%.0s' {1..64})
sum_a19=$(printf 'b%.0s' {1..64})
sum_x20=$(printf 'c%.0s' {1..64})
sum_a20=$(printf 'd%.0s' {1..64})
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")
github_fetch_releases() { printf '%s' "$FIXTURE_RELEASES"; }
github_fetch_checksums() { printf '%s\n' "$FIXTURE_CHECKSUMS"; }
echo "Release selection:"
local out
out=$(github_upstream_release "$pkg" 86400 2>/dev/null) || out="<error>"
check "quarantine falls back past the young v2.0.0" "1.9.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
check "selected release reports its published_at" "$old2d" "$(jq -r '.published_at // "<none>"' <<<"$out")"
check "x86_64 checksum via {tag} template and ./ prefix" "$sum_x19" "$(jq -r '.sha256sums.x86_64[0] // "<none>"' <<<"$out")"
check "aarch64 checksum via {pkgver} template" "$sum_a19" "$(jq -r '.sha256sums.aarch64[0] // "<none>"' <<<"$out")"
out=$(github_upstream_release "$pkg" 0 2>/dev/null) || out="<error>"
check "no policy selects the newest stable release" "2.0.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
check "prerelease v1.9.9 and draft v1.9.8 are never selected" "" "$(jq -r 'select(.pkgver == "1.9.9" or .pkgver == "1.9.8") | .pkgver' <<<"$out")"
out=$(BYPASS_MIN_RELEASE_AGE=1 github_upstream_release "$pkg" 86400 2>/dev/null) || out="<error>"
check "bypass lifts the quarantine" "2.0.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
check "x86_64 checksum via * binary-mode prefix" "$sum_x20" "$(jq -r '.sha256sums.x86_64[0] // "<none>"' <<<"$out")"
out=$(github_upstream_release "$pkg" 8640000 2>/dev/null) || out="<error>"
check "everything quarantined reports no update" "{}" "$(jq -c . <<<"$out")"
printf 'pkgver=1.9.0\npkgrel=1\n' > "$pkg/PKGBUILD"
out=$(github_upstream_release "$pkg" 86400 2>/dev/null) || out="<error>"
check "already checked in reports no update" "{}" "$(jq -c . <<<"$out")"
printf 'pkgver=1.0.0\npkgrel=1\n' > "$pkg/PKGBUILD"
echo "Failure paths:"
local rc
FIXTURE_RELEASES=$(jq -n '[{tag_name: "v1.9.0", published_at: "not-a-date", draft: false, prerelease: false}]')
rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$?
check "invalid published_at fails the sync" "1" "$rc"
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" '[{tag_name: "release 1.9!", published_at: $old, draft: false, prerelease: false}]')
rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$?
check "unusable tag fails the sync" "1" "$rc"
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" '[{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false}]')
FIXTURE_CHECKSUMS="$sum_x19 ./tool-v1.9.0-x64.tar.xz"
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="<error>"
check "x86_64 checksum via the asset digest" "$sum_x19" "$(jq -r '.sha256sums.x86_64[0] // "<none>"' <<<"$out")"
check "aarch64 checksum via the asset digest" "$sum_a19" "$(jq -r '.sha256sums.aarch64[0] // "<none>"' <<<"$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: {}}')
st=0; release_age_status "$rel" 86400 || st=$?
check "old enough passes" "0" "$st"
rel=$(jq -n --arg p "$young" '{pkgver: "2.0.0", published_at: $p, sha256sums: {}}')
st=0; release_age_status "$rel" 86400 || st=$?
check "too young is held" "1" "$st"
st=0; release_age_status "$rel" 0 || st=$?
check "no policy passes anything" "0" "$st"
st=0; BYPASS_MIN_RELEASE_AGE=1 release_age_status "$rel" 86400 || st=$?
check "deliberate bypass passes" "0" "$st"
rel=$(jq -n '{pkgver: "2.0.0", sha256sums: {}}')
st=0; release_age_status "$rel" 86400 || st=$?
check "missing published_at is unprovable" "2" "$st"
rel=$(jq -n '{pkgver: "2.0.0", published_at: "2 days ago", sha256sums: {}}')
st=0; release_age_status "$rel" 86400 || st=$?
check "relative-date expression is unprovable, not an age" "2" "$st"
rel=$(jq -n --arg p "$(date -u -d '2 days ago' +%Y-%m-%dT%H:%M:%S+00:00)" '{pkgver: "1.9.0", published_at: $p, sha256sums: {}}')
st=0; release_age_status "$rel" 86400 || st=$?
check "numeric-offset ISO timestamp passes" "0" "$st"
echo "Duration parser:"
local agepkg="$TEMP_DIR/selftest-age"
mkdir -p "$agepkg/.omarchy"
check_age() {
local json_value="$1" expected="$2" got
jq -n "{source: \"local\", min_release_age: $json_value}" > "$agepkg/.omarchy/package.json"
got=$(package_min_release_age_seconds "$agepkg") || got="<reject>"
check "min_release_age $json_value" "$expected" "$got"
}
check_age '"24h"' 86400
check_age '"90m"' 5400
check_age '"2d"' 172800
check_age '3600' 3600
check_age '"600s"' 600
check_age '"010h"' 36000
check_age '"abc"' "<reject>"
check_age '"24hh"' "<reject>"
check_age 'false' "<reject>"
check_age '""' "<reject>"
check_age '"9999999999"' "<reject>"
echo "Manifest validation:"
printf 'pkgver=1.0.0\n' > "$agepkg/PKGBUILD"
local vst
echo '{"source": "local", "upstream": false}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "upstream: false is rejected" "1" "$vst"
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"
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
# replaced by mise-shaped fixtures.
echo "End-to-end sync_package with the checked-in mise-bin metadata:"
local e2e_root="$TEMP_DIR/e2e-pkgbuilds"
mkdir -p "$e2e_root"
cp -a "$BUILD_ROOT/pkgbuilds/mise-bin" "$e2e_root/mise-bin"
# Fixture versions extend the checked-in pkgver so they stay newer no
# matter what version the real package is at when the test runs.
local mise_current mise_aged mise_fresh mise_x64 mise_a64
mise_current=$(grep -m1 '^pkgver=' "$e2e_root/mise-bin/PKGBUILD" | cut -d= -f2- | tr -d "\"'")
mise_aged="${mise_current}.90"
mise_fresh="${mise_current}.91"
mise_x64=$(printf 'e%.0s' {1..64})
mise_a64=$(printf 'f%.0s' {1..64})
FIXTURE_RELEASES=$(jq -n --arg young "$young" --arg old2 "$old2d" \
--arg aged "v$mise_aged" --arg fresh "v$mise_fresh" '[
{tag_name: $fresh, published_at: $young, draft: false, prerelease: false},
{tag_name: $aged, published_at: $old2, draft: false, prerelease: false}
]')
FIXTURE_CHECKSUMS=$(printf '%s\n' \
"$mise_x64 ./mise-v$mise_aged-linux-x64.tar.xz" \
"$mise_a64 ./mise-v$mise_aged-linux-arm64.tar.xz")
local prev_updated=$UPDATED prev_failed=$FAILED
PKGBUILDS_DIR="$e2e_root" sync_package mise-bin >/dev/null 2>&1 || true
check "sync_package updates without failures" "updated=1 failed=0" \
"updated=$((UPDATED - prev_updated)) failed=$((FAILED - prev_failed))"
check "the 24h manifest policy holds v$mise_fresh and ships v$mise_aged" "$mise_aged" \
"$(grep -m1 '^pkgver=' "$e2e_root/mise-bin/PKGBUILD" | cut -d= -f2-)"
check "pkgrel resets to 1" "1" \
"$(grep -m1 '^pkgrel=' "$e2e_root/mise-bin/PKGBUILD" | cut -d= -f2-)"
check "x86_64 checksum lands in the PKGBUILD" "sha256sums_x86_64=('$mise_x64')" \
"$(grep -m1 '^sha256sums_x86_64=' "$e2e_root/mise-bin/PKGBUILD")"
check "aarch64 checksum lands in the PKGBUILD" "sha256sums_aarch64=('$mise_a64')" \
"$(grep -m1 '^sha256sums_aarch64=' "$e2e_root/mise-bin/PKGBUILD")"
# A malformed declaration must fail the run loudly, and still be discovered.
local badpkg="$e2e_root/selftest-broken"
mkdir -p "$badpkg/.omarchy"
printf 'pkgver=1.0.0\npkgrel=1\n' > "$badpkg/PKGBUILD"
echo '{"source": "local", "upstream": false}' > "$badpkg/.omarchy/package.json"
check "malformed upstream stays discoverable for scheduled runs" "yes" \
"$(PKGBUILDS_DIR="$e2e_root" packages_for_upstream_sync | grep -qx selftest-broken && echo yes || echo no)"
prev_failed=$FAILED
PKGBUILDS_DIR="$e2e_root" sync_package selftest-broken >/dev/null 2>&1 || true
check "malformed upstream fails the sync instead of skipping" "1" "$((FAILED - prev_failed))"
FAILED=0
echo ""
if [[ "$failures" -eq 0 ]]; then
print_success "Self-test passed"
else
print_error "$failures self-test failure(s)"
exit 1
fi
}
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 && "${SPECIFIC_PACKAGES[0]}" == "self-test" ]]; then
cmd_self_test
exit 0
fi
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then
SPECIFIC_MODE=true
for package in "${SPECIFIC_PACKAGES[@]}"; do
sync_package "$package"
done
else
while IFS= read -r package; do
sync_package "$package"
done < <(packages_for_upstream_sync)
fi
echo ""
if [[ $FAILED -gt 0 ]]; then
print_error "Upstream sync completed with failures"
else
print_success "Upstream sync complete!"
fi
echo " Target: $PKGBUILDS_DIR"
echo " Updated: $UPDATED"
echo " Skipped: $SKIPPED"
echo " Failed: $FAILED"
if [[ $FAILED -gt 0 ]]; then
exit 1
fi