#!/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)" } # A new machine is a stable room: that is what a fresh device.conf says, # so that is the manifest its updater is asked about. run "fresh install of $version (a new machine, on stable)" \ env VALIDATION_CHANNEL=stable \ "$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"