#!/usr/bin/env bash # Publish an Archipelago OTA release to a Gitea remote and verify downloads. set -euo pipefail VERSION="${1:-}" REMOTE="${2:-gitea-vps2}" if [ -z "$VERSION" ]; then echo "Usage: $0 VERSION [remote]" exit 1 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" VERSION_DIR="$PROJECT_ROOT/releases/v${VERSION}" BACKEND="$VERSION_DIR/archipelago" FRONTEND="$VERSION_DIR/archipelago-frontend-${VERSION}.tar.gz" fail() { echo "Error: $*" >&2; exit 1; } [ -f "$PROJECT_ROOT/releases/manifest.json" ] || fail "releases/manifest.json missing" [ -f "$BACKEND" ] || fail "backend artifact missing: $BACKEND" [ -f "$FRONTEND" ] || fail "frontend artifact missing: $FRONTEND" "$SCRIPT_DIR/check-release-manifest.sh" # §A supply-chain gate: never publish an unsigned OTA manifest. Fleet nodes # with the pinned release-root anchor refuse to auto-apply unsigned manifests, # and enforcement will tighten to hard-reject — an unsigned publish would # strand them. Grep proves presence; ceremony verify proves the crypto. EXPECTED_DID="did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur" grep -q '"signature":' "$PROJECT_ROOT/releases/manifest.json" \ && grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$PROJECT_ROOT/releases/manifest.json" \ || fail "releases/manifest.json is not signed by the release root — run: bash scripts/sign-manifest.sh" if [ -x "$PROJECT_ROOT/core/target/release/archipelago" ]; then "$PROJECT_ROOT/core/target/release/archipelago" ceremony verify "$PROJECT_ROOT/releases/manifest.json" \ || fail "manifest signature failed cryptographic verification" fi remote_url=$(git -C "$PROJECT_ROOT" remote get-url "$REMOTE") case "$remote_url" in http://*@*) ;; *) fail "$REMOTE must be an authenticated http:// Gitea remote URL for API uploads" ;; esac auth=${remote_url#http://} auth=${auth%@*} host_path=${remote_url#http://$auth@} host=${host_path%%/*} repo_path=${host_path#*/} repo_path=${repo_path%.git} api="http://$host/api/v1/repos/$repo_path" release_url="$api/releases/tags/v${VERSION}" echo "Pushing main and v${VERSION} to $REMOTE..." git -C "$PROJECT_ROOT" push "$REMOTE" main "refs/tags/v${VERSION}" release_json=$(curl -fsS -u "$auth" "$release_url" || true) if [ -z "$release_json" ]; then echo "Creating Gitea release v${VERSION}..." release_body=$(python3 - "$VERSION" <<'PY' import json import sys version = sys.argv[1] print(json.dumps({ "tag_name": f"v{version}", "target_commitish": "main", "name": f"v{version}", "body": f"Archipelago v{version} release artifacts for OTA updates.", "draft": False, "prerelease": True, })) PY ) release_json=$(curl -fsS -u "$auth" -H 'Content-Type: application/json' -d "$release_body" "$api/releases") fi release_id=$(printf '%s' "$release_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])') asset_names=$(curl -fsS -u "$auth" "$api/releases/$release_id/assets" | python3 -c 'import json,sys; print("\n".join(a["name"] for a in json.load(sys.stdin)))') upload_asset() { local path="$1" local name="$2" if printf '%s\n' "$asset_names" | grep -Fxq "$name"; then echo "Asset $name already exists; leaving it in place." return fi echo "Uploading $name..." curl --fail --show-error --silent --http1.1 --connect-timeout 20 --max-time 900 \ -u "$auth" \ -F "attachment=@$path" \ "$api/releases/$release_id/assets?name=$name" >/dev/null asset_names=$(printf '%s\n%s\n' "$asset_names" "$name") } upload_asset "$BACKEND" "archipelago" upload_asset "$FRONTEND" "archipelago-frontend-${VERSION}.tar.gz" echo "Verifying public download URLs from manifest..." python3 - "$PROJECT_ROOT/releases/manifest.json" <<'PY' | while read -r url size; do import json import sys manifest = json.load(open(sys.argv[1])) for component in manifest["components"]: print(component["download_url"], component["size_bytes"]) PY headers=$(curl -fsSI -L --max-time 60 "$url") || fail "download URL failed: $url" actual_size=$(printf '%s\n' "$headers" | awk 'tolower($1)=="content-length:" { size=$2 } END { gsub("\r", "", size); print size }') [ "$actual_size" = "$size" ] || fail "download size mismatch for $url (expected $size, got ${actual_size:-missing})" done echo "Release v${VERSION} published and verified on $REMOTE."