diff --git a/scripts/create-release.sh b/scripts/create-release.sh index 0d87dc79..6c90f02d 100755 --- a/scripts/create-release.sh +++ b/scripts/create-release.sh @@ -156,6 +156,14 @@ mkdir -p "$PROJECT_ROOT/releases" "$SCRIPT_DIR/create-release-manifest.sh" --version "$VERSION" --date "$RELEASE_DATE" --output "$PROJECT_ROOT/releases/manifest.json" 2>&1 | grep -v "^$" cp "$PROJECT_ROOT/releases/manifest.json" "$PROJECT_ROOT/release-manifest.json" +echo "[6b/8] Staging release artifacts for validation..." +VERSION_DIR="$PROJECT_ROOT/releases/v${VERSION}" +FRONTEND_ARCHIVE="/tmp/archipelago-frontend-${VERSION}.tar.gz" +mkdir -p "$VERSION_DIR" +install -m 0755 "$PROJECT_ROOT/core/target/release/archipelago" "$VERSION_DIR/archipelago" +install -m 0644 "$FRONTEND_ARCHIVE" "$VERSION_DIR/archipelago-frontend-${VERSION}.tar.gz" +"$SCRIPT_DIR/check-release-manifest.sh" + echo "[7/8] Committing version bump..." git -C "$PROJECT_ROOT" add \ core/archipelago/Cargo.toml \ @@ -179,15 +187,13 @@ echo " - Version bumped in Cargo.toml and package.json" echo " - Changelog updated in CHANGELOG.md" echo " - Release manifest: releases/manifest.json" echo " - Release manifest copy: release-manifest.json" +echo " - Staged artifacts: releases/v${VERSION}/" echo " - Git tag: v${VERSION}" echo "" echo "Next steps:" echo " 1. Review: git log --oneline -5" -echo " 2. Create Gitea release v${VERSION} and upload artifacts:" -echo " archipelago" -echo " archipelago-frontend-${VERSION}.tar.gz" -echo " 3. Push to gitea-local and gitea-vps2:" -echo " git push gitea-local main --tags && git push gitea-vps2 main --tags" -echo " 4. Verify manifest is live on both mirrors:" +echo " 2. Publish commits, tag, artifacts, and verify download URLs:" +echo " scripts/publish-release-assets.sh ${VERSION} gitea-vps2" +echo " 3. Verify manifest is live on both mirrors:" echo " curl -fsS http://localhost:3000/lfg2025/archy/raw/branch/main/releases/manifest.json" echo " curl -fsS http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/manifest.json" diff --git a/scripts/publish-release-assets.sh b/scripts/publish-release-assets.sh new file mode 100755 index 00000000..cd227515 --- /dev/null +++ b/scripts/publish-release-assets.sh @@ -0,0 +1,102 @@ +#!/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" + +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."