Files
tessera-ctl/.gitea/scripts/publish-release.sh
T
schalli 507556f158
Tessera CI/CD / Lint & Type Check (push) Successful in 49s
Tessera CI/CD / Tests (push) Successful in 1m8s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 4m57s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m9s
fix(ci): Release-Anhaenge nie ueber die oeffentliche Gitea-Adresse hochladen
publish-release.sh ignoriert GITHUB_API_URL/GITHUB_SERVER_URL (git.vicolab.de
hinter dem Proxy bricht grosse Uploads ab -- Release 1.2.0 blieb dadurch
zunaechst ohne Anhaenge). Im CI wird das Host-Gateway des Job-Containers aus
/proc/net/route ermittelt und Gitea direkt auf Port 3002 angesprochen, lokal
localhost:3002; GITEA_API bleibt als Override. Gateway-Weg aus Job-Netz
gegen die Gitea-API verifiziert; Anhaenge fuer v1.2.0 vom Host nachgetragen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 13:48:17 +02:00

250 lines
9.3 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# publish-release.sh -- Gitea-Release je Freigabe-Tag aus CHANGELOG.md anlegen
# (quick-260916-dcz).
#
# Entscheidung wie publish-images.sh allein anhand GITHUB_REF:
# refs/tags/vX.Y.Z -> Abschnitt "## X.Y.Z" aus CHANGELOG.md schneiden und als
# Release "Tessera X.Y.Z" anlegen (bzw. aktualisieren, wenn
# der Release zum Tag schon existiert -- idempotent)
# alles andere -> nichts zu tun (Exit 0)
#
# Aufrufformen:
# sh .gitea/scripts/publish-release.sh # im CI, Tag aus GITHUB_REF
# sh .gitea/scripts/publish-release.sh --tag v1.0.0 # lokal, expliziter Tag
# sh .gitea/scripts/publish-release.sh --dry-run --tag v1.0.0 # nur JSON und Ziel zeigen
#
# Umgebung:
# GITEA_TOKEN Zugriffstoken (Pflicht im echten Lauf; im CI aus secrets.REGISTRY_TOKEN
# ueber `env`). Wird nie ausgegeben und nie als Argument uebergeben --
# der Authorization-Header kommt aus einer temporaeren Datei.
# GITEA_API API-Basis (expliziter Override). Ohne Angabe NIE die oeffentliche
# Adresse (GITHUB_API_URL / GITHUB_SERVER_URL zeigen auf
# git.vicolab.de hinter dem Proxy, der grosse Uploads abbricht --
# Release 1.2.0 hatte deshalb zunaechst keine Anhaenge): im CI wird
# das Host-Gateway des Job-Containers aus /proc/net/route ermittelt
# und Gitea direkt auf Port 3002 angesprochen (derselbe Weg wie der
# Registry-Push), lokal http://localhost:3002/api/v1.
# GITEA_REPO owner/repo; sonst GITHUB_REPOSITORY, sonst schalli/tessera-ctl.
# CHANGELOG_FILE Pfad zur Aenderungsliste; Vorgabe CHANGELOG.md.
# DESKTOP_DIST Ordner mit den Desktop-Paketen und manifest.json (Phase 18,
# 18-02); Vorgabe desktop-dist. Fehlt manifest.json bei Tags,
# bricht das Skript ab -- der Release-Text ist dann schon
# angelegt/aktualisiert, der Job wird sichtbar rot.
#
# Fehlt der Abschnitt fuer die Version, endet das Skript mit Exit 1 -- es entsteht
# nie ein leerer Release. JSON wird ausschliesslich mit jq gebaut.
#
# Release-Dateien (Phase 18, 18-02): jede Datei aus manifest.json wird idempotent
# angehaengt -- GET .../releases/{id}/assets, vorhandene Datei gleichen Namens per
# DELETE .../releases/{id}/assets/{asset_id} entfernen, dann frisch per
# POST .../releases/{id}/assets?name=... (multipart-Feld attachment) hochladen.
# Der multipart-Upload braucht eine zweite Header-Datei ($HDR_AUTH) OHNE
# Content-Type: application/json -- curl setzt den multipart-Content-Type sonst
# nicht korrekt, wenn der JSON-Header schon gesetzt ist.
set -eu
usage() {
echo "Aufruf: publish-release.sh [--dry-run] [--tag vX.Y.Z]" >&2
}
DRY_RUN=0
TAG=""
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY_RUN=1 ;;
--tag)
[ $# -ge 2 ] || { usage; exit 2; }
TAG="$2"
shift
;;
*) usage; exit 2 ;;
esac
shift
done
if [ -z "$TAG" ]; then
REF="${GITHUB_REF:-}"
case "$REF" in
refs/tags/v*) TAG="${REF#refs/tags/}" ;;
*)
echo "Kein Freigabe-Tag (nur refs/tags/v*): nichts zu tun."
exit 0
;;
esac
fi
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Ungueltiger Tag '$TAG' (erwartet vX.Y.Z)." >&2
exit 1
fi
VERSION="${TAG#v}"
# Host-Gateway des Containers: Default-Route in /proc/net/route, Gateway als
# Hex in Little-Endian (z. B. 010011AC = 172.17.0.1). Reines POSIX sh.
host_gateway() {
[ -r /proc/net/route ] || return 1
gw=$(awk '$2 == "00000000" { print $3; exit }' /proc/net/route)
[ -n "$gw" ] || return 1
printf '%d.%d.%d.%d\n' \
"0x$(printf '%s' "$gw" | cut -c7-8)" "0x$(printf '%s' "$gw" | cut -c5-6)" \
"0x$(printf '%s' "$gw" | cut -c3-4)" "0x$(printf '%s' "$gw" | cut -c1-2)"
}
if [ -n "${GITEA_API:-}" ]; then
API="$GITEA_API"
elif [ -n "${GITHUB_ACTIONS:-}${CI:-}" ] && GW=$(host_gateway); then
API="http://$GW:3002/api/v1"
else
API="http://localhost:3002/api/v1"
fi
API="${API%/}"
REPO="${GITEA_REPO:-${GITHUB_REPOSITORY:-schalli/tessera-ctl}}"
echo "Gitea-API: $API Repo: $REPO Tag: $TAG"
CHANGELOG="${CHANGELOG_FILE:-CHANGELOG.md}"
if [ ! -f "$CHANGELOG" ]; then
echo "$CHANGELOG nicht gefunden." >&2
exit 1
fi
command -v jq >/dev/null 2>&1 || { echo "jq fehlt." >&2; exit 1; }
# Abschnitt "## X.Y.Z" bis zur naechsten "## "-Ueberschrift, ohne die eigene
# Ueberschrift; danach fuehrende und abschliessende Leerzeilen entfernen.
BODY=$(awk -v ver="$VERSION" '
BEGIN { esc = ver; gsub(/\./, "\\.", esc); pat = "^## " esc "( |$)" }
$0 ~ pat { f = 1; next }
/^## / { if (f) exit }
f { print }
' "$CHANGELOG" | awk '
{ line[NR] = $0; if ($0 !~ /^[[:space:]]*$/) last = NR }
END { for (i = 1; i <= last; i++) print line[i] }
' | sed '1{/^$/d}')
if [ -z "$BODY" ]; then
echo "$CHANGELOG hat keinen Abschnitt fuer Version $VERSION (erwartet eine Zeile '## $VERSION – <Datum>'). Kein Release ohne Text." >&2
exit 1
fi
NAME="Tessera $VERSION"
CREATE_JSON=$(jq -n --arg tag "$TAG" --arg name "$NAME" --arg body "$BODY" \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
UPDATE_JSON=$(jq -n --arg name "$NAME" --arg body "$BODY" '{name: $name, body: $body}')
RELEASES_URL="$API/repos/$REPO/releases"
TAG_URL="$API/repos/$REPO/releases/tags/$TAG"
DESKTOP_DIST="${DESKTOP_DIST:-desktop-dist}"
MANIFEST="$DESKTOP_DIST/manifest.json"
if [ "$DRY_RUN" -eq 1 ]; then
echo "Probelauf (kein Netzaufruf):"
echo " POST $RELEASES_URL"
echo " PATCH $RELEASES_URL/<id> (falls GET $TAG_URL bereits 200 liefert)"
printf '%s\n' "$CREATE_JSON"
if [ -f "$MANIFEST" ]; then
for FNAME in $(jq -r '.files[].name' "$MANIFEST"); do
echo " POST $RELEASES_URL/<id>/assets?name=$FNAME"
done
else
echo " ($MANIFEST fehlt -- keine geplanten Uploads im Probelauf)"
fi
exit 0
fi
if [ -z "${GITEA_TOKEN:-}" ]; then
echo "Kein Zugriffstoken in der Umgebung gesetzt (siehe Kopfkommentar)." >&2
exit 1
fi
umask 077
TMPDIR_REL=$(mktemp -d)
trap 'rm -rf "$TMPDIR_REL"' EXIT INT TERM
HDR="$TMPDIR_REL/headers"
HDR_AUTH="$TMPDIR_REL/headers-auth"
RESP="$TMPDIR_REL/response.json"
ASSETS_RESP="$TMPDIR_REL/assets.json"
JSONFILE="$TMPDIR_REL/payload.json"
printf 'Authorization: token %s\nContent-Type: application/json\n' "$GITEA_TOKEN" > "$HDR"
# Zweite Header-Datei ohne Content-Type: application/json -- der multipart-Upload
# (POST .../assets) darf keinen JSON-Content-Type mitbekommen.
printf 'Authorization: token %s\n' "$GITEA_TOKEN" > "$HDR_AUTH"
# upload_asset FILE NAME RELEASE_ID -- idempotent: vorhandene Datei gleichen Namens
# wird zuerst entfernt (GET -> DELETE), dann frisch hochgeladen (POST multipart).
upload_asset() {
ASSET_FILE="$1"
ASSET_NAME="$2"
ASSET_RELEASE_ID="$3"
ASSETS_CODE=$(curl -sS --header @"$HDR" -o "$ASSETS_RESP" -w '%{http_code}' "$RELEASES_URL/$ASSET_RELEASE_ID/assets")
if [ "$ASSETS_CODE" != "200" ]; then
echo "GET $RELEASES_URL/$ASSET_RELEASE_ID/assets antwortete mit $ASSETS_CODE:" >&2
cat "$ASSETS_RESP" >&2
exit 1
fi
EXISTING_ID=$(jq -r --arg n "$ASSET_NAME" '.[] | select(.name == $n) | .id' "$ASSETS_RESP")
if [ -n "$EXISTING_ID" ]; then
DEL_CODE=$(curl -sS --header @"$HDR" -X DELETE -o "$RESP" -w '%{http_code}' "$RELEASES_URL/$ASSET_RELEASE_ID/assets/$EXISTING_ID")
if [ "$DEL_CODE" != "204" ]; then
echo "DELETE $RELEASES_URL/$ASSET_RELEASE_ID/assets/$EXISTING_ID antwortete mit $DEL_CODE:" >&2
cat "$RESP" >&2
exit 1
fi
fi
UPLOAD_CODE=$(curl -sS --header @"$HDR_AUTH" -X POST -F "attachment=@${ASSET_FILE};filename=${ASSET_NAME}" -o "$RESP" -w '%{http_code}' "$RELEASES_URL/$ASSET_RELEASE_ID/assets?name=$ASSET_NAME")
if [ "$UPLOAD_CODE" != "201" ]; then
echo "POST $RELEASES_URL/$ASSET_RELEASE_ID/assets?name=$ASSET_NAME antwortete mit $UPLOAD_CODE:" >&2
cat "$RESP" >&2
exit 1
fi
}
CODE=$(curl -sS --header @"$HDR" -o "$RESP" -w '%{http_code}' "$TAG_URL")
case "$CODE" in
200)
ID=$(jq -r .id "$RESP")
printf '%s' "$UPDATE_JSON" > "$JSONFILE"
CODE=$(curl -sS --header @"$HDR" -X PATCH --data @"$JSONFILE" -o "$RESP" -w '%{http_code}' "$RELEASES_URL/$ID")
if [ "$CODE" = "200" ]; then
echo "Release $TAG aktualisiert (id $ID)"
else
echo "PATCH $RELEASES_URL/$ID antwortete mit $CODE:" >&2
cat "$RESP" >&2
exit 1
fi
;;
404)
printf '%s' "$CREATE_JSON" > "$JSONFILE"
CODE=$(curl -sS --header @"$HDR" -X POST --data @"$JSONFILE" -o "$RESP" -w '%{http_code}' "$RELEASES_URL")
if [ "$CODE" = "201" ]; then
ID=$(jq -r .id "$RESP")
echo "Release $TAG angelegt (id $ID)"
else
echo "POST $RELEASES_URL antwortete mit $CODE:" >&2
cat "$RESP" >&2
exit 1
fi
;;
*)
echo "GET $TAG_URL antwortete mit $CODE:" >&2
cat "$RESP" >&2
exit 1
;;
esac
# Release-Dateien aus dem Manifest anhaengen (Phase 18, 18-02). Der Release-Text
# ist an dieser Stelle bereits angelegt/aktualisiert -- fehlt das Manifest, wird
# das trotzdem hart abgebrochen (kein Release ohne Pakete bei einem Freigabe-Tag).
if [ ! -f "$MANIFEST" ]; then
echo "$MANIFEST fehlt -- kein Release ohne Desktop-Pakete." >&2
exit 1
fi
for FNAME in $(jq -r '.files[].name' "$MANIFEST"); do
upload_asset "$DESKTOP_DIST/$FNAME" "$FNAME" "$ID"
echo "Release-Datei $FNAME hochgeladen"
done