552299a761
Anlass: feature/cleanse-skill-index — ein Werkstatt-Worker hatte git init statt
Klonen gemacht (Orphan-Commit ohne gemeinsamen Vorfahren, Inhalt: ~/.hermes-Index-
Dateien statt Repo-Dateien). Solche Karten sahen aus wie normale Vorschlaege,
liefen bei Annahme aber IMMER in 'refusing to merge unrelated histories' — und
die Fehlermeldung ('bitte am PC aufloesen') war eine Sackgasse. Das passiert
oefter; darum drei Schichten dagegen:
1) ERKENNEN (Auftragsbuch): neue Karten-Flags 'verwaist' (kein merge-base mit
main -> rote Markierung 'kein gemeinsamer Ursprung', Annehmen-Knopf fehlt,
API+Runner halten zusaetzlich dicht) und 'leer' (Diff gegen main leer ->
'bringt nichts'-Badge). Empfehlung auf der Karte: ablehnen mit Grund,
Idee frisch in die Queue.
2) HEILEN (beide Runner, mc2 + lucy): kollidiert der Merge, weil main weiter-
gelaufen ist, versucht der Runner automatisch einen Rebase des Branches auf
main (isolierter Worktree; Gates laufen danach normal, Merge-Message sagt
'auto-rebased'). Nur wenn auch der Rebase kollidiert, faellt die Karte durch —
mit ehrlicher Meldung statt 'am PC aufloesen'.
3) VERHINDERN (werkstatt-SOUL): voll klonen (nie git init/--depth), Selbstcheck
'git merge-base HEAD origin/main' + fetch/rebase vor JEDEM Push, nie
~/.hermes-Artefakte committen.
Doku: AUFTRAGSBUCH.md + FALLEN.md (Erkennungsmuster: Diff 0 Dateien + behind ~
ganze Historie) + OFFENE-FAEDEN. Geprueft: py_compile gruen, bash -n beide
Runner gruen, tsc+vite build gruen (dist dabei); merge-base-Verhalten am echten
kaputten Branch auf der Box verifiziert.
163 lines
7.7 KiB
Bash
163 lines
7.7 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)"
|
|
|
|
# Kaputt aufgesetzte Branches (Worker machte git init/Shallow statt zu klonen) haben KEINEN
|
|
# gemeinsamen Vorfahren — git verweigert den Merge immer. Ehrlich sagen statt „Konflikt".
|
|
git merge-base origin/main "origin/$BRANCH" >/dev/null 2>&1 \
|
|
|| fail "Branch hat keinen gemeinsamen Ursprung mit main (kaputt aufgesetzt, z. B. git init statt Klonen) — bitte ablehnen (mit Grund) und die Idee neu in die Queue geben"
|
|
|
|
# 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"
|
|
MERGE_MSG="Auftragsbuch: '$BRANCH' angenommen (Ein-Klick-Gate)"
|
|
if ! git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
|
|
merge --no-ff "origin/$BRANCH" -m "$MERGE_MSG" >>"$LOG" 2>&1; then
|
|
git -C "$WT" merge --abort 2>/dev/null || true
|
|
# Selbstheilung: veralteten Branch mechanisch auf main rebasen (zweiter Worktree am
|
|
# Branch-Kopf). Klappt das sauber, wird der rebasede Stand gemergt — kein PC nötig.
|
|
status "laeuft" "Merge-Konflikt — Auto-Rebase wird versucht"
|
|
WT2="/tmp/annahme-rebase-$SLUG"
|
|
git worktree remove --force "$WT2" 2>/dev/null || true
|
|
REBASED=""
|
|
if git worktree add --detach "$WT2" "origin/$BRANCH" >/dev/null 2>&1 \
|
|
&& git -C "$WT2" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
|
|
rebase origin/main >>"$LOG" 2>&1; then
|
|
REBASED="$(git -C "$WT2" rev-parse HEAD)"
|
|
else
|
|
git -C "$WT2" rebase --abort 2>/dev/null || true
|
|
fi
|
|
git worktree remove --force "$WT2" 2>/dev/null || true
|
|
[ -n "$REBASED" ] || fail "Merge-Konflikt mit main, Auto-Rebase scheiterte ebenfalls — echter Inhaltskonflikt; am einfachsten ablehnen und die Idee neu in die Queue geben (frischer Branch von aktuellem main)"
|
|
git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
|
|
merge --no-ff "$REBASED" -m "$MERGE_MSG — auto-rebased (Branch war veraltet)" >>"$LOG" 2>&1 \
|
|
|| { git -C "$WT" merge --abort 2>/dev/null || true; fail "Merge nach Auto-Rebase fehlgeschlagen (unerwartet) — Log: $LOG"; }
|
|
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
|