Replace mise's upstream hook with a declarative GitHub-releases provider

After the quarantine moved into the manifest, all mise-bin's hook still knew
was data: the repository, the checksum manifest name, and the asset filename
patterns. That now lives in .omarchy/package.json as an upstream block --

  "upstream": {
    "github": "jdx/mise",
    "checksums": "SHASUMS256.txt",
    "assets": { "x86_64": "mise-{tag}-linux-x64.tar.xz", ... }
  }

-- handled by helpers/upstream-github.sh inside bin/sync-upstream. The
provider walks the release feed (drafts/prereleases excluded), honors
min_release_age and BYPASS_MIN_RELEASE_AGE during selection, reports
published_at so the framework backstop still applies, fails closed on any
unreadable tag or timestamp, and skips the checksum fetch when the newest
qualifying release is already checked in.

upstream.sh remains the escape hatch for feeds that fit no convention
(openai-codex-desktop's Debian index, tmog's version.txt, t3code's
electron-builder manifest); declaring both is an error.
This commit is contained in:
Ryan Hughes
2026-08-24 19:30:33 -04:00
parent 48ad6b9d7b
commit 699261471a
5 changed files with 193 additions and 96 deletions
+24 -5
View File
@@ -5,6 +5,7 @@ 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
@@ -17,7 +18,10 @@ Usage: $0 [PACKAGE...]
Update packages that track an upstream vendor release feed instead of the AUR.
A package opts in by providing pkgbuilds/<package>/.omarchy/upstream.sh, a hook
A package whose upstream ships tagged GitHub releases with a checksum manifest
opts in declaratively, via "upstream" in .omarchy/package.json (see
helpers/upstream-github.sh for the schema); no code needed. Anything with a
bespoke feed provides pkgbuilds/<package>/.omarchy/upstream.sh instead, a hook
that reports the newest upstream release as JSON on stdout:
{
@@ -304,12 +308,21 @@ sync_package() {
return 0
fi
if [[ ! -f "$hook" ]]; then
local github_repo
github_repo=$(package_upstream_github_repo "$package_dir")
if [[ -n "$github_repo" && -f "$hook" ]]; then
print_error "Package $package declares both upstream.github and an upstream.sh hook; keep exactly one"
((++FAILED))
return 0
fi
if [[ -z "$github_repo" && ! -f "$hook" ]]; then
if [[ "$SPECIFIC_MODE" == true ]]; then
print_error "Package $package is missing .omarchy/upstream.sh"
print_error "Package $package has neither an upstream declaration nor .omarchy/upstream.sh"
((++FAILED))
else
print_info "Skipping $package: no upstream hook"
print_info "Skipping $package: no upstream source"
((++SKIPPED))
fi
return 0
@@ -325,7 +338,13 @@ sync_package() {
print_info "Checking $package for upstream releases..."
local release
if ! release=$(cd "$package_dir" && PACKAGE_NAME="$package" \
if [[ -n "$github_repo" ]]; then
if ! release=$(github_upstream_release "$package_dir" "$min_age"); then
print_error "GitHub release 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
+21 -1
View File
@@ -13,6 +13,7 @@
# { "source": "aur", "rebuild_on": ["qt6-base"] }
# { "source": "local" }
# { "source": "local", "min_release_age": "24h" }
# { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "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.
@@ -164,9 +165,14 @@ package_has_upstream_hook() {
[[ -f "$pkgdir/.omarchy/upstream.sh" ]]
}
package_has_upstream_provider() {
local pkgdir="$1"
[[ -n "$(package_metadata_value "$pkgdir" '.upstream.github' "")" ]]
}
packages_for_upstream_sync() {
package_dirs | while IFS= read -r pkgdir; do
if package_has_upstream_hook "$pkgdir"; then
if package_has_upstream_hook "$pkgdir" || package_has_upstream_provider "$pkgdir"; then
basename "$pkgdir"
fi
done
@@ -340,6 +346,20 @@ validate_package_metadata() {
return 1
fi
if ! jq -e '
(.upstream // {}) | type == "object"
and (if . == {} then true else
((.github // "") | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z"))
and ((.checksums // "") | type == "string" and length > 0)
and ((.assets // {}) | type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0)
)))
end)
' "$metadata" >/dev/null; then
echo "invalid upstream for $(basename "$pkgdir"): needs github owner/repo, checksums asset name, and an assets arch->name map"
return 1
fi
pkgrel_type=$(jq -r 'if has("pkgrel") then .pkgrel | type else "missing" end' "$metadata")
case "$pkgrel_type" in
object|missing) ;;
+139
View File
@@ -0,0 +1,139 @@
# 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 --
#
# "upstream": {
# "github": "jdx/mise",
# "checksums": "SHASUMS256.txt",
# "assets": {
# "x86_64": "mise-{tag}-linux-x64.tar.xz",
# "aarch64": "mise-{tag}-linux-arm64.tar.xz"
# }
# }
#
# {tag} and {pkgver} interpolate into asset names; tags may carry a leading
# "v", which is stripped for pkgver. Drafts and prereleases are ignored. The
# provider emits the same JSON contract as an upstream.sh hook, so
# bin/sync-upstream's validation and min_release_age backstop apply
# unchanged; a feed that fits no convention keeps a bespoke upstream.sh.
package_upstream_github_repo() {
local pkgdir="$1"
package_metadata_value "$pkgdir" '.upstream.github' ""
}
# Emits the newest qualifying release as hook-contract JSON. min_release_age
# is honored during selection (newest release older than the window wins,
# even when a younger one exists) and BYPASS_MIN_RELEASE_AGE=1 lifts it.
# Unusable tags or timestamps anywhere in the feed fail the sync rather than
# being skipped: a feed this provider cannot fully read is a feed it should
# not silently choose from.
github_upstream_release() {
local package_dir="$1" min_age="${2:-0}"
local metadata repo checksums_name
metadata=$(metadata_file_for_dir "$package_dir")
repo=$(jq -r '.upstream.github // ""' "$metadata")
if [[ ! "$repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
echo "invalid upstream.github repository: '${repo:-<empty>}'" >&2
return 1
fi
checksums_name=$(jq -r '.upstream.checksums // ""' "$metadata")
if [[ -z "$checksums_name" ]]; then
echo "upstream.checksums names the checksum manifest asset and is required" >&2
return 1
fi
local arches
mapfile -t arches < <(jq -r '.upstream.assets // {} | keys[]' "$metadata")
if [[ ${#arches[@]} -eq 0 ]]; then
echo "upstream.assets must map at least one architecture to an asset name" >&2
return 1
fi
local releases now
now=$(date +%s)
if ! releases=$(curl -fsSL "https://api.github.com/repos/$repo/releases?per_page=20"); then
echo "could not fetch the release feed for $repo" >&2
return 1
fi
local candidates=0 best_tag="" best_pkgver="" best_published_at=""
local tag published_at pkgver published_epoch
while IFS=$'\t' read -r tag published_at; do
if [[ ! "$tag" =~ ^v?([A-Za-z0-9._+]+)$ ]]; then
echo "$repo release has an unusable tag: ${tag:-<empty>}" >&2
return 1
fi
pkgver=${BASH_REMATCH[1]}
if [[ -z "$published_at" ]] || ! published_epoch=$(date --date="$published_at" +%s 2>/dev/null); then
echo "$repo release $tag has an invalid published_at: ${published_at:-<empty>}" >&2
return 1
fi
candidates=$((candidates + 1))
if (( now - published_epoch < min_age )); then
if [[ "${BYPASS_MIN_RELEASE_AGE:-}" == "1" ]]; then
echo "Bypassing release-age gate for $repo $tag" >&2
else
continue
fi
fi
if [[ -z "$best_pkgver" ]] || [[ "$(vercmp "$pkgver" "$best_pkgver")" -gt 0 ]]; then
best_tag=$tag
best_pkgver=$pkgver
best_published_at=$published_at
fi
done < <(jq -r '.[] | select((.draft or .prerelease) | not) | [.tag_name // empty, .published_at // empty] | @tsv' <<<"$releases")
if (( candidates == 0 )); then
echo "no stable releases found in the feed for $repo" >&2
return 1
fi
if [[ -z "$best_tag" ]]; then
echo "every recent $repo release is still inside the release-age quarantine; skipping" >&2
echo '{}'
return 0
fi
# Already checked in: report no update instead of re-fetching checksums.
local current_pkgver
current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'")
if [[ "$best_pkgver" == "$current_pkgver" ]]; then
echo '{}'
return 0
fi
local checksums
if ! checksums=$(curl -fsSL "https://github.com/$repo/releases/download/$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
for arch in "${arches[@]}"; do
if [[ ! "$arch" =~ ^[a-z0-9_]+$ ]]; then
echo "invalid architecture key in upstream.assets: '$arch'" >&2
return 1
fi
template=$(jq -r --arg arch "$arch" '.upstream.assets[$arch]' "$metadata")
filename=${template//\{pkgver\}/$best_pkgver}
filename=${filename//\{tag\}/$best_tag}
# Manifest lines are "<sha256> <name>", 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 [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then
echo "no valid checksum for $filename in $repo $best_tag $checksums_name" >&2
return 1
fi
jq_args+=(--arg "sum_$arch" "$checksum")
jq_filter+=" | .sha256sums[\"$arch\"] = [\$sum_$arch]"
done
jq -n "${jq_args[@]}" "$jq_filter"
}
+9 -1
View File
@@ -1,5 +1,13 @@
{
"source": "local",
"release_ring": "fast",
"min_release_age": "24h"
"min_release_age": "24h",
"upstream": {
"github": "jdx/mise",
"checksums": "SHASUMS256.txt",
"assets": {
"x86_64": "mise-{tag}-linux-x64.tar.xz",
"aarch64": "mise-{tag}-linux-arm64.tar.xz"
}
}
}
-89
View File
@@ -1,89 +0,0 @@
#!/bin/bash
set -euo pipefail
repo="jdx/mise"
# Keep a compromised mise release from reaching Omarchy before there has been
# time for maintainers and the community to notice and pull it. The window
# comes from min_release_age in .omarchy/package.json, exported by
# bin/sync-upstream as MIN_RELEASE_AGE_SECONDS. Walking the release list
# instead of gating on /releases/latest alone means mise's near-daily cadence
# cannot starve updates: the newest release that has finished its quarantine
# ships even while an even newer one is still inside it. Nothing younger than
# the window ever ships without the explicit BYPASS_MIN_RELEASE_AGE=1 bypass,
# which bin/sync-upstream honors too.
minimum_release_age_seconds=${MIN_RELEASE_AGE_SECONDS:-0}
now=$(date +%s)
releases=$(curl -fsSL "https://api.github.com/repos/$repo/releases?per_page=20")
candidates=0
best_tag=""
best_pkgver=""
best_published_at=""
while IFS=$'\t' read -r tag published_at; do
if [[ ! "$tag" =~ ^v([A-Za-z0-9._+]+)$ ]]; then
echo "mise release has an invalid tag: ${tag:-<empty>}" >&2
exit 1
fi
pkgver=${BASH_REMATCH[1]}
if [[ -z "$published_at" ]] || ! published_epoch=$(date --date="$published_at" +%s); then
echo "mise release $tag has an invalid published_at: ${published_at:-<empty>}" >&2
exit 1
fi
candidates=$((candidates + 1))
if (( now - published_epoch < minimum_release_age_seconds )); then
if [[ "${BYPASS_MIN_RELEASE_AGE:-}" == "1" ]]; then
echo "Bypassing mise release-age gate for $tag" >&2
else
continue
fi
fi
if [[ -z "$best_pkgver" ]] || [[ "$(vercmp "$pkgver" "$best_pkgver")" -gt 0 ]]; then
best_tag=$tag
best_pkgver=$pkgver
best_published_at=$published_at
fi
done < <(jq -r '.[] | select((.draft or .prerelease) | not) | [.tag_name // empty, .published_at // empty] | @tsv' <<<"$releases")
if (( candidates == 0 )); then
echo "No stable mise releases found in the release feed" >&2
exit 1
fi
if [[ -z "$best_tag" ]]; then
echo "Every recent mise release is still inside the release-age quarantine; skipping" >&2
echo '{}'
exit 0
fi
tag=$best_tag
pkgver=$best_pkgver
checksums=$(curl -fsSL \
"https://github.com/$repo/releases/download/$tag/SHASUMS256.txt")
checksum_for() {
local filename=$1
local checksum
checksum=$(awk -v filename="./$filename" '$2 == filename { print $1 }' <<<"$checksums")
if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then
echo "No valid checksum found for $filename in $tag" >&2
exit 1
fi
echo "$checksum"
}
x86_64=$(checksum_for "mise-v${pkgver}-linux-x64.tar.xz")
aarch64=$(checksum_for "mise-v${pkgver}-linux-arm64.tar.xz")
jq -n \
--arg pkgver "$pkgver" \
--arg published_at "$best_published_at" \
--arg x86_64 "$x86_64" \
--arg aarch64 "$aarch64" \
'{pkgver: $pkgver, published_at: $published_at, sha256sums: {x86_64: [$x86_64], aarch64: [$aarch64]}}'