Files
edgeguard-native/scripts/release.sh
noroot 561816d79d fix(release): bun statt npm-Fallback für Release-Builds erzwingen
make ui fällt ohne bun still auf npm install zurück — löst Dependencies
gegen package.json neu auf statt gegen das gepinnte management-ui/
bun.lock und hinterlässt eine dazu inkonsistente package-lock.json.
Der gelockerte Dirty-Check (--untracked-files=no, letzter Commit)
ignoriert genau dieses Artefakt, weil es zusammen mit anderer fremder
unversionierter Arbeit im Repo liegt — beide Fixes zusammen hätten
also einen Release mit abweichender Dependency-Auflösung klaglos
durchgelassen.

release.sh bricht jetzt hart ab wenn bun fehlt, statt sich auf den
Fallback zu verlassen — Release-Builds (Testing UND Stable) müssen
reproduzierbar aus dem gepinnten Lockfile bauen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:46:12 +02:00

194 lines
8.7 KiB
Bash
Executable File

#!/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<version>
# ./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
# `make ui` fällt bei fehlendem bun auf `npm install` zurück (Convenience
# für schnelle lokale Builds) — das löst Dependencies gegen package.json
# neu auf statt gegen das gepinnte management-ui/bun.lock, und hinterlässt
# eine package-lock.json die nichts mit dem committeten Lockfile zu tun
# hat. Für einen Release (Testing ODER Stable) ist das nicht akzeptabel —
# beide Kanäle müssen reproduzierbar aus dem gepinnten Lockfile bauen.
command -v bun >/dev/null 2>&1 || \
abort "bun fehlt — Release-Builds müssen aus management-ui/bun.lock bauen, nicht aus dem npm-Fallback. Installieren: https://bun.sh/install"
# ─── 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