Phase 2 Drei-Welten-Plan: No-Progress-Bremse + Betrieb-Playbook + mem0-Lern-Buendelung
- no-progress-bremse.py: generische, turn-uebergreifende Schleifenbremse (pre/post_tool_call); Muster-Hash statt hartkodierter Fehler-Strings, Selbst-Lernen via neue-signaturen.jsonl. Schliesst die Luecke der nativen guardrails (pro Turn, nur klassifizierte Fehler). - ensure-profile-hooks.py: kann Eintraege in BESTEHENDE Event-Bloecke einfuegen (zweiter pre_tool_call-Key haette den Tabu-Guard lautlos verschluckt) + registriert Bremse. - betrieb-playbook-Skill: SSH-/Deploy-Diagnose-Checklisten (Passphrase-Falle zuerst), Triage-Regel im Steckbrief-Hook; Skill wird in alle Profile gesynct. - mc2-memory: Lern-Extraktion gebuendelt (4 Turns oder 180 s idle = EIN Lauf) statt pro Turn - Extraktion konkurrierte mit Lucys Hirn um die Slots. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ hint=""
|
||||
ctx="$(cat <<EOF
|
||||
[BOX-ORIENTIERUNG — du bist ein Hintergrund-Worker auf der lokalen AI-Box (Linux, Zeit UTC / lokal Europe/Berlin, locale de_DE), NICHT Lucy und kein Dev-Laptop. Du arbeitest EINE Kanban-Aufgabe ab.]
|
||||
Die Modelle / das "Hirn" laufen ausschliesslich ueber den Router llama-swap auf 127.0.0.1:8080 (startet je Modell einen llama-server) bzw. das OpenAI-kompatible Gateway von MC2 auf 127.0.0.1:9001/v1 — es gibt KEINEN anderen Modell-Server, also nicht danach suchen. Weitere Dienste: hermes-gateway :8642, mem0-Gedaechtnis :8765. Der MC2-Live-Checkout ~/mission-control-v2 ist nur zum LESEN da (Schreiben/committen TABU — Code-Arbeit immer im frischen Klon deines Task-Workspaces). Grenzen (was gehoert wohin): MC2 = Steuerpult (verwalten/klicken/zusehen, KEIN Chat-UI), Lucy = Chat & Stimme (eigenes Repo), Hermes-Quelle ist TABU (Agenten-Verhalten nur ueber Config/SOUL/Skill/Hook/MCP aendern). Feste Entscheide/Verbote + volle Grenzen-Landkarte: ~/mission-control-v2/docs/wissen/ (GRENZEN/VERDIKTE/FALLEN/STACK) + ~/wissens-vault/eigenbau-landkarte.md.${hint}
|
||||
TRIAGE (19.07. — Drei-Bahnen-Regel): Ordne deine Karte ZUERST einer Bahn zu und bleib darin: Denken (Konzept/Plan), Bauen (Code schreiben/aendern), Betrieb (SSH/Deploy/Dienste). Fuer Betrieb-Arbeit lade IMMER zuerst den Skill betrieb-playbook (Diagnose-Checklisten; max 3 Anlaeufe pro Hindernis, dann kanban_block mit exaktem Fehlertext — die No-Progress-Bremse erzwingt das mechanisch).
|
||||
PROTOKOLL-PFLICHT (15.07.): Beende deine Aufgabe IMMER mit dem Tool-Aufruf kanban_complete (fertig) oder kanban_block (Frage/Sackgasse). Eine Text-Zusammenfassung OHNE diesen Aufruf zaehlt als Absturz (Protokollverstoss) und blockiert die Karte — genau so ging am 14.07. eine fertige Arbeit verloren.
|
||||
REPO-WEGWEISER mission-control-v2 (NICHT raten/suchen — so liegt es): backend/app.py = Einstieg + Router-Mounting · backend/routers/*.py = REST-Endpunkte · backend/services/*.py = Logik · backend/gateway_app.py = /v1-Gateway-Prozess (:9010) · frontend/src/views/*.tsx = Ansichten · frontend/src/lib/api.ts = Typen+Fetch · frontend/src/lib/queries.ts = TanStack-Query-Hooks (Polling) · frontend/src/nav.ts = Sidebar · deploy/ = Skripte/Units/Hooks/Skills · Projekt-Regeln: AGENTS.md (Wurzel) + docs/wissen/.
|
||||
FRONTEND-BAUEN (seit 15.07. abends): Node LTS liegt sudo-frei unter ~/.local (node/npm im Worker-PATH). Bei Frontend-Aenderungen baust DU das dist MIT: cd frontend && npm ci --no-audit --no-fund && npm run build — und committest frontend/dist im selben Branch (die Box zeigt sonst nach Annahme den alten Stand; Bau-Beweis 15.07.: kompletter Build in ~4 s).
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""No-Progress-Bremse (Phase 2 des Drei-Welten-Plans, 19.07.2026).
|
||||
|
||||
Generische, turn-UEBERGREIFENDE Schleifenbremse — ergaenzt Hermes' native
|
||||
tool_loop_guardrails, die (a) pro Turn zaehlen und (b) nur als Fehler
|
||||
klassifizierte Ergebnisse sehen. Die teuer gelernte Luecke (SSH-Marathon 18.07.,
|
||||
36 min / 75 Schleifen): ein Befehl laeuft "erfolgreich" durch, die AUSGABE sagt
|
||||
aber immer dasselbe ("Permission denied") — der Agent variiert Befehle gegen
|
||||
eine unsichtbare Wand, ueber Turn-Grenzen hinweg, und nichts stoppt ihn.
|
||||
|
||||
Prinzip (KEINE hartkodierten Fehler-Strings — Leitplanke des Commanders:
|
||||
"wenn sich was aendert, muss es das SELBST erkennen"):
|
||||
post_tool_call → Ergebnis normalisieren (Zahlen/Hex-IDs raus) → Muster-Hash.
|
||||
Gleicher Hash beim gleichen Tool in Folge = Zaehler hoch.
|
||||
Unbekannte Muster landen in neue-signaturen.jsonl —
|
||||
Futter fuer Traum/Radar, um daraus Playbooks zu verdichten.
|
||||
pre_tool_call → Zaehler >= 3 fuer dieses Tool: EINMAL mechanisch blocken
|
||||
(Diagnose-Anweisung statt naechster Blindversuch), Zaehler
|
||||
zuruecksetzen. Der Folgeversuch ist frei; laeuft er wieder
|
||||
3x gegen dasselbe Muster, blockt es erneut.
|
||||
|
||||
Gezaehlt wird nur, wo Wiederholung wirklich Stillstand heisst: terminal IMMER
|
||||
(dort verstecken sich die "erfolgreichen" Fehler), andere Tools nur bei
|
||||
status=error. Lese-Tools deckt die native idempotent-Bremse ab.
|
||||
|
||||
Aufruf (hooks in config.yaml): `no-progress-bremse.py pre` bzw. `... post`.
|
||||
State: ~/.hermes/state/no-progress/<session>.json (48 h Selbst-Aufraeumen).
|
||||
Fail-open: jeder eigene Fehler -> {} (die Bremse darf nie selbst zur Wand werden).
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
STATE_DIR = os.path.expanduser("~/.hermes/state/no-progress")
|
||||
SIG_LOG = os.path.join(STATE_DIR, "neue-signaturen.jsonl")
|
||||
SEEN_FILE = os.path.join(STATE_DIR, "seen-sigs.json")
|
||||
THRESHOLD = 3
|
||||
STATE_TTL = 48 * 3600
|
||||
|
||||
BLOCK_REASON = (
|
||||
"NO-PROGRESS-BREMSE: '{tool}' hat {n}x in Folge dasselbe Ergebnismuster geliefert — "
|
||||
"das ist Stillstand, egal wie sehr die Befehle variieren. STOPP. "
|
||||
"1) Benenne das Hindernis in EINEM Satz (was genau meldet das Ergebnis?). "
|
||||
"2) Diagnose statt Variation — bei SSH/Zugriff ZUERST: Schluessel-Passphrase pruefen "
|
||||
"(`ssh-keygen -y -f <key> </dev/null` — stilles 'Permission denied' = verschluesselter Key), "
|
||||
"dann Gegenseite (Auth-Log, Dienststatus, Erreichbarkeit) LESEN statt raten. "
|
||||
"3) Bleibt es unklar: Aufgabe als blocked/Karte mit dem EXAKTEN Ergebnistext an den "
|
||||
"Commander uebergeben. Dein naechster Versuch ist frei — nutze ihn erst NACH der Diagnose."
|
||||
)
|
||||
|
||||
_NUM_RE = re.compile(r"\d+")
|
||||
_HEX_RE = re.compile(r"\b[0-9a-f]{8,}\b")
|
||||
_WS_RE = re.compile(r"\s+")
|
||||
|
||||
|
||||
def _norm_sig(tool: str, text: str) -> str:
|
||||
t = (text or "")[:2000].lower()
|
||||
t = _HEX_RE.sub("#", _NUM_RE.sub("#", t))
|
||||
t = _WS_RE.sub(" ", t).strip()[:400]
|
||||
return hashlib.sha1(f"{tool}|{t}".encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
def _session_key(p: dict) -> str:
|
||||
sid = p.get("session_id") or p.get("parent_session_id") or ""
|
||||
if sid:
|
||||
return re.sub(r"[^A-Za-z0-9_.-]", "_", str(sid))[:80]
|
||||
return "cwd-" + hashlib.sha1(os.getcwd().encode()).hexdigest()[:12]
|
||||
|
||||
|
||||
def _prune() -> None:
|
||||
now = time.time()
|
||||
for f in os.listdir(STATE_DIR):
|
||||
fp = os.path.join(STATE_DIR, f)
|
||||
if f.endswith(".json") and f != os.path.basename(SEEN_FILE):
|
||||
if now - os.path.getmtime(fp) > STATE_TTL:
|
||||
os.remove(fp)
|
||||
|
||||
|
||||
def _load(path: str) -> dict:
|
||||
try:
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
return json.load(fh)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _save(path: str, data: dict) -> None:
|
||||
tmp = path + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as fh:
|
||||
json.dump(data, fh)
|
||||
os.replace(tmp, path)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
mode = sys.argv[1] if len(sys.argv) > 1 else "post"
|
||||
payload = json.load(sys.stdin)
|
||||
tool = str(payload.get("tool_name") or "")
|
||||
if not tool:
|
||||
print("{}")
|
||||
return
|
||||
os.makedirs(STATE_DIR, exist_ok=True)
|
||||
sfile = os.path.join(STATE_DIR, _session_key(payload) + ".json")
|
||||
state = _load(sfile)
|
||||
tools = state.setdefault("tools", {})
|
||||
|
||||
if mode == "post":
|
||||
status = str(payload.get("status") or "ok")
|
||||
# terminal immer beobachten (dort tarnen sich Fehler als Erfolg),
|
||||
# sonst nur echte Fehler — Lese-Tools regelt die native Bremse.
|
||||
if tool != "terminal" and status != "error":
|
||||
print("{}")
|
||||
return
|
||||
sig = _norm_sig(tool, str(payload.get("result") or ""))
|
||||
entry = tools.get(tool) or {}
|
||||
entry["count"] = entry.get("count", 0) + 1 if entry.get("sig") == sig else 1
|
||||
entry["sig"] = sig
|
||||
tools[tool] = entry
|
||||
_save(sfile, state)
|
||||
_prune()
|
||||
seen = _load(SEEN_FILE)
|
||||
sigs = seen.setdefault("sigs", [])
|
||||
if sig not in sigs and status == "error" or (tool == "terminal" and sig not in sigs and entry["count"] >= 2):
|
||||
sigs.append(sig)
|
||||
del sigs[:-500]
|
||||
_save(SEEN_FILE, seen)
|
||||
sample = _WS_RE.sub(" ", str(payload.get("result") or ""))[:200]
|
||||
with open(SIG_LOG, "a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps({"ts": int(time.time()), "tool": tool,
|
||||
"sig": sig, "sample": sample}, ensure_ascii=False) + "\n")
|
||||
print("{}")
|
||||
return
|
||||
|
||||
# mode == "pre"
|
||||
entry = tools.get(tool) or {}
|
||||
if entry.get("count", 0) >= THRESHOLD:
|
||||
entry["count"] = 0
|
||||
tools[tool] = entry
|
||||
_save(sfile, state)
|
||||
print(json.dumps({"decision": "block",
|
||||
"reason": BLOCK_REASON.format(tool=tool, n=THRESHOLD)},
|
||||
ensure_ascii=False))
|
||||
return
|
||||
print("{}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
print("{}")
|
||||
+9
-1
@@ -160,6 +160,14 @@ cp "$SRC/deploy/skills/projekt-start/SKILL.md" "$HOME/.hermes/skills/projekt-sta
|
||||
# Output = Konzept-Dokument (~/konzepte/<slug>-konzept.md) → Vorlage fuer projekt-start in Hermes Desktop.
|
||||
mkdir -p "$HOME/.hermes/skills/konzept-fliessband"
|
||||
cp "$SRC/deploy/skills/konzept-fliessband/SKILL.md" "$HOME/.hermes/skills/konzept-fliessband/SKILL.md"
|
||||
# Betrieb-Playbook (Phase 2 Drei-Welten-Plan, 19.07.2026): Diagnose-Checklisten der
|
||||
# Betrieb-Bahn (SSH/Deploy) — Gegenstueck zur No-Progress-Bremse. Auch in die Profile,
|
||||
# damit betrieb-Worker es laden koennen (Profile teilen KEINE Skills, Lehre 18.07.).
|
||||
for _sd in "$HOME/.hermes/skills" "$HOME"/.hermes/profiles/*/skills; do
|
||||
[ -d "$(dirname "$_sd")" ] || continue
|
||||
mkdir -p "$_sd/betrieb-playbook"
|
||||
cp "$SRC/deploy/skills/betrieb-playbook/SKILL.md" "$_sd/betrieb-playbook/SKILL.md"
|
||||
done
|
||||
# Agent-Hooks (Faden 5, 13.07.2026): Skripte frisch halten (reproduzierbar, ueberlebt
|
||||
# Box-Neuaufbau). box-steckbrief-inject.sh = Box-Selbstwissen in JEDEN Kanban-Worker
|
||||
# (pre_llm_call, scoped auf task_id → Lucy-Chat unberuehrt); pre-verify-gates.sh = das
|
||||
@@ -169,7 +177,7 @@ mkdir -p "$HOME/.hermes/agent-hooks"
|
||||
# Nur kopieren, wenn der Inhalt sich WIRKLICH geaendert hat — ein blindes cp aendert die
|
||||
# mtime auch bei identischem Inhalt und loest sonst bei JEDEM Deploy die harmlose
|
||||
# "script modified since approval"-Warnung von `hermes hooks doctor` aus.
|
||||
for _h in box-steckbrief-inject.sh pre-verify-gates.sh tabu-pfade-guard.py; do
|
||||
for _h in box-steckbrief-inject.sh pre-verify-gates.sh tabu-pfade-guard.py no-progress-bremse.py; do
|
||||
_src="$SRC/deploy/agent-hooks/$_h"; _dst="$HOME/.hermes/agent-hooks/$_h"
|
||||
cmp -s "$_src" "$_dst" 2>/dev/null || { cp "$_src" "$_dst"; chmod +x "$_dst"; }
|
||||
done
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Stellt sicher, dass ein Worker-Profil die Agent-Hooks kennt (Faden 8, 13.07.2026).
|
||||
"""Stellt sicher, dass ein Worker-Profil die Agent-Hooks kennt (Faden 8, 13.07.2026;
|
||||
erweitert 19.07.2026 um die No-Progress-Bremse und Mehrfach-Eintraege pro Event).
|
||||
|
||||
Kanban-Worker laufen unter IHREM Profil (HERMES_HOME=~/.hermes/profiles/<name>) und lesen
|
||||
dessen config.yaml — NICHT die Top-Level ~/.hermes/config.yaml. Die worker-relevanten Hooks
|
||||
(box-steckbrief-inject = Box-Wissen, tabu-pfade-guard = Schreibschutz) muessen also in JEDER
|
||||
Worker-Profil-config.yaml stehen, sonst feuern sie fuer Worker nie (Bug-Fund 13.07.).
|
||||
muessen also in JEDER Worker-Profil-config.yaml stehen, sonst feuern sie fuer Worker nie
|
||||
(Bug-Fund 13.07.).
|
||||
|
||||
Idempotent + validiert + Backup. Setzt einen bestehenden `pre_verify`-Hook-Block als Anker
|
||||
voraus (haben alle von default/werkstatt geklonten Profile); fehlt er, wird sauber
|
||||
uebersprungen (exit 0), damit deploy.sh nicht bricht. Arg: Pfad zur Profil-config.yaml.
|
||||
WICHTIG (Falle, 19.07.): Ein Event darf nur EINMAL als YAML-Key existieren. Existiert
|
||||
`pre_tool_call:` bereits (tabu-pfade-guard), wird ein weiterer Eintrag IN den Block
|
||||
eingefuegt — ein zweiter `pre_tool_call:`-Key wuerde beim YAML-Laden den ersten
|
||||
verschlucken und den Tabu-Guard lautlos deaktivieren.
|
||||
|
||||
Idempotent + validiert + Backup. Anker fuer neue Event-Bloecke ist der bestehende
|
||||
`pre_verify`-Block. Arg: Pfad zur Profil-config.yaml.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
@@ -22,10 +27,13 @@ except Exception:
|
||||
sys.exit(0)
|
||||
|
||||
AH = os.path.expanduser("~/.hermes/agent-hooks")
|
||||
WANT = {
|
||||
"pre_llm_call": f"{AH}/box-steckbrief-inject.sh",
|
||||
"pre_tool_call": f"{AH}/tabu-pfade-guard.py",
|
||||
}
|
||||
# Reihenfolge zaehlt: tabu-pfade-guard MUSS vor der Bremse stehen (Schutz vor Komfort).
|
||||
WANT = [
|
||||
("pre_llm_call", f"{AH}/box-steckbrief-inject.sh", 10),
|
||||
("pre_tool_call", f"{AH}/tabu-pfade-guard.py", 10),
|
||||
("pre_tool_call", f"{AH}/no-progress-bremse.py pre", 10),
|
||||
("post_tool_call", f"{AH}/no-progress-bremse.py post", 10),
|
||||
]
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print(" usage: ensure-profile-hooks.py <config.yaml>")
|
||||
@@ -44,22 +52,41 @@ if txt.count(anchor) != 1:
|
||||
print(f" [{os.path.basename(os.path.dirname(p))}] kein eindeutiger pre_verify-Anker — SKIP")
|
||||
sys.exit(0)
|
||||
|
||||
add = ""
|
||||
for event, cmd in WANT.items():
|
||||
if cmd not in txt:
|
||||
add += f" {event}:\n - command: {cmd}\n timeout: 10\n"
|
||||
new = txt
|
||||
changed = False
|
||||
for event, cmd, timeout in WANT:
|
||||
if cmd in new:
|
||||
continue
|
||||
entry = f" - command: {cmd}\n timeout: {timeout}\n"
|
||||
ev_line = f" {event}:\n"
|
||||
if ev_line in new:
|
||||
# Eintrag ANS ENDE des bestehenden Event-Blocks: direkt nach dem Key einfuegen
|
||||
# waere auch ok, aber hinter dem letzten Eintrag haelt die gewollte Reihenfolge.
|
||||
idx = new.index(ev_line) + len(ev_line)
|
||||
end = idx
|
||||
for line in new[idx:].splitlines(keepends=True):
|
||||
if line.startswith((" - ", " ")):
|
||||
end += len(line)
|
||||
else:
|
||||
break
|
||||
new = new[:end] + entry + new[end:]
|
||||
else:
|
||||
new = new.replace(anchor, anchor + ev_line + entry, 1)
|
||||
changed = True
|
||||
|
||||
if not add:
|
||||
if not changed:
|
||||
print(f" [{os.path.basename(os.path.dirname(p))}] Hooks schon registriert.")
|
||||
sys.exit(0)
|
||||
|
||||
new = txt.replace(anchor, anchor + add, 1)
|
||||
try:
|
||||
d = yaml.safe_load(new)
|
||||
h = d["hooks"]
|
||||
for event, cmd in WANT.items():
|
||||
assert any(cmd in x.get("command", "") for x in h.get(event, []))
|
||||
for event, cmd, _t in WANT:
|
||||
assert any(cmd == x.get("command", "") for x in h.get(event, [])), f"{event}: {cmd} fehlt"
|
||||
assert any("pre-verify-gates.sh" in x.get("command", "") for x in h["pre_verify"])
|
||||
# Tabu-Guard muss VOR der Bremse stehen (beide in pre_tool_call).
|
||||
cmds = [x.get("command", "") for x in h["pre_tool_call"]]
|
||||
assert cmds.index(f"{AH}/tabu-pfade-guard.py") < cmds.index(f"{AH}/no-progress-bremse.py pre")
|
||||
except Exception as exc:
|
||||
print(f" [{p}] Validierung fehlgeschlagen ({exc}) — config UNVERAENDERT.")
|
||||
sys.exit(0)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: betrieb-playbook
|
||||
description: Betrieb-Bahn-Playbook fuer SSH-, Deploy- und Dienst-Aufgaben auf Box/pve/LXCs. IMMER laden, bevor du per SSH auf eine Kiste gehst, einen Dienst anfasst oder etwas deployst — es enthaelt die Diagnose-Checklisten, die teure Schleifen verhindern.
|
||||
---
|
||||
|
||||
# Betrieb-Playbook (Bahn: BETRIEB)
|
||||
|
||||
## Triage zuerst
|
||||
Jede Aufgabe gehoert in GENAU eine Bahn: **Denken** (Konzept/Plan → Konzept-Fliessband),
|
||||
**Bauen** (Code/Feature → Werkstatt-Karte), **Betrieb** (SSH/Deploy/Dienst → DIESES Playbook).
|
||||
Wenn deine Aufgabe Code SCHREIBT, ist sie Bauen — nicht Betrieb. Mischformen aufteilen.
|
||||
|
||||
## Eiserne Regeln
|
||||
1. **Erster Versuch sitzt:** Vor dem ersten Befehl 30 Sekunden Lage klaeren (welcher Host,
|
||||
welcher User, welcher Key, welcher Dienst). Zeit = Schleifen × Kontext.
|
||||
2. **Maximal 3 Anlaeufe pro Hindernis.** Liefert ein Befehl 3x dasselbe Ergebnismuster
|
||||
(auch als "Erfolg" getarnt wie `Permission denied` in der Ausgabe): STOPP — Diagnose
|
||||
nach Checkliste, sonst blocked/Karte mit EXAKTEM Fehlertext an den Commander.
|
||||
Die No-Progress-Bremse erzwingt das mechanisch — arbeite MIT ihr, nicht gegen sie.
|
||||
3. **Verify ist Pflicht:** Nach jeder Aenderung den Zielzustand BEWEISEN
|
||||
(systemctl is-active, ss -tlnp, curl mit HTTP-Code) und in den Bericht schreiben.
|
||||
4. **Backup vor Aenderung:** Config/Datei erst `.bak`-kopieren, dann anfassen.
|
||||
Rollback-Weg im Kopf haben, BEVOR du aenderst.
|
||||
|
||||
## SSH-Diagnose-Checkliste (die teuer gelernte Reihenfolge)
|
||||
1. **Passphrase-Falle ZUERST:** `ssh-keygen -y -f <key> </dev/null` — scheitert das,
|
||||
ist der Key passphrase-verschluesselt und non-interaktiv nutzlos
|
||||
(stilles `Permission denied` TROTZ korrektem Pubkey; Wurzel des 36-min-Marathons 18.07.).
|
||||
2. Richtiger Key erzwungen? `ssh -o IdentitiesOnly=yes -i <key> user@host` —
|
||||
eine ~/.ssh/config-Wildcard kann den falschen Key aufzwingen.
|
||||
3. `Host key verification failed` = known_hosts-Problem des AUFRUFERS, nicht der Gegenseite
|
||||
(`ssh-keyscan host >> ~/.ssh/known_hosts` nach Pruefung des Fingerprints).
|
||||
4. Gegenseite LESEN statt raten: pve/LXC-Auth-Log via `journalctl _COMM=sshd-session`;
|
||||
Dienststatus `systemctl status <dienst>`; Erreichbarkeit `ss -tlnp` am Ziel.
|
||||
5. LXCs ohne SSH-Weg: `ssh pve` + `pct exec <id> -- <cmd>` / `pct push` (Dateien erst
|
||||
per scp auf pve zwischenlagern). authorized_keys auf pve IMMER in /root/.ssh UND
|
||||
/etc/pve/priv pflegen.
|
||||
|
||||
## Deploy-Checkliste (Box/LXC)
|
||||
1. Quelle = Git-Branch, nie lose Dateien. Clone nach /tmp, nach Erfolg aufraeumen.
|
||||
2. Zielzustand vorher erfassen (laeuft ein Alt-Prozess lose? Unit vorhanden/enabled?).
|
||||
3. Dateien uebertragen → Alt-Prozess sauber stoppen → Unit `daemon-reload` +
|
||||
`enable --now` → Verify (Schritt 3 der Eisernen Regeln).
|
||||
4. Secrets (.env) NIE durchs Repo, NIE in Karten — liegen am Ziel oder kommen vom Commander.
|
||||
Reference in New Issue
Block a user