release: refuse upload over existing tag unless --replace
scripts/build-release.sh --upload now checks for an existing release at the given tag before POSTing a new one. If found and --replace is not set, errors out with a clear message. This prevents the silent-overwrite problem: a user on v2.9.7 running ./scripts/upgrade.sh sees "no update available" when the v2.9.7 release's tarball contents have silently changed. Version tags should be immutable once published. --replace is still available for deliberate test-bench iteration (DELETEs the existing release, then POSTs). Documented as destructive in the --help output and DEV_WORKFLOW.md. Bunker Admin
This commit is contained in:
parent
6e01d580b2
commit
c2f12aa2bf
@ -157,8 +157,17 @@ Packages only runtime files (~9 MB) — no source code, no node_modules:
|
|||||||
|
|
||||||
# Preview contents without creating tarball
|
# Preview contents without creating tarball
|
||||||
./scripts/build-release.sh --dry-run
|
./scripts/build-release.sh --dry-run
|
||||||
|
|
||||||
|
# --upload refuses to overwrite an existing tag. To deliberately replace
|
||||||
|
# a release (destructive — users on that tag see no upgrade signal):
|
||||||
|
./scripts/build-release.sh --tag v2.2.0 --upload --replace
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Version hygiene:** bump the tag when changing release contents. Overwriting
|
||||||
|
an existing release silently breaks upgrade checks for users already on that
|
||||||
|
version — they see "no update available" even though the tarball they'd
|
||||||
|
download differs.
|
||||||
|
|
||||||
The tarball contains:
|
The tarball contains:
|
||||||
- `docker-compose.yml` (copy of `docker-compose.prod.yml` — image-only, no build blocks)
|
- `docker-compose.yml` (copy of `docker-compose.prod.yml` — image-only, no build blocks)
|
||||||
- `.env.example`, `config.sh` (configuration wizard)
|
- `.env.example`, `config.sh` (configuration wizard)
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
# --tag TAG Version tag (default: git describe or commit SHA)
|
# --tag TAG Version tag (default: git describe or commit SHA)
|
||||||
# --output DIR Output directory (default: ./releases/)
|
# --output DIR Output directory (default: ./releases/)
|
||||||
# --upload Upload to Gitea Releases API after building
|
# --upload Upload to Gitea Releases API after building
|
||||||
|
# --replace Delete + recreate an existing release with this tag
|
||||||
|
# (DESTRUCTIVE: users already on TAG see no upgrade signal)
|
||||||
# --dry-run Show what would be included without creating tarball
|
# --dry-run Show what would be included without creating tarball
|
||||||
# --help Show this help
|
# --help Show this help
|
||||||
#
|
#
|
||||||
@ -28,6 +30,7 @@ TAG=""
|
|||||||
OUTPUT_DIR="${PROJECT_DIR}/releases"
|
OUTPUT_DIR="${PROJECT_DIR}/releases"
|
||||||
UPLOAD=false
|
UPLOAD=false
|
||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
|
REPLACE=false
|
||||||
|
|
||||||
# --- Colors ---
|
# --- Colors ---
|
||||||
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
|
||||||
@ -48,6 +51,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--tag) TAG="$2"; shift 2 ;;
|
--tag) TAG="$2"; shift 2 ;;
|
||||||
--output) OUTPUT_DIR="$2"; shift 2 ;;
|
--output) OUTPUT_DIR="$2"; shift 2 ;;
|
||||||
--upload) UPLOAD=true; shift ;;
|
--upload) UPLOAD=true; shift ;;
|
||||||
|
--replace) REPLACE=true; shift ;;
|
||||||
--dry-run) DRY_RUN=true; shift ;;
|
--dry-run) DRY_RUN=true; shift ;;
|
||||||
--help|-h)
|
--help|-h)
|
||||||
sed -n '2,20p' "$0" | grep '^#' | sed 's/^# \?//'
|
sed -n '2,20p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||||
@ -223,6 +227,31 @@ if [[ "$UPLOAD" == "true" ]]; then
|
|||||||
warn "GITEA_REGISTRY_API_TOKEN not set — skipping upload"
|
warn "GITEA_REGISTRY_API_TOKEN not set — skipping upload"
|
||||||
warn "Set GITEA_REGISTRY_API_TOKEN in .env and re-run with --upload"
|
warn "Set GITEA_REGISTRY_API_TOKEN in .env and re-run with --upload"
|
||||||
else
|
else
|
||||||
|
# Refuse to overwrite an existing release unless --replace is explicit.
|
||||||
|
# Silently overwriting a published tag breaks upgrade checks for users
|
||||||
|
# already on that version (they see "no update available" even though
|
||||||
|
# the tarball changed).
|
||||||
|
EXISTING_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
"${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases/tags/${TAG}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}")
|
||||||
|
if [[ "$EXISTING_RESPONSE" == "200" ]]; then
|
||||||
|
if [[ "$REPLACE" != "true" ]]; then
|
||||||
|
error "Release ${TAG} already exists."
|
||||||
|
error " Bump the version to a new tag, or pass --replace to delete and recreate."
|
||||||
|
error " --replace is destructive: users on ${TAG} will see no upgrade signal but get different tarball contents."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
warn "Release ${TAG} exists — deleting first (--replace)"
|
||||||
|
EXISTING_ID=$(curl -sf "${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases/tags/${TAG}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "")
|
||||||
|
if [[ -n "$EXISTING_ID" ]]; then
|
||||||
|
curl -sf -X DELETE \
|
||||||
|
"${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases/${EXISTING_ID}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" >/dev/null
|
||||||
|
success "Deleted existing release ${TAG} (id ${EXISTING_ID})"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
info "Creating Gitea release ${TAG}..."
|
info "Creating Gitea release ${TAG}..."
|
||||||
RELEASE_RESPONSE=$(curl -sf -X POST \
|
RELEASE_RESPONSE=$(curl -sf -X POST \
|
||||||
"${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases" \
|
"${GITEA_HOST}/api/v1/repos/admin/changemaker.lite/releases" \
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user