From 6940bd05a174b0ba50d668031fbe4b9ed13a5677 Mon Sep 17 00:00:00 2001 From: Schalli Date: Wed, 16 Sep 2026 11:24:20 +0200 Subject: [PATCH] ci(quick-260916-dcz): Gitea-Release je Freigabe-Tag aus CHANGELOG.md (publish-release.sh, idempotent, --dry-run), Schritt in ci.yml - publish-release.sh: POSIX sh, Entscheidung anhand GITHUB_REF wie publish-images.sh, --tag/--dry-run, Abschnitt per awk, JSON nur per jq --arg, Token nur aus GITEA_TOKEN ueber Header-Datei, GET/tags -> PATCH oder POST, Exit 1 ohne Abschnitt - ci.yml: vierter Schritt im Job publish mit GITEA_TOKEN aus secrets.REGISTRY_TOKEN ueber env - Bewiesen: dry-run v1.0.0 (Body 2227 Zeichen), v9.9.9 Exit 1, main nichts zu tun, API-Basis aus GITHUB_SERVER_URL; Docker-Abbild traegt den Changelog nur im Server-Bundle (1/0); Release Tessera 1.0.0 (id 1) angelegt, zweiter Lauf PATCH, genau ein Release Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY --- .gitea/scripts/publish-release.sh | 155 ++++++++++++++++++++++++++++++ .gitea/workflows/ci.yml | 6 ++ 2 files changed, 161 insertions(+) create mode 100755 .gitea/scripts/publish-release.sh diff --git a/.gitea/scripts/publish-release.sh b/.gitea/scripts/publish-release.sh new file mode 100755 index 0000000..8bc31a1 --- /dev/null +++ b/.gitea/scripts/publish-release.sh @@ -0,0 +1,155 @@ +#!/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; sonst GITHUB_API_URL, sonst GITHUB_SERVER_URL/api/v1, +# sonst http://localhost:3002/api/v1 (nur lokal erreichbar). +# GITEA_REPO owner/repo; sonst GITHUB_REPOSITORY, sonst schalli/tessera-ctl. +# CHANGELOG_FILE Pfad zur Aenderungsliste; Vorgabe CHANGELOG.md. +# +# Fehlt der Abschnitt fuer die Version, endet das Skript mit Exit 1 -- es entsteht +# nie ein leerer Release. JSON wird ausschliesslich mit jq gebaut. +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}" + +API="${GITEA_API:-${GITHUB_API_URL:-${GITHUB_SERVER_URL:+${GITHUB_SERVER_URL}/api/v1}}}" +API="${API:-http://localhost:3002/api/v1}" +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 – '). 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" + +if [ "$DRY_RUN" -eq 1 ]; then + echo "Probelauf (kein Netzaufruf):" + echo " POST $RELEASES_URL" + echo " PATCH $RELEASES_URL/ (falls GET $TAG_URL bereits 200 liefert)" + printf '%s\n' "$CREATE_JSON" + 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" +RESP="$TMPDIR_REL/response.json" +JSONFILE="$TMPDIR_REL/payload.json" +printf 'Authorization: token %s\nContent-Type: application/json\n' "$GITEA_TOKEN" > "$HDR" + +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 + echo "Release $TAG angelegt (id $(jq -r .id "$RESP"))" + 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 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 2d2fd7a..41fc4b5 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,6 +1,7 @@ # Kanalmodell (quick-260914-ku1): main -> Kanal beta (Etiketten beta + latest); # Tag v* -> Kanal live (Etiketten live + vX.Y.Z); Zweig live ohne Tag wird nur geprueft. # Die Entscheidung trifft .gitea/scripts/publish-images.sh anhand GITHUB_REF. +# Tag v* (quick-260916-dcz): zusaetzlich Gitea-Release aus dem CHANGELOG.md-Abschnitt (publish-release.sh). name: Tessera CI/CD on: @@ -66,3 +67,8 @@ jobs: - name: Versionsstempel berechnen, Abbilder bauen und veroeffentlichen run: sh .gitea/scripts/publish-images.sh + + - name: Gitea-Release zum Freigabe-Tag anlegen (nur bei Tags v*) + env: + GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: sh .gitea/scripts/publish-release.sh