#!/usr/bin/env bash # Gitea-Repo-Anlage für Lucy (17.07.2026): schließt die Lücke im „Projekte selbst # starten"-Weg — bislang konnte die Werkstatt nur Branches in BESTEHENDE Repos # proposen, aber kein neues Projekt-Repo anlegen. # # Nutzung: gitea-repo-create.sh ["Beschreibung"] # Ergebnis (stdout, letzte Zeile): CLONE → Aufrufer kann direkt klonen. # # HARTE Sicherheits-Leitplanken (Gitea ist öffentlich erreichbar!): # • Repos werden IMMER PRIVAT angelegt (kein öffentliches Repo aus Versehen). # • Token wird aus ~/.git-credentials gelesen, NIE ausgegeben/geloggt. # • Jede Anlage wird protokolliert (Audit-Spur) + still an die Chronik gespiegelt. # Braucht ein Token mit Scope `write:user` (zusätzlich zu `write:repository`); ohne das # meldet Gitea 403 — das Skript sagt dann genau, was zu tun ist (Token in Gitea neu # generieren), statt kryptisch zu scheitern. set -uo pipefail NAME="${1:-}" BESCHREIBUNG="${2:-Von Lucy angelegtes Projekt-Repo.}" CRED="$HOME/.git-credentials" LOG="$HOME/.hermes/state/gitea-repos-created.log" if [ -z "$NAME" ]; then echo "Nutzung: gitea-repo-create.sh [\"Beschreibung\"]" >&2 exit 2 fi # Repo-Name defensiv prüfen (Gitea erlaubt Buchstaben/Ziffern/-/_/.) if ! printf '%s' "$NAME" | grep -qE '^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$'; then echo "Ungültiger Repo-Name '$NAME' (erlaubt: Buchstaben/Ziffern/.-_, Beginn alphanumerisch)." >&2 exit 2 fi if [ ! -r "$CRED" ]; then echo "Keine ~/.git-credentials gefunden — ohne gespeicherte Gitea-Anmeldung geht das nicht." >&2 exit 1 fi # Zeile für den Gitea-Host herausgreifen (falls mehrere Hosts hinterlegt sind) LINE="$(grep -m1 'git\.tobisniceshomelab\.ddnsfree\.com' "$CRED" || head -1 "$CRED")" USER="$(printf '%s' "$LINE" | sed -E 's#https://([^:]+):.*#\1#')" TOKEN="$(printf '%s' "$LINE" | sed -E 's#https://[^:]+:([^@]+)@.*#\1#')" HOST="$(printf '%s' "$LINE" | sed -E 's#https://[^@]+@([^/]+).*#\1#')" BASE="https://${HOST}" if [ -z "$USER" ] || [ -z "$TOKEN" ] || [ -z "$HOST" ]; then echo "Konnte User/Token/Host nicht aus ~/.git-credentials lesen." >&2 exit 1 fi RESP="$(mktemp)"; trap 'rm -f "$RESP"' EXIT CODE="$(curl -s -o "$RESP" -w '%{http_code}' -X POST "$BASE/api/v1/user/repos" \ -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \ -d "$(python3 -c 'import json,sys; print(json.dumps({"name":sys.argv[1],"description":sys.argv[2],"private":True,"auto_init":True,"default_branch":"main"}))' "$NAME" "$BESCHREIBUNG")")" case "$CODE" in 201) CLONE="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("clone_url",""))' "$RESP" 2>/dev/null)" FULL="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("full_name",""))' "$RESP" 2>/dev/null)" mkdir -p "$(dirname "$LOG")" echo "$(date '+%Y-%m-%d %H:%M') angelegt (privat): $FULL" >> "$LOG" # Stiller Chronik-Spiegel (Audit-Spur, wie die Radar-Meldungen) curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \ -H 'Content-Type: application/json' \ --data "$(python3 -c 'import json,sys; print(json.dumps({"text":"Neues PRIVATES Projekt-Repo angelegt: "+sys.argv[1],"subject":"Gitea","source":"gitea-repo-create","priority":"silent"}))' "$FULL")" \ >/dev/null 2>&1 || true echo "Repo '$FULL' angelegt (privat, main initialisiert)." echo "CLONE ${CLONE}" exit 0 ;; 403) if grep -q 'write:user' "$RESP"; then echo "FEHLT: Das gespeicherte Gitea-Token darf keine Repos anlegen (Scope 'write:user' fehlt)." >&2 echo "So wird es freigeschaltet (einmalig, durch den Commander):" >&2 echo " Gitea → Einstellungen → Anwendungen → neues Token mit Scopes 'write:repository' UND 'write:user'," >&2 echo " dann in ~/.git-credentials das alte Token ersetzen. Danach funktioniert dieses Skript." >&2 else echo "403 von Gitea:" >&2; head -c 300 "$RESP" >&2; echo >&2 fi exit 3 ;; 409) echo "Ein Repo namens '$NAME' existiert bereits." >&2 exit 4 ;; *) echo "Gitea antwortete mit HTTP $CODE:" >&2; head -c 300 "$RESP" >&2; echo >&2 exit 1 ;; esac