Lucy-Pipeline (S3): Auftragsbuch liest zwei Repos, Annahme baut am PC
Das Auftragsbuch-Gegenstueck fuer Lucy (Abloesungs-Paket S3, PC-Annahme-Weg): - backend/services/auftragsbuch.py: Multi-Repo (mc2 + lucy via ~/lucy-Clone). Karten tragen repo-Feld; Status-Schluessel fuer Lucy = 'lucy:<branch>'; fehlt ~/lucy, werden Lucy-Karten still weggelassen. Runner je Repo. - deploy/lucy-annahme.sh: detached Annahme-Runner fuer Lucy-Karten. Vorpruefung am PC VOR dem Merge (Executor erreichbar, Arbeitskopie main+clean, laeuft Lucy?), dann Merge im isolierten Worktree von ~/lucy -> Push main -> PC zieht ff-only und baut detached (deploy/lucy-annahme.ps1 im Lucy-Repo), Box pollt .lucy-annahme.json und spiegelt Fortschritt auf die Karte. Rot = Merge automatisch revertiert, laufende Lucy bleibt die alte. Executor-Zugang (URL+Token) kommt aus ~/.hermes/config.yaml — kein zweiter Ablageort. Neustart nur, wenn Lucy vorher lief (User-Entscheid). - Router/UI: repo-Parameter (rueckwaertskompatibel, Default mc2), rosa Lucy-Badge, eigener Annahme-Confirm-Text, Diff/Ablehnen je Repo. - werkstatt-SOUL: Lucy-Auftraege ebenfalls propose-only, bauen macht der PC bei der Annahme. - Doku: docs/AUFTRAGSBUCH.md (Lucy-Annahme-Kapitel), docs/wissen/OFFENE-FAEDEN (S2 erledigt, S3-Stand). Geprueft: py_compile gruen, bash -n gruen, tsc+vite build gruen (dist dabei).
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env bash
|
||||
# Auftragsbuch: einen LUCY-Vorschlags-Branch ANNEHMEN — das PC-Gegenstück zu auftrag-annehmen.sh.
|
||||
# Lucy (Electron) lebt auf dem Windows-PC (F:\Coding Stuff\lucy); dist liegt NICHT im Repo.
|
||||
# Annehmen heißt darum: Merge auf main (hier, Box) → der PC zieht main, baut dist neu und
|
||||
# startet Lucy neu, WENN sie lief (User-Entscheid: Lucy startet nur, wenn der Commander sie will).
|
||||
# Schlägt am PC irgendetwas fehl, wird der Merge auf main automatisch revertiert — die laufende
|
||||
# Lucy bleibt die alte (der PC-Runner stellt dist aus seinem Backup wieder her).
|
||||
#
|
||||
# Der PC wird über den PC-Executor (client/hermes-pc/executor.py, :7777, Bearer-Token aus
|
||||
# ~/.hermes/config.yaml) gesteuert. /shell hat 60 s Timeout → der Build läuft DETACHED
|
||||
# (deploy/lucy-annahme.ps1 im Lucy-Repo schreibt eine Status-Datei, wir pollen sie).
|
||||
#
|
||||
# Läuft wie der MC2-Runner als EIGENE systemd-Unit und als /tmp-Kopie (Selbst-Reset-Falle).
|
||||
# Status-Schlüssel in der Auftragsbuch-JSON: "lucy:<branch>".
|
||||
#
|
||||
# Nutzung: lucy-annahme.sh <branch> (z. B. wartung/turncheck-default-aus)
|
||||
set -uo pipefail
|
||||
|
||||
BRANCH="${1:?Nutzung: lucy-annahme.sh <branch>}"
|
||||
SRC="${MC2_SRC:-$HOME/mission-control-v2}"
|
||||
LUCY="${MC2_LUCY_DIR:-$HOME/lucy}"
|
||||
STATUS="${MC2_AUFTRAG_STATUS:-/srv/models/mc2-auftragsbuch.json}"
|
||||
HERMES_CONF="${HERMES_CONF:-$HOME/.hermes/config.yaml}"
|
||||
SLUG="lucy-$(echo "$BRANCH" | tr '/' '-')"
|
||||
WT="/tmp/annahme-$SLUG"
|
||||
LOG="/tmp/annahme-$SLUG.log"
|
||||
|
||||
# Windows-Seite (Pfade im PC-Dateisystem; einfach gehalten — EIN PC, EIN Checkout).
|
||||
PC_REPO='F:\Coding Stuff\lucy'
|
||||
PC_STATUSFILE='F:\Coding Stuff\lucy\.lucy-annahme.json'
|
||||
PC_RUNNER='F:\Coding Stuff\lucy\deploy\lucy-annahme.ps1'
|
||||
BUILD_TIMEOUT_S=1500 # 25 min Deckel für npm ci + tsc + electron-builder + Stimm-Warmup
|
||||
|
||||
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" "lucy:$BRANCH" "$1" "$2" <<'PY'
|
||||
import json, os, sys, time
|
||||
p, key, state, detail = sys.argv[1:5]
|
||||
try:
|
||||
d = json.load(open(p, encoding="utf-8"))
|
||||
except Exception:
|
||||
d = {}
|
||||
d.setdefault("branches", {})[key] = {"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
|
||||
}
|
||||
|
||||
# PC-Executor: PowerShell-Kommando ausführen. stdout des PCs → stdout; rc 0 nur wenn
|
||||
# Transport UND Kommando grün. Token/URL kommen aus der Hermes-Config (kein zweiter Ablageort).
|
||||
PC_URL="$(sed -n 's/^[[:space:]]*PC_EXECUTOR_URL:[[:space:]]*//p' "$HERMES_CONF" | head -1 | tr -d "\"'" | tr -d '[:space:]')"
|
||||
PC_TOKEN="$(sed -n 's/^[[:space:]]*PC_EXECUTOR_TOKEN:[[:space:]]*//p' "$HERMES_CONF" | head -1 | tr -d "\"'" | tr -d '[:space:]')"
|
||||
pc_shell(){ # $1 = PowerShell-Kommando
|
||||
python3 - "$PC_URL" "$PC_TOKEN" "$1" <<'PY'
|
||||
import json, sys, urllib.request
|
||||
url, token, cmd = sys.argv[1:4]
|
||||
req = urllib.request.Request(url + "/shell", data=json.dumps({"command": cmd}).encode(),
|
||||
headers={"Authorization": "Bearer " + token,
|
||||
"Content-Type": "application/json"})
|
||||
try:
|
||||
r = json.load(urllib.request.urlopen(req, timeout=58))
|
||||
except Exception as e:
|
||||
print(f"(PC-Executor nicht erreichbar: {e})")
|
||||
sys.exit(2)
|
||||
out = (r.get("stdout") or "").strip()
|
||||
if out:
|
||||
print(out)
|
||||
err = (r.get("stderr") or "").strip()
|
||||
if err:
|
||||
print(err, file=sys.stderr)
|
||||
sys.exit(0 if r.get("returncode") == 0 else 1)
|
||||
PY
|
||||
}
|
||||
|
||||
cleanup_wt(){ git -C "$LUCY" worktree remove --force "$WT" 2>/dev/null || true; }
|
||||
|
||||
fail(){
|
||||
status "fehlgeschlagen" "$1"
|
||||
notify "Lucy-Vorschlag '$BRANCH' konnte NICHT eingespielt werden: $1 — nichts wurde verändert, der Branch bleibt liegen."
|
||||
cleanup_wt
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Revert nach schon gepushtem Merge (PC-Phase rot): main zurückdrehen, PC-Checkout nachziehen.
|
||||
revert_main(){ # $1 = Grund für die Meldung
|
||||
status "rollback" "PC-Phase rot — Merge wird automatisch zurückgedreht"
|
||||
git -C "$LUCY" fetch -q origin || true
|
||||
cleanup_wt
|
||||
if git -C "$LUCY" 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
|
||||
# PC-Checkout best-effort auf den revertierten Stand nachziehen (Quelle = laufende Lucy).
|
||||
pc_shell "git -C '$PC_REPO' fetch origin main 2>&1; git -C '$PC_REPO' merge --ff-only FETCH_HEAD 2>&1" >>"$LOG" 2>&1 || true
|
||||
status "zurueckgerollt" "$1 — Merge automatisch revertiert, Lucy läuft in der alten Version weiter"
|
||||
notify "Lucy-Vorschlag '$BRANCH' hat den PC-Build/Neustart GERISSEN: $1. Merge automatisch zurückgedreht — Lucy läuft in der alten Version weiter. Der Branch bleibt zur Analyse liegen. Log: $LOG"
|
||||
exit 1
|
||||
fi
|
||||
cleanup_wt
|
||||
status "kritisch" "Revert fehlgeschlagen — Lucy-Repo und PC von Hand prüfen"
|
||||
notify "KRITISCH: Lucy-Annahme '$BRANCH' scheiterte UND der automatische Revert schlug fehl — bitte Lucy-Repo (Gitea main) und PC prüfen. Log: $LOG"
|
||||
exit 2
|
||||
}
|
||||
|
||||
[ -n "$PC_URL" ] && [ -n "$PC_TOKEN" ] || fail "PC-Executor-Zugang fehlt in $HERMES_CONF (PC_EXECUTOR_URL/TOKEN)"
|
||||
|
||||
# ── Vorprüfungen am PC (VOR dem Merge — solange ist nichts kaputtzumachen) ──
|
||||
status "laeuft" "Vorprüfung: ist der PC bereit?"
|
||||
curl -sf -m 8 "$PC_URL/health" >/dev/null 2>&1 \
|
||||
|| fail "PC-Executor nicht erreichbar — Lucy-Annahme braucht den eingeschalteten PC (Executor-Task läuft bei Anmeldung)"
|
||||
|
||||
PC_BRANCH="$(pc_shell "git -C '$PC_REPO' rev-parse --abbrev-ref HEAD" 2>>"$LOG")" \
|
||||
|| fail "PC-Arbeitskopie nicht lesbar (git am PC): $PC_BRANCH"
|
||||
[ "$PC_BRANCH" = "main" ] || fail "PC-Arbeitskopie steht auf '$PC_BRANCH' statt main — bitte am PC aufräumen"
|
||||
|
||||
PC_DIRTY="$(pc_shell "git -C '$PC_REPO' status --porcelain -uno" 2>>"$LOG")" \
|
||||
|| fail "PC-Arbeitskopie nicht prüfbar"
|
||||
[ -z "$PC_DIRTY" ] || fail "PC-Arbeitskopie hat ungespeicherte Änderungen — bitte erst am PC committen/aufräumen: $(echo "$PC_DIRTY" | head -3 | tr '\n' ' ')"
|
||||
|
||||
WAS_RUNNING="$(pc_shell "if (Get-Process Lucy -ErrorAction SilentlyContinue) { '1' } else { '0' }" 2>>"$LOG")" || WAS_RUNNING="0"
|
||||
case "$WAS_RUNNING" in 1|0) ;; *) WAS_RUNNING="0" ;; esac
|
||||
|
||||
# ── Merge auf main (Box, isolierter Worktree — wie beim MC2-Runner) ─────────
|
||||
status "laeuft" "Merge wird vorbereitet"
|
||||
git -C "$LUCY" fetch -q origin || fail "git fetch (Gitea) fehlgeschlagen"
|
||||
git -C "$LUCY" rev-parse --verify -q "refs/remotes/origin/$BRANCH" >/dev/null \
|
||||
|| fail "Branch origin/$BRANCH existiert nicht (schon gemergt/gelöscht?)"
|
||||
|
||||
cleanup_wt
|
||||
git -C "$LUCY" 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: Lucy-Vorschlag '$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
|
||||
|
||||
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
|
||||
|
||||
# ── PC: main ziehen, Build detached starten ─────────────────────────────────
|
||||
status "laeuft" "PC zieht den neuen Stand"
|
||||
pc_shell "git -C '$PC_REPO' fetch origin main 2>&1; if (\$LASTEXITCODE -ne 0) { exit 1 }; git -C '$PC_REPO' merge --ff-only FETCH_HEAD 2>&1" >>"$LOG" 2>&1 \
|
||||
|| revert_main "PC konnte main nicht ziehen (git fetch/merge am PC rot)"
|
||||
|
||||
pc_shell "if (Test-Path '$PC_RUNNER') { exit 0 } else { exit 1 }" >>"$LOG" 2>&1 \
|
||||
|| revert_main "PC-Runner-Skript fehlt im Lucy-Repo (deploy/lucy-annahme.ps1)"
|
||||
|
||||
pc_shell "Remove-Item -Force '$PC_STATUSFILE' -ErrorAction SilentlyContinue; Start-Process powershell -WindowStyle Hidden -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','$PC_RUNNER','-WasRunning','$WAS_RUNNING'" >>"$LOG" 2>&1 \
|
||||
|| revert_main "PC-Build ließ sich nicht starten"
|
||||
|
||||
# ── Status-Datei pollen, Fortschritt in die UI spiegeln ─────────────────────
|
||||
status "laeuft" "PC baut Lucy (dist) — das dauert ein paar Minuten"
|
||||
DEADLINE=$(( $(date +%s) + BUILD_TIMEOUT_S ))
|
||||
LAST_DETAIL=""
|
||||
FINAL=""
|
||||
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
|
||||
sleep 10
|
||||
RAW="$(pc_shell "if (Test-Path '$PC_STATUSFILE') { Get-Content -Raw '$PC_STATUSFILE' }" 2>>"$LOG")" || continue
|
||||
[ -n "$RAW" ] || continue
|
||||
PARSED="$(printf '%s' "$RAW" | python3 -c '
|
||||
import json, sys
|
||||
try:
|
||||
d = json.loads(sys.stdin.read())
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
detail = str(d.get("detail", "")).replace("|", "/").replace("\n", " ")
|
||||
print(f"{1 if d.get(\"done\") else 0}|{1 if d.get(\"ok\") else 0}|{detail}")
|
||||
')"
|
||||
[ -n "$PARSED" ] || continue
|
||||
DONE="${PARSED%%|*}"; REST="${PARSED#*|}"; OK="${REST%%|*}"; DETAIL="${REST#*|}"
|
||||
if [ "$DETAIL" != "$LAST_DETAIL" ] && [ -n "$DETAIL" ]; then
|
||||
LAST_DETAIL="$DETAIL"
|
||||
status "laeuft" "PC: $DETAIL"
|
||||
fi
|
||||
if [ "$DONE" = "1" ]; then FINAL="$OK"; break; fi
|
||||
done
|
||||
|
||||
[ -n "$FINAL" ] || revert_main "PC-Build-Timeout nach $((BUILD_TIMEOUT_S/60)) Minuten (Status-Datei blieb offen)"
|
||||
[ "$FINAL" = "1" ] || revert_main "${LAST_DETAIL:-PC meldet Fehler ohne Detail}"
|
||||
|
||||
# ── Grün: Branch aufräumen, ehrlich melden ──────────────────────────────────
|
||||
git -C "$LUCY" push origin --delete "$BRANCH" >>"$LOG" 2>&1 || true
|
||||
git -C "$LUCY" fetch -q --prune origin 2>/dev/null || true
|
||||
status "eingespielt" "${LAST_DETAIL:-Build grün}"
|
||||
if [ "$WAS_RUNNING" = "1" ]; then
|
||||
notify "Lucy-Vorschlag '$BRANCH' ist LIVE — dist am PC neu gebaut, Lucy neu gestartet, Stimme meldet sich. ✅"
|
||||
else
|
||||
notify "Lucy-Vorschlag '$BRANCH' ist eingespielt — dist am PC neu gebaut. Lucy war aus und bleibt aus; die neue Version startet beim nächsten Öffnen. ✅"
|
||||
fi
|
||||
exit 0
|
||||
Reference in New Issue
Block a user