#!/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 – '). 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/ (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//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