#!/bin/bash # deploy.sh — den Box-Wart auf der Box auf den Stand von origin/main bringen. # # Zweistufig (seit 24.09.2026): # Stufe 1: Sperre gegen zwei gleichzeitige Deploys, laufende Update-Jobs abwarten lassen, # alten Stand merken, main holen (nur fast-forward) — dann die NEUE Fassung dieses # Skripts als Stufe 2 starten. Änderungen an deploy.sh wirken damit sofort, nicht # erst beim nächsten Lauf (die alte Falle: bash lief nach dem Pull mit der alten Datei). # Stufe 2: Prüftor (deploy/pruefen.sh), Abhängigkeiten, llama-swap-Config nur, wenn sich der # Abzug in DIESEM Deploy geändert hat, Units/Drop-ins/Cron-Skripte/Skills/Plugins # ausspielen, Dienste neu starten, Nachprüfung. Scheitert irgendein Schritt: zurück auf # den alten Stand, Dienste neu starten, dringende Meldung. # # Aufruf auf der Box: bash ~/mission-control-v2/deploy/deploy.sh # Schalter: MC_DEPLOY_TROTZDEM=1 (auch bei laufenden Jobs), MC_DEPLOY_OHNE_TESTS=1 (Notfall), # MC_DEPLOY_SKIP_SWAP_CONFIG=1 (llama-swap-Config nie anfassen). set -euo pipefail cd "$HOME/mission-control-v2" export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" API="http://127.0.0.1:9001" LSWAP_LIVE="/etc/llama-swap/config.yaml" DEPLOY_LOG="/srv/models/mc2-deploy.log" # Units, die ganz dem Repo gehören (deploy.sh spielt sie aus). Aktiviert wird nur, was laufen soll: # Konsole und Spracherkennung schlafen seit 24.09.2026 (disable --now), ihre Units bleiben aber aktuell. UNITS="mission-control-2.service mc2-gateway.service mc2-steward.service lucy-stimme.service box-console.service voice-service.service mc2-radar.service mc2-radar.timer mc2-backup.service mc2-backup.timer mc2-morgenmeldung.service mc2-morgenmeldung.timer mc2-autoupdate.service mc2-autoupdate.timer projekte-sync.service projekte-sync.timer" AKTIV="mission-control-2.service mc2-gateway.service mc2-steward.service lucy-stimme.service mc2-radar.timer mc2-backup.timer mc2-morgenmeldung.timer mc2-autoupdate.timer projekte-sync.timer" # Skills, die abgelöst sind: aus Lucys Skill-Index nehmen (verschoben, nicht gelöscht). Dazu die # alten Bindestrich-Doppel der übrigen Skills (deploy.sh schreibt die Unterstrich-Fassung). SKILLS_ALT="autonomie konzept-fliessband llm-wiki morning-report orchestrator projekt-start pruefstand review trend-radar wartung konzept_fliessband llm_wiki morning_report projekt_start trend_radar betrieb-playbook pc-pfad-cache" laufende_jobs() { curl -sf -m 5 "$API/api/jobs" 2>/dev/null \ | python3 -c 'import json,sys; print(sum(1 for j in json.load(sys.stdin).get("jobs", []) if j.get("state") in ("running", "queued")))' \ 2>/dev/null || echo 0 } stufe1() { exec 9>"${XDG_RUNTIME_DIR}/mc2-deploy.lock" flock -n 9 || { echo "Es läuft schon ein Deploy."; exit 1; } # Update- und Download-Jobs leben (noch) im MC2-Prozess: ein Neustart würde sie abwürgen. local n; n="$(laufende_jobs)" if [ "${n:-0}" -gt 0 ] && [ "${MC_DEPLOY_TROTZDEM:-0}" != "1" ]; then echo "Es laufen $n Job(s) (Update oder Download). Deploy verschoben — später erneut, oder MC_DEPLOY_TROTZDEM=1." exit 1 fi local vorher; vorher="$(git rev-parse HEAD)" echo "Starte Deploy (vorher ${vorher:0:7})…" git fetch -q origin main git merge --ff-only -q origin/main # Ab hier die NEUE Fassung dieses Skripts. Die Sperre (fd 9) bleibt über exec erhalten. MC_DEPLOY_STUFE=2 MC_DEPLOY_VORHER="$vorher" exec bash deploy/deploy.sh "$@" } stufe2() { local vorher="$MC_DEPLOY_VORHER" neu swap_bak="" neu="$(git rev-parse --short HEAD)" zurueck() { trap - ERR echo "✗ Deploy von $neu gescheitert — zurück auf ${vorher:0:7}." git reset -q --hard "$vorher" || true if [ -n "$swap_bak" ] && [ -f "$swap_bak" ]; then cp "$swap_bak" "$LSWAP_LIVE" && echo " llama-swap-Config zurückgespielt." fi systemctl --user daemon-reload || true systemctl --user restart mc2-gateway.service mission-control-2.service mc2-steward.service || true echo "$(date -Is) GESCHEITERT $neu, zurück auf ${vorher:0:7}" >> "$DEPLOY_LOG" 2>/dev/null || true bash deploy/notify.sh -d -s "[Alarm] Deploy" \ "Deploy von $neu ist gescheitert, die Box läuft wieder mit ${vorher:0:7}. Details: journalctl oder deploy-Ausgabe." || true exit 1 } trap zurueck ERR if [ "$neu" = "${vorher:0:7}" ]; then echo "Kein neuer Stand auf main — spiele trotzdem alles aus (Units, Skripte, Neustart)." fi # 1. Abhängigkeiten (inkl. pytest fürs Prüftor) echo "Aktualisiere Python-Abhängigkeiten…" backend/.venv/bin/pip install -q -r backend/requirements.txt -r backend/requirements-dev.txt # 2. Prüftor: vor jedem Umschalten. Rot = zurück auf den alten Stand. if [ "${MC_DEPLOY_OHNE_TESTS:-0}" = "1" ]; then echo "Prüftor übersprungen (MC_DEPLOY_OHNE_TESTS=1)." else bash deploy/pruefen.sh fi # 3. llama-swap-Config: /etc/llama-swap/config.yaml ist die LEBENDE Wahrheit, MC2 schreibt sie # selbst (Oberfläche, Radar). Der Repo-Abzug überschreibt sie nur, wenn er sich in DIESEM Deploy # geändert hat — sonst würde jede Abweichung (z. B. ein per Oberfläche eingetragenes Modell) # zurückgedreht. Kein Neustart nötig: llama-swap läuft mit -watch-config. if [ "${MC_DEPLOY_SKIP_SWAP_CONFIG:-0}" != "1" ] && ! git diff --quiet "$vorher" HEAD -- deploy/llama-swap.config.yaml; then swap_bak="$LSWAP_LIVE.bak-$(date +%Y%m%d-%H%M%S)" echo "llama-swap-Abzug geändert — sichere $LSWAP_LIVE nach $swap_bak und spiele ihn ein." diff -u "$LSWAP_LIVE" deploy/llama-swap.config.yaml | head -40 || true cp "$LSWAP_LIVE" "$swap_bak" cp deploy/llama-swap.config.yaml "$LSWAP_LIVE.neu" mv "$LSWAP_LIVE.neu" "$LSWAP_LIVE" # Nur die letzten 5 Sicherungen behalten (ältere root-eigene bleiben notfalls liegen). ls -1t "$LSWAP_LIVE".bak-* 2>/dev/null | tail -n +6 | xargs -r rm -f 2>/dev/null || true elif ! cmp -s deploy/llama-swap.config.yaml "$LSWAP_LIVE"; then echo "Hinweis: Die lebende llama-swap-Config weicht vom Repo-Abzug ab (Änderung über die Oberfläche?) — nicht angefasst." fi # 4. Units und Drop-ins (alle Repo-Units, damit Box und Repo nicht auseinanderlaufen) local ziel="$HOME/.config/systemd/user" u for u in $UNITS; do install -m 644 "deploy/$u" "$ziel/$u" done install -D -m 644 deploy/mc2-steward.service.d-warmset.conf "$ziel/mc2-steward.service.d/warmset.conf" install -D -m 644 deploy/mission-control-2.service.d-override.conf "$ziel/mission-control-2.service.d/override.conf" systemctl --user daemon-reload # shellcheck disable=SC2086 systemctl --user enable $AKTIV >/dev/null 2>&1 # shellcheck disable=SC2086 systemctl --user start mc2-radar.timer mc2-backup.timer mc2-morgenmeldung.timer projekte-sync.timer # Den Update-Timer nur starten, wenn er nicht schon läuft: Mit Persistent=true holt ein frisch # gestarteter Timer einen verpassten Sonntagslauf sofort nach (so am 24.09.2026 passiert). Neu # gestartet wird die Box dabei ohnehin nur sonntags zwischen 04 und 07 Uhr (autoupdate.sh). systemctl --user is-active --quiet mc2-autoupdate.timer || systemctl --user start mc2-autoupdate.timer # 5. Cron-Skripte für Hermes (~/.hermes/scripts ist die Vorgabe von `hermes cron --script`) mkdir -p "$HOME/.hermes/scripts" install -m 755 deploy/jobs/*.sh deploy/jobs/*.py "$HOME/.hermes/scripts/" # 6. Skills und Plugins echo "Synchronisiere Hermes-Skills…" mkdir -p "$HOME/.hermes/skills" local d name alt for d in deploy/skills/*/; do name="$(basename "$d" | tr '-' '_')" mkdir -p "$HOME/.hermes/skills/$name" cp -r "$d". "$HOME/.hermes/skills/$name/" done for alt in $SKILLS_ALT; do if [ -d "$HOME/.hermes/skills/$alt" ]; then mkdir -p "$HOME/.hermes/skills-archiv" rm -rf "$HOME/.hermes/skills-archiv/$alt" mv "$HOME/.hermes/skills/$alt" "$HOME/.hermes/skills-archiv/" fi done # Plugins nur kopieren: aktiviert wird einmalig von Hand (hermes plugins enable …), neuer Code # greift beim nächsten Neustart des Hermes-Gateways — den macht deploy.sh bewusst nicht. mkdir -p "$HOME/.hermes/plugins" for d in deploy/hermes-plugins/*/; do cp -r "${d%/}" "$HOME/.hermes/plugins/" done # 7. Neustart und Nachprüfung echo "Starte Gateway, Box-Wart und Wächter neu…" systemctl --user restart mc2-gateway.service mission-control-2.service mc2-steward.service local i ok=0 for i in $(seq 1 30); do if curl -sf -m 3 "$API/api/health" >/dev/null && curl -sf -m 3 http://127.0.0.1:9010/gw/health >/dev/null \ && systemctl --user is-active --quiet mc2-steward.service; then ok=1; break fi sleep 2 done [ "$ok" = 1 ] || { echo "Nachprüfung: Dienste antworten nicht."; false; } trap - ERR echo "$(date -Is) $neu (vorher ${vorher:0:7})" >> "$DEPLOY_LOG" 2>/dev/null || true echo "✅ Deploy $neu ist live (vorher ${vorher:0:7})." } # Beides in Funktionen und ein exit direkt dahinter: bash liest ein Skript häppchenweise, und # git merge ersetzt diese Datei mitten im Lauf. So läuft nichts aus der neuen Datei unkontrolliert. if [ "${MC_DEPLOY_STUFE:-1}" = "2" ]; then stufe2 "$@"; else stufe1 "$@"; fi; exit $?