scripts/github-installer-release-assets.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/sh
set -eu
command_name="${1:-}"
if [ "$#" -gt 0 ]; then shift; fi
tag=""
directory="dist"
usage() {
cat <<'EOF'
Usage: github-installer-release-assets.sh upload --tag TAG [--directory DIR]
github-installer-release-assets.sh download --tag TAG [--directory DIR]
Uploads or downloads the exact four historical installer assets while keeping
the GitHub Release in draft state, then verifies every asset digest.
EOF
}
case "$command_name" in
-h|--help) usage; exit 0 ;;
esac
while [ "$#" -gt 0 ]; do
case "$1" in
--tag) tag="${2:-}"; shift 2 ;;
--directory) directory="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
esac
done
case "$command_name" in upload|download) ;; *) usage >&2; exit 2 ;; esac
if ! printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
echo "Release tag must be valid Vastora SemVer." >&2
exit 2
fi
for required in awk cmp find gh grep jq mkdir mktemp sha256sum; do
if ! command -v "$required" >/dev/null 2>&1; then
echo "Required command is not installed: $required" >&2
exit 1
fi
done
expected_assets='["install.sh","vastora-center-install.tar.gz","vastora-center-install.tar.gz.sha256","vastora-release-manifest.json"]'
release_json="$(gh release view "$tag" --json isDraft,assets)"
if [ "$(printf '%s' "$release_json" | jq -r '.isDraft')" != "true" ]; then
echo "GitHub Release $tag is not a draft; refusing installer asset mutation." >&2
exit 1
fi
if ! printf '%s' "$release_json" | jq -e --argjson expected "$expected_assets" '([.assets[].name] - $expected) == []' >/dev/null; then
echo "GitHub Release $tag contains an unexpected asset; refusing to replace it." >&2
exit 1
fi
validate_assets() {
asset_dir="$1"
manifest="$asset_dir/vastora-release-manifest.json"
for asset in install.sh vastora-center-install.tar.gz vastora-center-install.tar.gz.sha256 vastora-release-manifest.json; do
if [ ! -f "$asset_dir/$asset" ]; then
echo "GitHub installer release asset is missing: $asset" >&2
return 1
fi
done
version="${tag#v}"
prefix="vastora/releases/$tag/"
if ! jq -e --arg version "$version" --arg prefix "$prefix" '
(.schema == 1) and (.version == $version) and
(.centerImage | test("^ghcr\\.io/petauron/vastora-center@sha256:[0-9a-f]{64}$")) and
((.assets | keys | sort) == ["install.sh", "vastora-center-install.tar.gz", "vastora-center-install.tar.gz.sha256"]) and
(.assets["install.sh"].key == ($prefix + "install.sh")) and
(.assets["vastora-center-install.tar.gz"].key == ($prefix + "vastora-center-install.tar.gz")) and
(.assets["vastora-center-install.tar.gz.sha256"].key == ($prefix + "vastora-center-install.tar.gz.sha256")) and
(all(.assets[]; .sha256 | test("^[0-9a-f]{64}$")))
' "$manifest" >/dev/null; then
echo "GitHub installer release manifest is invalid." >&2
return 1
fi
for asset in install.sh vastora-center-install.tar.gz vastora-center-install.tar.gz.sha256; do
expected="$(jq -r --arg asset "$asset" '.assets[$asset].sha256' "$manifest")"
actual="$(sha256sum "$asset_dir/$asset" | awk 'NR == 1 {print tolower($1)}')"
if [ "$actual" != "$expected" ]; then
echo "GitHub installer release asset digest mismatch: $asset" >&2
return 1
fi
done
}
if [ "$command_name" = "download" ]; then
mkdir -p "$directory"
if find "$directory" -mindepth 1 -maxdepth 1 -print -quit | grep -q .; then
echo "Download directory must be empty: $directory" >&2
exit 1
fi
gh release download "$tag" --dir "$directory"
validate_assets "$directory"
echo "Downloaded and verified GitHub installer assets for $tag."
exit 0
fi
validate_assets "$directory"
temporary_dir="$(mktemp -d "${TMPDIR:-/tmp}/vastora-github-release.XXXXXX")"
cleanup() { rm -rf "$temporary_dir"; }
trap cleanup EXIT HUP INT TERM
# Retry only an individual missing upload, never rebuild or overwrite an asset.
# Refresh the draft after uncertain responses: the upload may have succeeded.
for asset in install.sh vastora-center-install.tar.gz vastora-center-install.tar.gz.sha256 vastora-release-manifest.json; do
attempt=1
while :; do
release_json="$(gh release view "$tag" --json isDraft,assets)"
if ! printf '%s' "$release_json" | jq -e --argjson expected "$expected_assets" '.isDraft == true and (([.assets[].name] - $expected) == [])' >/dev/null; then
echo "Release changed during upload; refusing asset mutation." >&2
exit 1
fi
if printf '%s' "$release_json" | jq -e --arg name "$asset" 'any(.assets[]; .name == $name)' >/dev/null; then
mkdir -p "$temporary_dir/existing"
gh release download "$tag" --pattern "$asset" --dir "$temporary_dir/existing"
if ! cmp -s "$directory/$asset" "$temporary_dir/existing/$asset"; then
echo "Existing GitHub asset differs: $asset; refusing replacement." >&2
exit 1
fi
break
fi
if gh release upload "$tag" "$directory/$asset" >"$temporary_dir/upload.log" 2>&1; then
break
fi
cat "$temporary_dir/upload.log" >&2
if [ "$attempt" -ge 3 ] || ! grep -Eq 'HTTP (404|408|429|5[0-9][0-9])|[Tt]imeout|timed out|connection reset|unexpected EOF|TLS handshake' "$temporary_dir/upload.log"; then
exit 1
fi
echo "Retrying missing upload $asset ($attempt/3)." >&2
sleep "$((attempt * 2))"
attempt="$((attempt + 1))"
done
done
mkdir -p "$temporary_dir/verified"
temporary_assets="$temporary_dir/verified"
gh release download "$tag" --dir "$temporary_assets"
validate_assets "$temporary_assets"
for asset in install.sh vastora-center-install.tar.gz vastora-center-install.tar.gz.sha256 vastora-release-manifest.json; do
if ! cmp -s "$directory/$asset" "$temporary_assets/$asset"; then
echo "Downloaded GitHub Release asset differs from the uploaded file: $asset" >&2
exit 1
fi
done
release_json="$(gh release view "$tag" --json isDraft,assets)"
if ! printf '%s' "$release_json" | jq -e --argjson expected "$expected_assets" '.isDraft == true and ([.assets[].name] | sort) == ($expected | sort)' >/dev/null; then
echo "GitHub Release does not contain exactly the verified installer assets in draft state." >&2
exit 1
fi
echo "Uploaded and verified GitHub installer assets for $tag."