7fac17ed9a
Die Intelligenz sitzt nicht mehr NEBEN dem Coden, sondern DRIN. Alles auf der Box gemessen, nicht angenommen. Stufe 1 — Speicher-Haushalt (llama-swap-Config, nicht im Repo): Coder dauerwarm statt ttl 600 (Kaltstart 23 s -> 491 ms), Devstral als 'kritiker' verdrahtet, Swap 11 GB -> 0. Zwei Befunde: Kontext 131k->65k spart bei Qwen3-Next 0 GB (Hybrid-Attention), und llama-swap haelt nur EINE Gruppe resident -> alles Ko-Residente in dieselbe Gruppe. `persistent: true` verhindert dabei das Freiraeumen vor grossen Modellen -> ausgeloester Kernel-OOM, behoben durch persistent:false + TTLs (Vision laedt jetzt in 10 s statt 117 s + Absturz). Stufe 2 — Governor v2 (deploy/governor/): Steht jetzt IM Pfad (:8100 -> MC2 :9001) statt daneben. Zaehlt den GANZEN Anfragekoerper inkl. tools/tool_calls und kalibriert sich aus den echten usage.prompt_tokens jeder Antwort nach: Schaetzfehler 200 % -> 0,3 %. Soft-Einschub nur noch, wenn die Nachrichtenkette es erlaubt (kein Dazwischen- funken in offene tool_calls). Neuer Status-Endpunkt + systemd-Unit. Lucys Alltagsmodelle sind vom Schnitt ausgenommen. Stufe 3 — OpenCode-Plugin (deploy/opencode/plugin/mc2-governor.ts): Werkzeug-Zaun (git push, rm -rf, sudo, curl|sh — bewiesen), Pruef-Tor auf session.idle mit Selbstreparatur, Savepoint statt Kompression, Meldungen an Lucys Briefkasten mit eigenem Absender 'loop'. Stufe 4 — Mannschaft (opencode.json): plan+build -> coder (51,5 t/s) · explore -> hermes (69,6 t/s, warm, gratis) · review -> Devstral (15,0 t/s, FREMDE Modellfamilie gegen blinde Flecken). Der 63-GB-Planer faellt aus der Tagesrolle raus. Stufe 5 — ein Regelwerk, zwei Ausloeser: deploy/opencode-lauf.sh faehrt dieselbe Bau-Pruef-Schleife unbeaufsichtigt (die Schleife liegt hier UND im Plugin: bei `opencode run` endet der Prozess, bevor session.idle fertig ist — gemessen). Bricht ab, wenn der Governor fehlt. gitea-repo-create.sh saet jetzt VERIFY neben der CI-Ampel: jedes neue Repo wird mit Innen- UND Aussen-Pruefung geboren. Oberflaeche: Token-Waechter-Kachel im Cockpit (Fuellstand, Marken, Ehrlichkeits-Nachweis), /api/governor als gleichursprüngliches Fenster, Devstral im Modellkatalog, Rollen-Texte auf die neue Mannschaft aktualisiert. .gitattributes: deploy/**/*.py auf LF (deploy/*.py greift nur eine Ebene tief). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
7.3 KiB
Bash
134 lines
7.3 KiB
Bash
#!/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 <name> ["Beschreibung"]
|
|
# Ergebnis (stdout, letzte Zeile): CLONE <https-clone-url> → 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.}"
|
|
# Worker-Subprozesse laufen evtl. mit abweichendem $HOME (Profil-Home) — das dedizierte
|
|
# Anlage-Token (write:user) UND .git-credentials liegen aber nur im ECHTEN User-Home.
|
|
# Darum den echten Home aus /etc/passwd aufloesen, sonst faellt das Skript aufs falsche
|
|
# Token zurueck (Fan-out-Nachlese 20.07.: projektstart-Worker blockte an genau dieser
|
|
# Stelle mit "Token hat nur write:repository").
|
|
REALHOME="$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f6)"; REALHOME="${REALHOME:-$HOME}"
|
|
CRED="$REALHOME/.git-credentials"
|
|
# Dediziertes Anlage-Token (Scope write:user) — GETRENNT vom Push-Token in
|
|
# ~/.git-credentials, damit ein Überschreiben nie Push/PR bricht. Priorität:
|
|
# ENV GITEA_CREATE_TOKEN → <echter-Home>/.config/gitea/create-token → Fallback .git-credentials.
|
|
CREATE_TOKEN_FILE="$REALHOME/.config/gitea/create-token"
|
|
LOG="$REALHOME/.hermes/state/gitea-repos-created.log"
|
|
|
|
if [ -z "$NAME" ]; then
|
|
echo "Nutzung: gitea-repo-create.sh <name> [\"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
|
|
# User + Host sind NICHT geheim: aus .git-credentials ableiten, sonst Standardwerte.
|
|
LINE="$(grep -m1 'git\.tobisniceshomelab\.ddnsfree\.com' "$CRED" 2>/dev/null || head -1 "$CRED" 2>/dev/null || true)"
|
|
USER="$(printf '%s' "$LINE" | sed -nE 's#https://([^:]+):.*#\1#p')"; USER="${USER:-Hitonabi}"
|
|
HOST="$(printf '%s' "$LINE" | sed -nE 's#https://[^@]+@([^/]+).*#\1#p')"; HOST="${HOST:-git.tobisniceshomelab.ddnsfree.com}"
|
|
BASE="https://${HOST}"
|
|
|
|
# Anlage-Token bestimmen (dediziert bevorzugt, damit Push-Token unberührt bleibt)
|
|
if [ -n "${GITEA_CREATE_TOKEN:-}" ]; then
|
|
TOKEN="$GITEA_CREATE_TOKEN"
|
|
elif [ -r "$CREATE_TOKEN_FILE" ]; then
|
|
TOKEN="$(tr -d '[:space:]' < "$CREATE_TOKEN_FILE")"
|
|
else
|
|
TOKEN="$(printf '%s' "$LINE" | sed -nE 's#https://[^:]+:([^@]+)@.*#\1#p')"
|
|
fi
|
|
if [ -z "$TOKEN" ]; then
|
|
echo "Kein Gitea-Token gefunden (weder \$GITEA_CREATE_TOKEN noch $CREATE_TOKEN_FILE noch ~/.git-credentials)." >&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
|
|
# CI-Ampel einpflanzen (Wasserdicht-Runde 22.07.): JEDES neue Repo wird mit der
|
|
# universellen Actions-Ampel geboren (deploy/ampel-ci.yml). Defensiv: fehlt die
|
|
# Vorlage oder scheitert der Seed, wird die Anlage NICHT abgebrochen, nur gewarnt.
|
|
# WICHTIG: Contents-API braucht write:repository → PUSH-Token nutzen (das
|
|
# dedizierte Anlage-Token hat u.U. nur write:user).
|
|
PUSHTOKEN="$(printf '%s' "$LINE" | sed -nE 's#https://[^:]+:([^@]+)@.*#\1#p')"; PUSHTOKEN="${PUSHTOKEN:-$TOKEN}"
|
|
# saat <lokale-vorlage> <pfad-im-repo> <commit-nachricht> <klartext-name>
|
|
saat () {
|
|
local QUELLE="$1" ZIEL="$2" MSG="$3" NAME="$4" CODE
|
|
if [ ! -r "$QUELLE" ]; then
|
|
echo "WARNUNG: Vorlage fehlt ($QUELLE) — Repo ohne $NAME angelegt." >&2
|
|
return
|
|
fi
|
|
CODE="$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/api/v1/repos/$FULL/contents/$ZIEL" \
|
|
-H "Authorization: token $PUSHTOKEN" -H "Content-Type: application/json" \
|
|
--data "$(python3 - "$QUELLE" "$MSG" <<'PY'
|
|
import base64, json, sys
|
|
inhalt = open(sys.argv[1], "rb").read()
|
|
print(json.dumps({"content": base64.b64encode(inhalt).decode(), "message": sys.argv[2]}))
|
|
PY
|
|
)")"
|
|
if [ "$CODE" = "201" ]; then
|
|
echo "$NAME eingepflanzt ($ZIEL)."
|
|
else
|
|
echo "WARNUNG: $NAME-Seed antwortete HTTP $CODE (Repo ist trotzdem da)." >&2
|
|
fi
|
|
}
|
|
# JEDES neue Repo wird mit beiden Wächtern geboren:
|
|
# ci.yml = die AUSSEN-Prüfung (Gitea Actions nach dem Push, Wasserdicht-Runde 22.07.)
|
|
# VERIFY = die INNEN-Prüfung (das OpenCode-Plugin führt sie nach jeder Etappe aus und
|
|
# gibt rote Tests dem Agenten sofort zurück, statt sie erst der CI zu zeigen)
|
|
# Defensiv: scheitert ein Seed, wird die Anlage NICHT abgebrochen, nur gewarnt.
|
|
saat "$REALHOME/mission-control-v2/deploy/ampel-ci.yml" ".gitea/workflows/ci.yml" \
|
|
"CI-Ampel (automatisch bei Repo-Anlage eingepflanzt)" "CI-Ampel"
|
|
saat "$REALHOME/mission-control-v2/deploy/opencode/VERIFY.template" "VERIFY" \
|
|
"Pruef-Tor (automatisch bei Repo-Anlage eingepflanzt)" "Pruef-Tor"
|
|
echo "Repo '$FULL' angelegt (privat, main initialisiert)."
|
|
echo "CLONE ${CLONE}"
|
|
exit 0 ;;
|
|
403)
|
|
if grep -q 'write:user' "$RESP"; then
|
|
echo "FEHLT: Das benutzte Gitea-Token darf keine Repos anlegen (Scope 'write:user' fehlt)." >&2
|
|
echo "So wird es freigeschaltet (einmalig, durch den Commander):" >&2
|
|
echo " 1) In Gitea ein Token mit Scope 'write:user' erzeugen (nur DAS reicht fürs Anlegen)." >&2
|
|
echo " 2) Den Token-Wert in ~/.config/gitea/create-token legen (chmod 600) —" >&2
|
|
echo " getrennt vom Push-Token, das in ~/.git-credentials unberührt bleibt." >&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
|