4dc5ca7343
Der letzte Meter des propose-only-Kreislaufs: Vorschlags-Branches (Werkstatt/ Orchestrator) und Traum-Skill-Kandidaten werden als API sichtbar; Annehmen laeuft als detached systemd-Unit (Merge im Worktree -> Push main -> Deploy -> Health -> Auto-Revert bei Rot). Dazu: Chronik-Endpoint (Announce-Store als Timeline), Wissens-Vault-Reader (read-only, Traversal-Guard), Zeitmaschine (Snapshots + detached Restore) und Morgenlage-Spiegel im Chef-Gutachter-Feed (priority=silent). python-multipart explizit in requirements (voice braucht es, war implizit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
140 lines
6.1 KiB
Bash
140 lines
6.1 KiB
Bash
#!/usr/bin/env bash
|
|
# Auftragsbuch: einen Vorschlags-Branch ANNEHMEN — der letzte Meter des propose-only-Kreislaufs.
|
|
# Werkstatt/Orchestrator liefern Branches (wartung/*, orchestrator/*), der Commander klickt in
|
|
# der Zentrale „Annehmen" → dieses Skript macht die bisherige Git-Handarbeit: Merge im
|
|
# isolierten Worktree (Lehre: NIE im Live-Checkout arbeiten) → Push nach main → Deploy →
|
|
# Health-Check → bei Rot automatischer Revert + Redeploy + Alarm.
|
|
#
|
|
# WICHTIG: läuft als EIGENE systemd-Unit (systemd-run, startet routers/auftragsbuch.py),
|
|
# NICHT als Kind des Backends — deploy.sh startet mission-control-2 neu und würde sonst
|
|
# den eigenen Eltern-Prozess mitten im Lauf töten. Außerdem wird es vom Backend als
|
|
# /tmp-KOPIE gestartet (deploy.sh macht git reset --hard → die Datei unter den Füßen
|
|
# eines laufenden bash zu tauschen korrumpiert das Skript, bekannte Falle).
|
|
#
|
|
# Nutzung: auftrag-annehmen.sh <branch> (z. B. wartung/saubere-zusammenfassung)
|
|
set -uo pipefail
|
|
|
|
BRANCH="${1:?Nutzung: auftrag-annehmen.sh <branch>}"
|
|
SRC="${MC2_SRC:-$HOME/mission-control-v2}"
|
|
STATUS="${MC2_AUFTRAG_STATUS:-/srv/models/mc2-auftragsbuch.json}"
|
|
API="${MC_API:-http://127.0.0.1:9001}"
|
|
SLUG="$(echo "$BRANCH" | tr '/' '-')"
|
|
WT="/tmp/annahme-$SLUG"
|
|
LOG="/tmp/annahme-$SLUG.log"
|
|
|
|
notify(){ bash "$SRC/deploy/notify.sh" -s "[Auftragsbuch]" "$1" || true; }
|
|
|
|
# Status-Fortschritt für die UI (atomar via tmp+replace; die API liest die Datei nur).
|
|
status(){ # $1=state $2=detail
|
|
python3 - "$STATUS" "$BRANCH" "$1" "$2" <<'PY'
|
|
import json, os, sys, time
|
|
p, branch, state, detail = sys.argv[1:5]
|
|
try:
|
|
d = json.load(open(p, encoding="utf-8"))
|
|
except Exception:
|
|
d = {}
|
|
d.setdefault("branches", {})[branch] = {"state": state, "detail": detail, "ts": time.time()}
|
|
tmp = p + ".tmp"
|
|
json.dump(d, open(tmp, "w", encoding="utf-8"), ensure_ascii=False)
|
|
os.replace(tmp, p)
|
|
PY
|
|
}
|
|
|
|
cleanup_wt(){ git -C "$SRC" worktree remove --force "$WT" 2>/dev/null || true; }
|
|
|
|
fail(){
|
|
status "fehlgeschlagen" "$1"
|
|
notify "Vorschlag '$BRANCH' konnte NICHT eingespielt werden: $1 — nichts wurde verändert, der Branch bleibt liegen."
|
|
cleanup_wt
|
|
exit 1
|
|
}
|
|
|
|
status "laeuft" "Merge wird vorbereitet"
|
|
cd "$SRC" || fail "Live-Checkout $SRC fehlt"
|
|
git fetch -q origin || fail "git fetch (Gitea) fehlgeschlagen"
|
|
git rev-parse --verify -q "refs/remotes/origin/$BRANCH" >/dev/null \
|
|
|| fail "Branch origin/$BRANCH existiert nicht (schon gemergt/gelöscht?)"
|
|
|
|
PRE="$(git rev-parse origin/main)"
|
|
|
|
# Isolierter Worktree — der Live-Checkout bleibt bis zum Deploy unberührt.
|
|
cleanup_wt
|
|
git worktree add --detach "$WT" origin/main >/dev/null 2>&1 || fail "Worktree konnte nicht angelegt werden"
|
|
if ! git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
|
|
merge --no-ff "origin/$BRANCH" -m "Auftragsbuch: '$BRANCH' angenommen (Ein-Klick-Gate)" >>"$LOG" 2>&1; then
|
|
git -C "$WT" merge --abort 2>/dev/null || true
|
|
fail "Merge-Konflikt mit main — der Branch ist veraltet, bitte am PC auflösen"
|
|
fi
|
|
|
|
# py_compile-Gate über die durch den Merge geänderten Python-Dateien (AGENTS.md-Regel).
|
|
PYS="$(git -C "$WT" diff --name-only "$PRE"..HEAD -- '*.py' 2>/dev/null | tr '\n' ' ')"
|
|
if [ -n "${PYS// /}" ]; then
|
|
PYBIN="$SRC/backend/.venv/bin/python"; [ -x "$PYBIN" ] || PYBIN="python3"
|
|
# shellcheck disable=SC2086
|
|
( cd "$WT" && "$PYBIN" -m py_compile $PYS ) >>"$LOG" 2>&1 || fail "py_compile-Gate rot — der Patch enthält kaputtes Python"
|
|
fi
|
|
|
|
status "laeuft" "Push nach main läuft"
|
|
PUSHED=0
|
|
for _ in 1 2 3; do
|
|
git -C "$WT" push origin HEAD:main >>"$LOG" 2>&1 && { PUSHED=1; break; }
|
|
sleep 5
|
|
done
|
|
[ "$PUSHED" = 1 ] || fail "Push nach main fehlgeschlagen (Gitea nicht erreichbar / Token?)"
|
|
cleanup_wt
|
|
|
|
status "laeuft" "Deploy läuft — die Zentrale startet gleich kurz neu"
|
|
notify "Vorschlag '$BRANCH' angenommen: Merge auf main ist durch, Deploy läuft. Die Zentrale ist gleich kurz weg."
|
|
|
|
# deploy.sh als Kopie ausführen (Selbst-Reset-Falle: es resettet das Repo, in dem es liegt).
|
|
cp "$SRC/deploy/deploy.sh" "/tmp/annahme-deploy-$SLUG.sh"
|
|
DEPLOY_OK=1
|
|
bash "/tmp/annahme-deploy-$SLUG.sh" >>"$LOG" 2>&1 || DEPLOY_OK=0
|
|
|
|
# Health-Check mit Geduld (Dienst-Neustart + Warmup brauchen einen Moment).
|
|
HEALTH=0
|
|
if [ "$DEPLOY_OK" = 1 ]; then
|
|
for _ in $(seq 1 18); do
|
|
curl -sf -m 5 "$API/api/health" >/dev/null 2>&1 && { HEALTH=1; break; }
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
if [ "$HEALTH" = 1 ]; then
|
|
# Gemergt + live + grün → der Remote-Branch hat seinen Zweck erfüllt.
|
|
git -C "$SRC" push origin --delete "$BRANCH" >>"$LOG" 2>&1 || true
|
|
git -C "$SRC" fetch -q --prune origin 2>/dev/null || true
|
|
status "eingespielt" "Deploy grün, Health-Check bestanden"
|
|
notify "Vorschlag '$BRANCH' ist LIVE — Deploy grün, Health-Check bestanden. ✅"
|
|
exit 0
|
|
fi
|
|
|
|
# ── Rollback: Merge revertieren, main zurückschieben, neu deployen, Alarm ──
|
|
status "rollback" "Health rot — automatischer Revert läuft"
|
|
git fetch -q origin || true
|
|
cleanup_wt
|
|
if git worktree add --detach "$WT" origin/main >/dev/null 2>&1 \
|
|
&& git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
|
|
revert -m 1 --no-edit HEAD >>"$LOG" 2>&1 \
|
|
&& git -C "$WT" push origin HEAD:main >>"$LOG" 2>&1; then
|
|
cleanup_wt
|
|
cp "$SRC/deploy/deploy.sh" "/tmp/annahme-rollback-$SLUG.sh"
|
|
bash "/tmp/annahme-rollback-$SLUG.sh" >>"$LOG" 2>&1 || true
|
|
R=0
|
|
for _ in $(seq 1 12); do
|
|
curl -sf -m 5 "$API/api/health" >/dev/null 2>&1 && { R=1; break; }
|
|
sleep 5
|
|
done
|
|
if [ "$R" = 1 ]; then
|
|
status "zurueckgerollt" "Health blieb rot — Merge automatisch revertiert, Box läuft wieder"
|
|
notify "Vorschlag '$BRANCH' hat den Health-Check GERISSEN. Automatisch zurückgerollt — die Box läuft wieder auf dem alten Stand. Der Branch bleibt zur Analyse liegen. Log: $LOG"
|
|
exit 1
|
|
fi
|
|
fi
|
|
cleanup_wt
|
|
status "kritisch" "Rollback fehlgeschlagen — Box braucht Hilfe"
|
|
curl -sf -m 5 -X POST "$API/api/alarm" -H 'Content-Type: application/json' \
|
|
--data "{\"subject\":\"[Auftragsbuch]\",\"text\":\"KRITISCH: Annehmen von $BRANCH UND Rollback fehlgeschlagen — Zentrale prüfen. Log: $LOG\"}" >/dev/null 2>&1 \
|
|
|| notify "KRITISCH: Annehmen von '$BRANCH' UND Rollback fehlgeschlagen — bitte Box prüfen (restore.sh liegt bereit). Log: $LOG"
|
|
exit 2
|