#!/usr/bin/env bash # EdgeGuard — Release erstellen (Testing-Push oder Stable-Promotion) # # Verwendung: # ./scripts/release.sh # Testing-Push: baut den aktuellen # # Stand, Version = datumsbasiert # # YYYY.MM.DD.NN, Upload → testing # ./scripts/release.sh stable # Stable-Promotion: baut den # # aktuellen Stand unter der # # nächsten Patch-Version (VERSION- # # Datei +1), Upload → stable, # # VERSION-Commit + Git-Tag v # ./scripts/release.sh stable 1.4.0 # Stable mit expliziter Version # # Kanal-Modell 1:1 von enconf (netcell-webpanel) übernommen — Suite=trixie # fest, Komponente=Kanal (stable/testing). An EdgeGuard angepasst: # - Gates laufen über die bestehende Go-Quality-Baseline (`make deb` ruft # `release-check` + `management-ui`-tsc automatisch auf) statt eigener # Preflight-Schritte. # - Kein Docs-/Changelog-/Checksum-/Marketing-Site-Deploy — dafür # existiert bei EdgeGuard keine Infrastruktur (siehe CLAUDE.md). # - Testing-Versionen sind datumsbasiert (wie enconf) DAMIT sie (a) bei # apt immer über jeder Stable-Semver-Version sortieren und (b) nie mit # einer Stable-Versionsnummer kollidieren — Gitea dedupliziert # (Name,Version) global über alle Kanäle hinweg. # # Voraussetzung: GITEA_TOKEN (env oder ~/.gitea-token) mit Package- # Upload/Delete/Query-Scope. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$REPO_DIR" OWNER="projekte" GITEA_URL="https://git.netcell-it.de" KEEP="${KEEP:-10}" log() { echo "[release] $*"; } warn() { echo " ⚠ $*" >&2; } abort() { echo "🛑 ABBRUCH: $*" >&2; exit 1; } if [ -z "${GITEA_TOKEN:-}" ]; then if [ -r "$HOME/.gitea-token" ]; then GITEA_TOKEN="$(tr -d '\n' < "$HOME/.gitea-token")" else abort "GITEA_TOKEN fehlt (env oder ~/.gitea-token)" fi fi export GITEA_TOKEN # ─── Testing-Version: datumsbasiert YYYY.MM.DD.NN ────────────────────── # NN = laufende Nummer für den Tag, ermittelt aus den bereits im Registry # vorhandenen Versionen des Meta-Pakets (edgeguard) mit dem heutigen # Datumspräfix. Erster Release des Tages -> .01. next_testing_version() { local today today="$(date -u +%Y.%m.%d)" local raw raw="$(curl -fsS -H "Authorization: token $GITEA_TOKEN" \ "$GITEA_URL/api/v1/packages/$OWNER?type=debian&q=edgeguard&limit=1000" 2>/dev/null || echo '[]')" local last_n last_n="$(printf '%s' "$raw" | jq -r --arg n "edgeguard" --arg pfx "${today}." \ '.[] | select(.name==$n) | .version | select(startswith($pfx))' \ | sed "s/^${today}\.//" | sort -n | tail -1)" printf '%s.%02d' "$today" "$(( ${last_n:-0} + 1 ))" } # ─── Stable-Version: Patch-Bump der VERSION-Datei (Default) ──────────── bump_patch() { local major minor patch IFS='.' read -r major minor patch <<< "$1" echo "${major}.${minor}.$((patch + 1))" } # ─── Alte Version im Ziel-Kanal ersetzen — Upload derselben Nummer würde # sonst mit 409 den alten Inhalt behalten (Gitea dedupliziert (Name, # Version,Arch,Kanal-Pool) — best-effort, 404 ist der Normalfall). ────── delete_before_upload() { local channel="$1" version="$2" name="$3" arch="$4" local url="$GITEA_URL/api/packages/$OWNER/debian/pool/trixie/$channel/$name/$version/$arch" curl -sS -o /dev/null -w '' -X DELETE -H "Authorization: token $GITEA_TOKEN" "$url" || true } replace_in_channel() { local channel="$1" version="$2" delete_before_upload "$channel" "$version" edgeguard-api amd64 delete_before_upload "$channel" "$version" edgeguard-api arm64 delete_before_upload "$channel" "$version" edgeguard-ui all delete_before_upload "$channel" "$version" edgeguard all } # ─── verify_channel_debs — Health-Gate wie enconf verify_stable_debs ─── # Holt den Packages-Index eines Kanals und prüft für JEDE dort # referenzierte .deb, ob sie unter ihrer Pool-URL wirklich mit HTTP 200 # abrufbar ist (nicht nur ob der Index existiert — Gitea regeneriert den # Index nicht immer zuverlässig nach einem Delete). verify_channel_debs() { local channel="$1" log "verify_channel_debs($channel): jede indexierte .deb muss abrufbar sein..." local files f code bad=0 checked=0 files="$(curl -sk -H "Authorization: token $GITEA_TOKEN" \ "$GITEA_URL/api/packages/$OWNER/debian/dists/trixie/$channel/binary-amd64/Packages" 2>/dev/null \ | awk '/^Filename:/{print $2}')" if [ -z "$files" ]; then warn "$channel: Packages-Index leer/nicht erreichbar — übersprungen" return 0 fi while IFS= read -r f; do [ -z "$f" ] && continue checked=$((checked + 1)) code="$(curl -sk -o /dev/null -w '%{http_code}' \ -H "Authorization: token $GITEA_TOKEN" "$GITEA_URL/api/packages/$OWNER/debian/$f")" if [ "$code" != "200" ]; then warn "$channel: $f -> HTTP $code" bad=$((bad + 1)) fi done <<< "$files" [ "$bad" -eq 0 ] || abort "$channel: $bad von $checked .deb(s) im Index nicht abrufbar — Registry inkonsistent." log "$channel: $checked .deb(s) verifiziert, alle abrufbar." } # ─── Testing-Push ─────────────────────────────────────────────────────── testing_push() { local new_version new_version="$(next_testing_version)" log "[testing 1/3] Gates + Build $new_version (make deb — release-check, amd64+arm64+ui)..." make VERSION="$new_version" deb log "[testing 2/3] Alte Version im testing-Kanal ersetzen + hochladen..." replace_in_channel testing "$new_version" ./scripts/apt-repo/publish.sh "$new_version" amd64 testing ./scripts/apt-repo/publish.sh "$new_version" arm64 testing log "[testing 3/3] Cleanup (keep last $KEEP) + Verify..." KEEP="$KEEP" ./scripts/apt-repo/cleanup-old.sh testing verify_channel_debs testing log "✅ Testing-Release $new_version veröffentlicht." } # ─── Stable-Promotion ─────────────────────────────────────────────────── promote_stable() { local explicit_version="${1:-}" local current_version new_version current_version="$(cat "$REPO_DIR/VERSION")" new_version="${explicit_version:-$(bump_patch "$current_version")}" git -C "$REPO_DIR" rev-parse "v$new_version" >/dev/null 2>&1 && \ abort "Git-Tag v$new_version existiert bereits." # --untracked-files=no: nur versionierte Änderungen blocken. Das Repo # kann fremde, noch nicht committete Arbeit in eigenen Verzeichnissen # liegen haben (untracked) — die geht ein Stable-Release nichts an. [ -n "$(git -C "$REPO_DIR" status --porcelain --untracked-files=no)" ] && \ abort "Uncommittete Änderungen an versionierten Dateien — commit/stash erst, dann Stable-Release." log "[stable 1/4] Gates + Build $new_version (make deb — release-check, amd64+arm64+ui)..." make VERSION="$new_version" deb log "[stable 2/4] Alte Version im stable-Kanal ersetzen + hochladen..." replace_in_channel stable "$new_version" ./scripts/apt-repo/publish.sh "$new_version" amd64 stable ./scripts/apt-repo/publish.sh "$new_version" arm64 stable log "[stable 3/4] VERSION-Datei setzen + Commit + Git-Tag..." printf '%s' "$new_version" > "$REPO_DIR/VERSION" git -C "$REPO_DIR" add VERSION git -C "$REPO_DIR" commit -m "chore(release): v$new_version stable" git -C "$REPO_DIR" tag "v$new_version" log "[stable 4/4] Cleanup (keep last $KEEP) + Verify..." KEEP="$KEEP" ./scripts/apt-repo/cleanup-old.sh stable verify_channel_debs stable log "✅ Stable-Release v$new_version fertig." log " Push nicht vergessen: git push origin main --tags" } case "${1:-}" in stable) promote_stable "${2:-}" ;; "") testing_push ;; *) abort "unbekanntes Argument '$1' (erwartet: 'stable' oder kein Argument)" ;; esac