507556f158
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>
250 lines
9.3 KiB
Bash
Executable File
250 lines
9.3 KiB
Bash
Executable File
#!/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
|