fix: the file the gate passed is the file published, and stable takes only the approved one
tools/release-gate.sh is the one gate: a fresh install, and the upgrade from what canary and stable serve, each on a room of that channel, plus any older release rooms are still on (RELEASE_GATE_FROM). The releases it upgrades from are fetched over HTTPS and checked against their manifests, so it needs no credential and a CI runner can run it. update_canary.sh VERSION PACKAGE gates and publishes that file without rebuilding it, since a second build is a second, untested file; the hash is taken before the gate and checked after it. update_stable.sh VERSION SHA256 refuses anything but the file that was approved. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6c04f743ed
commit
a9700536bf
Executable
+146
@@ -0,0 +1,146 @@
|
||||
#!/bin/bash
|
||||
# THE RELEASE GATE: may this exact file go to a classroom?
|
||||
#
|
||||
# tools/release-gate.sh PACKAGE.deb [FROM_VERSION...]
|
||||
#
|
||||
# Runs tools/validate-trixie-release.sh against PACKAGE as
|
||||
#
|
||||
# * a fresh install;
|
||||
# * an upgrade from what canary serves today, on a canary room;
|
||||
# * an upgrade from what stable serves today, on a stable room;
|
||||
# * an upgrade from each FROM_VERSION, on a stable room — the release a
|
||||
# room is really still on, which can be several behind stable.
|
||||
#
|
||||
# The releases it upgrades from are downloaded over HTTPS the way a
|
||||
# classroom downloads them, and each is checked against the SHA-256 its
|
||||
# manifest publishes, or, for a FROM_VERSION, against the version dpkg
|
||||
# reads out of it. Nothing here needs a credential or writes to the
|
||||
# update server, so a CI runner can run it on code nobody has reviewed.
|
||||
#
|
||||
# Every run is one line in RELEASE_GATE_REPORT (default: printed only),
|
||||
# so the result can be attached to the release it describes.
|
||||
#
|
||||
# It stops at the first failure: a gate that carries on reports a pass
|
||||
# for a machine it never finished building.
|
||||
set -euo pipefail
|
||||
# The fetches below run in $(...), which otherwise ignores set -e: a
|
||||
# manifest that failed to download would go on to compare an empty hash
|
||||
# with an empty hash, and match.
|
||||
shopt -s inherit_errexit
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
source "$ROOT/tools/release-lib.sh"
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 PACKAGE.deb [FROM_VERSION...]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
candidate="$(realpath "$1")"
|
||||
shift
|
||||
extra_from=("$@")
|
||||
|
||||
[[ -f "$candidate" ]] || { echo "Package not found: $candidate" >&2; exit 1; }
|
||||
|
||||
version="$(release_package_version "$candidate")"
|
||||
release_require_version "$version"
|
||||
|
||||
# The name a release is published under is the version dpkg reads out of
|
||||
# it. A file called 0.2.6.6 that says it is 0.2.6.5 is the wrong file.
|
||||
if [[ "$(basename "$candidate")" != "$(release_package_name "$version")" ]]; then
|
||||
echo "$(basename "$candidate") says it is $version." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hash="$(sha256sum "$candidate" | awk '{print $1}')"
|
||||
report="${RELEASE_GATE_REPORT:-}"
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
record() {
|
||||
printf '%s\n' "$1"
|
||||
[[ -z "$report" ]] || printf '%s\n' "$1" >> "$report"
|
||||
}
|
||||
|
||||
[[ -z "$report" ]] || : > "$report"
|
||||
record "release gate for $(basename "$candidate")"
|
||||
record "sha256 $hash"
|
||||
record "started $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
# A channel's current package, fetched as a classroom fetches it and
|
||||
# proven to be what the manifest promises.
|
||||
fetch_channel() {
|
||||
local channel="$1" manifest from from_hash name got
|
||||
|
||||
manifest="$(curl -fsS -H 'Cache-Control: no-cache' "$RELEASE_URL/$channel/manifest.json")"
|
||||
read -r from from_hash < <(python3 -c 'import json, sys
|
||||
m = json.loads(sys.argv[1])
|
||||
print(m["version"], m["sha256"])' "$manifest")
|
||||
|
||||
release_require_version "$from"
|
||||
[[ "$from_hash" =~ ^[0-9a-f]{64}$ ]]
|
||||
name="$(release_package_name "$from")"
|
||||
curl -fsS -o "$work/$name" "$RELEASE_URL/packages/$name"
|
||||
got="$(sha256sum "$work/$name" | awk '{print $1}')"
|
||||
|
||||
if [[ "$got" != "$from_hash" ]]; then
|
||||
echo "$channel serves $name with sha256 $got; its manifest says $from_hash" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$work/$name"
|
||||
}
|
||||
|
||||
# A published release by version, for the rooms still on an older one.
|
||||
fetch_version() {
|
||||
local from="$1" name
|
||||
|
||||
release_require_version "$from"
|
||||
name="$(release_package_name "$from")"
|
||||
curl -fsS -o "$work/$name" "$RELEASE_URL/packages/$name"
|
||||
|
||||
if [[ "$(release_package_version "$work/$name")" != "$from" ]]; then
|
||||
echo "$name does not say it is $from" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$work/$name"
|
||||
}
|
||||
|
||||
run() {
|
||||
local label="$1" started
|
||||
shift
|
||||
started=$SECONDS
|
||||
|
||||
echo
|
||||
echo "== $label"
|
||||
"$@"
|
||||
record "PASS $label ($(( (SECONDS - started) / 60 )) min)"
|
||||
}
|
||||
|
||||
run "fresh install of $version" \
|
||||
"$ROOT/tools/validate-trixie-release.sh" "$candidate"
|
||||
|
||||
for channel in canary stable; do
|
||||
previous="$(fetch_channel "$channel")"
|
||||
from="$(release_package_version "$previous")"
|
||||
|
||||
if ! release_version_gt "$version" "$from"; then
|
||||
echo "$channel is on $from; $version is not newer, so no room could take it." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run "upgrade $from -> $version on a $channel room ($channel serves $from)" \
|
||||
env VALIDATION_CHANNEL="$channel" \
|
||||
"$ROOT/tools/validate-trixie-release.sh" "$candidate" "$previous"
|
||||
done
|
||||
|
||||
for from in "${extra_from[@]}"; do
|
||||
previous="$(fetch_version "$from")"
|
||||
|
||||
run "upgrade $from -> $version on a stable room" \
|
||||
env VALIDATION_CHANNEL=stable \
|
||||
"$ROOT/tools/validate-trixie-release.sh" "$candidate" "$previous"
|
||||
done
|
||||
|
||||
record "finished $(date -u +%Y-%m-%dT%H:%M:%SZ): $(basename "$candidate") passed the release gate"
|
||||
Reference in New Issue
Block a user