printf '%s\n' '--- HERMES PROZESSE ---'
ps -ef | grep -i '[h]ermes' | head -n 30
RESTIC_REPOSITORY=/mnt/mybooklive-vps/RESTIC RESTIC_PASSWORD_FILE=/etc/restic/nas-password restic backup   /home/hermes/.hermes   /opt/struktur/portal   /opt/struktur/projects-dashboard   /opt/struktur/dashboard   /opt/struktur/agent-solutions-cockpit   /opt/struktur/00_INFRASTRUKTUR   /etc/nginx   /etc/systemd/system   --tag pre-hermes-update-20260816
RESTIC_REPOSITORY=/mnt/mybooklive-vps/RESTIC RESTIC_PASSWORD_FILE=/etc/restic/nas-password restic snapshots 6939d186
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent

echo "--- BRANCH ---"
git branch --show-current

echo "--- STATUS ---"
git status --short

echo "--- HEAD ---"
git log -1 --oneline

echo "--- REMOTES ---"
git remote -v

echo "--- FETCH ---"
git fetch --all --prune

echo "--- AHEAD / BEHIND ---"
git rev-list --left-right --count HEAD...@{upstream}

echo "--- REMOTE HEAD ---"
git log -1 --oneline @{upstream}
'
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent

echo "--- SHALLOW ---"
git rev-parse --is-shallow-repository

echo "--- LOKALER COMMIT ---"
git log --oneline --decorate origin/main..HEAD

echo "--- PACKAGE-LOCK DIFF STAT ---"
git diff --stat -- package-lock.json

echo "--- PACKAGE-LOCK DIFF KOPF ---"
git diff -- package-lock.json | head -n 120
'
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent

echo "--- UNSHALLOW FETCH ---"
git fetch --unshallow origin

echo "--- SHALLOW DANACH ---"
git rev-parse --is-shallow-repository

echo "--- AHEAD / BEHIND ECHT ---"
git rev-list --left-right --count HEAD...origin/main

echo "--- HEAD IST VORFAHRE VON ORIGIN/MAIN? ---"
if git merge-base --is-ancestor HEAD origin/main; then
    echo JA
else
    echo NEIN
fi

echo "--- MERGE BASE ---"
git merge-base HEAD origin/main

echo "--- NEUE COMMITS, ERSTE 15 ---"
git log --oneline HEAD..origin/main | head -n 15
'
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent

git diff -- package-lock.json \
  > /home/hermes/package-lock-pre-update-20260816.patch

ls -lh /home/hermes/package-lock-pre-update-20260816.patch
'
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent
git restore package-lock.json

echo "--- STATUS DANACH ---"
git status --short
'
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent

echo "--- STATUS ---"
git status --short

echo "--- PACKAGE-LOCK GEGEN ORIGIN/MAIN ---"
git diff --stat origin/main -- package-lock.json

echo "--- PACKAGE.JSON STATUS ---"
git status --short -- package.json package-lock.json
'
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent

echo "--- HERMES VERSION ---"
./venv/bin/python -m hermes_cli.main --version

echo "--- UPDATE HILFE ---"
./venv/bin/python -m hermes_cli.main update --help
'
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent
./venv/bin/python -m hermes_cli.main update --backup
'
systemctl restart hermes-dashboard.service
printf '%s\n' '--- DASHBOARD ---'
systemctl status hermes-dashboard.service --no-pager -l | head -n 15
printf '%s\n' '--- GATEWAY ---'
systemctl status hermes-gateway-hermesvps.service --no-pager -l | head -n 15
sudo -u hermes bash -lc '
cd /home/hermes/.hermes/hermes-agent

echo "--- VERSION ---"
./venv/bin/python -m hermes_cli.main --version

echo "--- GIT STATUS ---"
git status --short

echo "--- AHEAD / BEHIND ---"
git rev-list --left-right --count HEAD...origin/main

echo "--- HEAD ---"
git log -1 --oneline
'
printf '%s\n' '--- GESUCHTES SCRIPT ---'
find /home/hermes /opt/struktur   -type f -name 'youtube_status_consistency.py'   2>/dev/null
printf '%s\n' '--- ÄHNLICHE DATEIEN ---'
find /home/hermes /opt/struktur   -type f \( -iname '*youtube*status*' -o -iname '*status*consisten*' \)   2>/dev/null | head -n 40
grep -RIn   '/home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py'   /home/hermes/.hermes 2>/dev/null
printf '%s\n' '--- CRON JOB EINTRAG ---'
sed -n '1,90p' /home/hermes/.hermes/profiles/hermesvps/cron/jobs.json
printf '%s\n' '--- GLOBAL SCRIPT ---'
ls -l /home/hermes/.hermes/scripts/youtube_status_consistency.py
printf '%s\n' '--- PROFIL SCRIPT-ORDNER ---'
ls -ld /home/hermes/.hermes/profiles/hermesvps/scripts 2>/dev/null || echo "nicht vorhanden"
printf '%s\n' '--- SCRIPT KOPF ---'
head -n 20 /home/hermes/.hermes/scripts/youtube_status_consistency.py
cp /home/hermes/.hermes/scripts/youtube_status_consistency.py    /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
chown hermes:hermes   /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
chmod 750   /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
printf '%s\n' '--- PRÜFUNG ---'
ls -l /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
printf '%s\n' '--- TESTLAUF ---'
sudo -u hermes   /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
printf '%s\n' '--- DB DATEI ---'
ls -l /opt/struktur/youtube-research/knowledge.db
printf '%s\n' '--- VERZEICHNIS ---'
ls -ld /opt/struktur/youtube-research
printf '%s\n' '--- STAT DB ---'
stat -c 'owner=%U group=%G mode=%a'   /opt/struktur/youtube-research/knowledge.db
printf '%s\n' '--- STAT VERZEICHNIS ---'
stat -c 'owner=%U group=%G mode=%a'   /opt/struktur/youtube-research
printf '%s\n' '--- HERMES SCHREIBTEST ---'
sudo -u hermes test -w /opt/struktur/youtube-research/knowledge.db   && echo 'DB beschreibbar'   || echo 'DB NICHT beschreibbar'
sudo -u hermes test -w /opt/struktur/youtube-research   && echo 'Verzeichnis beschreibbar'   || echo 'Verzeichnis NICHT beschreibbar'
printf '%s\n' '--- YOUTUBE SERVICE ---'
systemctl cat youtube-research.service
printf '%s\n' '--- ACL VERFÜGBAR ---'
command -v setfacl || echo 'setfacl nicht vorhanden'
printf '%s\n' '--- ACL VORHER SICHERN ---'
getfacl /opt/struktur/youtube-research   /opt/struktur/youtube-research/knowledge.db   > /root/youtube-research-acl-before-hermes-20260816.txt
setfacl -m u:hermes:rwx /opt/struktur/youtube-research
setfacl -m u:hermes:rw /opt/struktur/youtube-research/knowledge.db
printf '%s\n' '--- SCHREIBRECHTE DANACH ---'
sudo -u hermes test -w /opt/struktur/youtube-research/knowledge.db   && echo 'DB beschreibbar'   || echo 'DB NICHT beschreibbar'
sudo -u hermes test -w /opt/struktur/youtube-research   && echo 'Verzeichnis beschreibbar'   || echo 'Verzeichnis NICHT beschreibbar'
printf '%s\n' '--- TESTLAUF SCRIPT ---'
sudo -u hermes   /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
printf '%s\n' '--- SQLITE DATEIEN ---'
ls -l /opt/struktur/youtube-research/knowledge.db*
printf '%s\n' '--- ACL SQLITE DATEIEN ---'
getfacl /opt/struktur/youtube-research/knowledge.db* 2>/dev/null
printf '%s\n' '--- JOURNAL MODE ---'
sqlite3 /opt/struktur/youtube-research/knowledge.db 'PRAGMA journal_mode;'
printf '%s\n' '--- OFFENE DB HANDLES ---'
lsof /opt/struktur/youtube-research/knowledge.db      /opt/struktur/youtube-research/knowledge.db-wal      /opt/struktur/youtube-research/knowledge.db-shm 2>/dev/null
printf '%s\n' '--- YOUTUBE SERVICE STATUS ---'
systemctl status youtube-research.service --no-pager -l | head -n 20
printf '%s\n' '--- PROZESS 1099194 ---'
ps -o pid,ppid,user,lstart,etime,cmd -p 1099194
printf '%s\n' '--- CMDLINE ---'
tr '\0' ' ' < /proc/1099194/cmdline
echo
printf '%s\n' '--- CGROUP ---'
cat /proc/1099194/cgroup
printf '%s\n' '--- ARBEITSVERZEICHNIS / PROGRAMM ---'
ls -l /proc/1099194/cwd /proc/1099194/exe
printf '%s\n' '--- ELTERNPROZESS ---'
PPID=$(ps -o ppid= -p 1099194 | tr -d ' ')
ps -o pid,ppid,user,lstart,etime,cmd -p "$PPID"
printf '%s\n' '--- E2E WORKER SERVICE ---'
systemctl status youtube-research-e2e-worker.service --no-pager -l | head -n 30
printf '%s\n' '--- E2E WORKER UNIT ---'
systemctl cat youtube-research-e2e-worker.service
printf '%s\n' '--- DB-VERBINDUNGEN IM CODE ---'
grep -nE 'sqlite3\.connect|\.close\(\)|knowledge\.db'   /opt/struktur/youtube-research/e2e_worker.py
printf '%s\n' '--- CODE ANFANG ---'
sed -n '1,45p' /opt/struktur/youtube-research/e2e_worker.py
printf '%s\n' '--- CODE HAUPTSCHLEIFE ---'
sed -n '190,230p' /opt/struktur/youtube-research/e2e_worker.py
printf '%s\n' '--- OFFENE KNOWLEDGE-DB FDS ---'
ls -l /proc/1099194/fd 2>/dev/null   | grep '/opt/struktur/youtube-research/knowledge.db'   | wc -l
printf '%s\n' '--- FD GESAMT ---'
ls /proc/1099194/fd 2>/dev/null | wc -l
printf '%s\n' '--- RESERVE FUNKTION ---'
grep -n '^def reserve' /opt/struktur/youtube-research/e2e_worker.py
printf '%s\n' '--- RESERVE CODE ---'
sed -n '70,120p' /opt/struktur/youtube-research/e2e_worker.py
sed -n '52,72p' /opt/struktur/youtube-research/e2e_worker.py
cp /opt/struktur/youtube-research/e2e_worker.py    /opt/struktur/youtube-research/e2e_worker.py.bak_20260817_fdleak
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/youtube-research/e2e_worker.py')
s = p.read_text()

old = """    while True:
        j=reserve(conn())
        if j: process(j)
        else: time.sleep(2)
"""

new = """    while True:
        c = conn()
        try:
            j = reserve(c)
        finally:
            c.close()
        if j: process(j)
        else: time.sleep(2)
"""

if old not in s:
    raise SystemExit("Abbruch: erwarteter Codeblock nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/youtube-research/e2e_worker.py
grep -n -A10 '^def main' /opt/struktur/youtube-research/e2e_worker.py
systemctl restart youtube-research-e2e-worker.service
sleep 3
printf '%s\n' '--- SERVICE STATUS ---'
systemctl status youtube-research-e2e-worker.service --no-pager -l | head -n 25
printf '%s\n' '--- NEUE PID ---'
systemctl show -p MainPID --value youtube-research-e2e-worker.service
printf '%s\n' '--- OFFENE KNOWLEDGE-DB FDS ---'
PID=$(systemctl show -p MainPID --value youtube-research-e2e-worker.service)
ls -l /proc/$PID/fd 2>/dev/null   | grep '/opt/struktur/youtube-research/knowledge.db'   | wc -l
printf '%s\n' '--- AKTUELLER SQLITE-ZUSTAND ---'
ls -l /opt/struktur/youtube-research/knowledge.db       /opt/struktur/youtube-research/knowledge.db-wal       /opt/struktur/youtube-research/knowledge.db-shm 2>/dev/null
printf '%s\n' '--- AKTUELLE ACL ---'
getfacl /opt/struktur/youtube-research/knowledge.db         /opt/struktur/youtube-research/knowledge.db-wal         /opt/struktur/youtube-research/knowledge.db-shm 2>/dev/null
sed -n '20,105p'   /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
sudo -u hermes python3 - <<'PY'
import sqlite3
from collections import Counter

DB = '/opt/struktur/youtube-research/knowledge.db'

conn = sqlite3.connect(f'file:{DB}?mode=ro', uri=True)
conn.row_factory = sqlite3.Row
cur = conn.cursor()

cur.execute("""
    SELECT v.id,
           v.transcript_status,
           v.transcript,
           (SELECT SUM(LENGTH(TRIM(ts.text)))
              FROM transcript_segments ts
             WHERE ts.video_id = v.id) AS seg_len
      FROM videos v
     WHERE v.transcript IS NULL
        OR TRIM(v.transcript) = ''
        OR v.transcript_status != 'done'
        OR (v.transcript_status = 'done'
            AND (v.transcript IS NULL OR TRIM(v.transcript) = ''))
""")

changes = []

for row in cur.fetchall():
    current = row['transcript_status']
    transcript = row['transcript'] or ''
    seg_len = row['seg_len'] or 0
    new = current

    if transcript and len(transcript.strip()) >= 500:
        new = 'done'
    elif not transcript and seg_len < 500:
        new = 'retry'
    elif not transcript and 500 <= seg_len < 5000:
        new = 'partial'
    elif transcript and len(transcript.strip()) < 500:
        new = 'partial'

    if new != current:
        changes.append((row['id'], current, new, len(transcript.strip()), seg_len))

conn.close()

print("ÄNDERUNGEN GESAMT:", len(changes))
print("ÜBERGÄNGE:")
for k, n in sorted(Counter((x[1], x[2]) for x in changes).items()):
    print(f"  {k!r} -> {k[1]!r}: {n}")

print("\nERSTE 20:")
for x in changes[:20]:
    print(x)
PY

sqlite3 -header -column /opt/struktur/youtube-research/knowledge.db "
SELECT
  id,
  youtube_id,
  title,
  transcript_status,
  LENGTH(COALESCE(transcript,'')) AS transcript_len,
  (
    SELECT SUM(LENGTH(TRIM(ts.text)))
    FROM transcript_segments ts
    WHERE ts.video_id = videos.id
  ) AS seg_len
FROM videos
WHERE id = 1196;
"
sudo -u hermes   /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
sqlite3 -header -column /opt/struktur/youtube-research/knowledge.db "
SELECT id, transcript_status
FROM videos
WHERE id = 1196;
"
printf '%s\n' '--- RUN STATUS DATEI ---'
ls -l /opt/struktur/youtube-research/run_status.json 2>/dev/null || echo "nicht vorhanden"
printf '%s\n' '--- ACL ---'
getfacl /opt/struktur/youtube-research/run_status.json 2>/dev/null
printf '%s\n' '--- VERWENDUNG IM CODE ---'
grep -RIn --exclude-dir=venv --exclude='*.bak*'   'run_status.json'   /opt/struktur/youtube-research 2>/dev/null
setfacl -m u:hermes:rw /opt/struktur/youtube-research/run_status.json
printf '%s\n' '--- PRÜFUNG ---'
getfacl /opt/struktur/youtube-research/run_status.json
printf '%s\n' '--- ERNEUTER TESTLAUF ---'
sudo -u hermes   /home/hermes/.hermes/profiles/hermesvps/scripts/youtube_status_consistency.py
printf '%s\n' '--- OBSIDIAN SKILL ---'
find /home/hermes/.hermes/profiles/hermesvps/skills   -maxdepth 4 -type f -iname 'SKILL.md'   -path '*obsidian*' 2>/dev/null
printf '%s\n' '--- VAULT ENV ---'
grep -n '^OBSIDIAN_VAULT_PATH='   /home/hermes/.hermes/profiles/hermesvps/.env 2>/dev/null   || echo 'OBSIDIAN_VAULT_PATH nicht gesetzt'
printf '%s\n' '--- VAULT ---'
ls -ld /opt/obsidian-vault
printf '%s\n' '--- HERMES VAULT RECHTE ---'
sudo -u hermes test -r /opt/obsidian-vault   && echo 'Vault lesbar'   || echo 'Vault NICHT lesbar'
sudo -u hermes test -w /opt/obsidian-vault   && echo 'Vault beschreibbar'   || echo 'Vault NICHT beschreibbar'
printf '%s\n' '--- BEISPIELDATEIEN ---'
find /opt/obsidian-vault -maxdepth 2 -type f -name '*.md' | head -n 10
cp /home/hermes/.hermes/profiles/hermesvps/.env    /home/hermes/.hermes/profiles/hermesvps/.env.bak_20260817_obsidian
printf '\nOBSIDIAN_VAULT_PATH=/opt/obsidian-vault\n'   >> /home/hermes/.hermes/profiles/hermesvps/.env
grep '^OBSIDIAN_VAULT_PATH='   /home/hermes/.hermes/profiles/hermesvps/.env
systemctl restart hermes-gateway-hermesvps.service
sleep 2
systemctl status hermes-gateway-hermesvps.service --no-pager -l | head -n 20
Lies aus meinem Obsidian-Vault die Datei
05-Ressourcen/Obsidian-Best-Practices.md
Nenne mir:
1. die erste Überschrift der Datei
2. zwei konkrete Inhalte daraus
3. den vollständigen Dateipfad, den du verwendet hast
Erfinde nichts. Wenn du die Datei nicht lesen kannst, sage das ausdrücklich.
python3 /opt/struktur/obsidian-graphiti-import/importer.py --ids 184
sqlite3 /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db ".tables"
sqlite3 /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "PRAGMA table_info(files);"
RESTIC_REPOSITORY=/mnt/mybooklive-vps/RESTIC RESTIC_PASSWORD_FILE=/etc/restic/nas-password restic backup   /home/hermes/.hermes   /opt/struktur/youtube-research   /etc/systemd/system   --tag post-hermes-update-20260817
grep -n -B 8 -A 20 -E 'restic|snapshots|snapshot'   /opt/struktur/dashboard/app-vpsverwaltung.py | head -n 160
sed -n '260,380p' /opt/struktur/dashboard/app-vpsverwaltung.py
cp /opt/struktur/dashboard/app-vpsverwaltung.py    /opt/struktur/dashboard/app-vpsverwaltung.py.bak_20260817_restic-sort
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = "      const snapshots = data.snapshots || [];"
new = "      const snapshots = [...(data.snapshots || [])].sort((a, b) => new Date(b.time) - new Date(a.time));"

if old not in s:
    raise SystemExit("Abbruch: erwartete Zeile nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
grep -n -A3 -B2 'const snapshots'   /opt/struktur/dashboard/app-vpsverwaltung.py
systemctl restart vpsverwaltung-dashboard.service
sleep 2
systemctl status vpsverwaltung-dashboard.service --no-pager -l | head -n 20
cat > /opt/struktur/00_INFRASTRUKTUR/restic-snapshot-notes.json <<'EOF'
{
  "40a56f36": {
    "title": "Hermes VPS – Post-Update-Granitstand",
    "description": "Verifizierter Stand nach Hermes-Update und anschließenden Reparaturen.",
    "changes": [
      "Hermes VPS auf v0.20.2 aktualisiert",
      "YouTube-Research-Cronjob repariert und manuell verifiziert",
      "FD-Leak im YouTube-Research-E2E-Worker behoben",
      "Obsidian-Vault lesend an Hermes VPS angebunden und getestet",
      "Kanban-Orchestration wieder auf Manual gestellt"
    ],
    "rollback_purpose": "Rückkehr auf den verifizierten Stand nach Update und Reparaturen.",
    "verification": "verifiziert"
  }
}
EOF

python3 -m json.tool   /opt/struktur/00_INFRASTRUKTUR/restic-snapshot-notes.json
cat > /opt/struktur/00_INFRASTRUKTUR/restic-snapshot-notes.json <<'EOF'
{
  "40a56f36": {
    "title": "Hermes VPS – Post-Update-Granitstand",
    "description": "Verifizierter Stand nach Hermes-Update und anschließenden Reparaturen.",
    "changes": [
      "Hermes VPS auf v0.20.2 aktualisiert",
      "YouTube-Research-Cronjob repariert und manuell verifiziert",
      "FD-Leak im YouTube-Research-E2E-Worker behoben",
      "Obsidian-Vault lesend an Hermes VPS angebunden und getestet",
      "Kanban-Orchestration wieder auf Manual gestellt"
    ],
    "rollback_purpose": "Rückkehr auf den verifizierten Stand nach Update und Reparaturen.",
    "verification": "verifiziert"
  }
}
EOF

python3 -m json.tool   /opt/struktur/00_INFRASTRUKTUR/restic-snapshot-notes.json
grep -n -B 10 -A 35 "@app.*restic"   /opt/struktur/dashboard/app-vpsverwaltung.py
grep -n -B 10 -A 40 "api/restic/snapshots"   /opt/struktur/dashboard/app-vpsverwaltung.py
cp /opt/struktur/dashboard/app-vpsverwaltung.py   /opt/struktur/dashboard/app-vpsverwaltung.py.bak_20260817_restic-notes
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """    return {'ok': True, 'snapshots': data if isinstance(data, list) else []}
"""

new = """    snapshots = data if isinstance(data, list) else []

    notes_path = '/opt/struktur/00_INFRASTRUKTUR/restic-snapshot-notes.json'
    try:
        with open(notes_path, 'r', encoding='utf-8') as f:
            notes = __import__('json').load(f)
    except Exception:
        notes = {}

    for snapshot in snapshots:
        sid = snapshot.get('id', '')
        for key, note in notes.items():
            if sid.startswith(key):
                snapshot['note'] = note
                break

    return {'ok': True, 'snapshots': snapshots}
"""

if old not in s:
    raise SystemExit("Abbruch: erwartete API-Zeile nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '515,545p' /opt/struktur/dashboard/app-vpsverwaltung.py
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """  const tags = (snapshot.tags || []).map(escapeHtml).join(', ') || '—';
  const paths = (snapshot.paths || []).map(p => '<li>' + escapeHtml(p) + '</li>').join('') || '<li>—</li>';
"""

new = """  const tags = (snapshot.tags || []).map(escapeHtml).join(', ') || '—';
  const paths = (snapshot.paths || []).map(p => '<li>' + escapeHtml(p) + '</li>').join('') || '<li>—</li>';

  const note = snapshot.note || {};
  const noteTitle = escapeHtml(note.title || '');
  const noteDescription = escapeHtml(note.description || '');
  const noteChanges = (note.changes || [])
    .map(x => '<li>' + escapeHtml(x) + '</li>')
    .join('');
  const rollbackPurpose = escapeHtml(note.rollback_purpose || '');
  const verification = escapeHtml(note.verification || '');
"""

if old not in s:
    raise SystemExit("Abbruch: erwarteter JS-Block nicht gefunden")

s = s.replace(old, new, 1)

old2 = """      <div class="row">
        <div class="label">Pfade</div>
        <div class="value"><ul>${paths}</ul></div>
      </div>
"""

new2 = """      <div class="row">
        <div class="label">Pfade</div>
        <div class="value"><ul>${paths}</ul></div>
      </div>

      ${noteTitle ? `
      <div class="row">
        <div class="label">Bezeichnung</div>
        <div class="value">${noteTitle}</div>
      </div>` : ''}

      ${noteDescription ? `
      <div class="row">
        <div class="label">Beschreibung</div>
        <div class="value">${noteDescription}</div>
      </div>` : ''}

      ${noteChanges ? `
      <div class="row">
        <div class="label">Änderungen</div>
        <div class="value"><ul>${noteChanges}</ul></div>
      </div>` : ''}

      ${rollbackPurpose ? `
      <div class="row">
        <div class="label">Rollback-Zweck</div>
        <div class="value">${rollbackPurpose}</div>
      </div>` : ''}

      ${verification ? `
      <div class="row">
        <div class="label">Verifizierungsstatus</div>
        <div class="value">${verification}</div>
      </div>` : ''}
"""

if old2 not in s:
    raise SystemExit("Abbruch: erwarteter HTML-Block nicht gefunden")

p.write_text(s.replace(old2, new2, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '268,345p' /opt/struktur/dashboard/app-vpsverwaltung.py
python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '315,365p' /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '365,385p' /opt/struktur/dashboard/app-vpsverwaltung.py
systemctl restart vpsverwaltung-dashboard.service
sleep 2
systemctl status vpsverwaltung-dashboard.service --no-pager -l | head -n 20
curl -s http://127.0.0.1:5002/api/restic/snapshots   | python3 -c "import sys,json; d=json.load(sys.stdin); s=next(x for x in d['snapshots'] if x['id'].startswith('40a56f36')); print(json.dumps(s.get('note'), ensure_ascii=False, indent=2))"
cp /opt/struktur/dashboard/app-vpsverwaltung.py   /opt/struktur/dashboard/app-vpsverwaltung.py.bak_20260817_snapshot-window
sed -i "s/width=760,height=650/width=760,height=975/"   /opt/struktur/dashboard/app-vpsverwaltung.py
python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
grep -n "width=760" /opt/struktur/dashboard/app-vpsverwaltung.py
grep -n -B 12 -A 35 -E "restic-backup|Repository erreichbar|restic.*snapshots|get_restic"   /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '418,470p' /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '470,522p' /opt/struktur/dashboard/app-vpsverwaltung.py
cp /opt/struktur/dashboard/app-vpsverwaltung.py   /opt/struktur/dashboard/app-vpsverwaltung.py.bak_20260817_restic-single-query
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """        elif s.get('restic'):
            try:
                r = subprocess.run(
                    [
                        'restic',
                        '-r', '/mnt/mybooklive-vps/RESTIC',
                        '--password-file', '/etc/restic/nas-password',
                        'snapshots',
                        '--json'
                    ],
                    capture_output=True,
                    text=True,
                    timeout=15
                )
                status_value = 'Repository erreichbar' if r.returncode == 0 else 'Repository nicht erreichbar'
            except Exception:
                status_value = 'Repository nicht erreichbar'

            result.append({
                'id': s['id'],
                'status': status_value
            })
"""

new = """        elif s.get('restic'):
            result.append({
                'id': s['id'],
                'status': 'Repository wird geprüft ...'
            })
"""

if old not in s:
    raise SystemExit("Abbruch: erwarteter Restic-Statusblock nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '482,510p' /opt/struktur/dashboard/app-vpsverwaltung.py
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """      const snapshots = [...(data.snapshots || [])].sort((a, b) => new Date(b.time) - new Date(a.time));
      window.resticSnapshots = snapshots;
"""

new = """      const badge = document.getElementById('badge-restic-backup');
      if (badge) {
        badge.textContent = 'Repository erreichbar';
        badge.className = 'badge online';
      }

      const snapshots = [...(data.snapshots || [])].sort((a, b) => new Date(b.time) - new Date(a.time));
      window.resticSnapshots = snapshots;
"""

if old not in s:
    raise SystemExit("Abbruch: erwarteter Snapshot-Block nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '379,405p' /opt/struktur/dashboard/app-vpsverwaltung.py
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """      const snapshots = [...(data.snapshots || [])].sort((a, b) => new Date(b.time) - new Date(a.time));
      window.resticSnapshots = snapshots;
"""

new = """      const badge = document.getElementById('badge-restic-backup');
      if (badge) {
        badge.textContent = 'Repository erreichbar';
        badge.className = 'badge online';
      }

      const snapshots = [...(data.snapshots || [])].sort((a, b) => new Date(b.time) - new Date(a.time));
      window.resticSnapshots = snapshots;
"""

if old not in s:
    raise SystemExit("Abbruch: erwarteter Snapshot-Block nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '379,405p' /opt/struktur/dashboard/app-vpsverwaltung.py
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

dup = """      const badge = document.getElementById('badge-restic-backup');
      if (badge) {
        badge.textContent = 'Repository erreichbar';
        badge.className = 'badge online';
      }

      const badge = document.getElementById('badge-restic-backup');
      if (badge) {
        badge.textContent = 'Repository erreichbar';
        badge.className = 'badge online';
      }
"""

single = """      const badge = document.getElementById('badge-restic-backup');
      if (badge) {
        badge.textContent = 'Repository erreichbar';
        badge.className = 'badge online';
      }
"""

if dup not in s:
    raise SystemExit("Abbruch: erwartete Dublette nicht gefunden")

p.write_text(s.replace(dup, single, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '379,402p' /opt/struktur/dashboard/app-vpsverwaltung.py
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """      if (!ok) {
        target.innerHTML =
          '<div class="restic-error">Snapshot-Abfrage nicht verfügbar: ' +
          escapeHtml(data.error || 'unbekannter Fehler') +
          '</div>';
        return;
      }
"""

new = """      if (!ok) {
        const badge = document.getElementById('badge-restic-backup');
        if (badge) {
          badge.textContent = 'Repository nicht erreichbar';
          badge.className = 'badge offline';
        }

        target.innerHTML =
          '<div class="restic-error">Snapshot-Abfrage nicht verfügbar: ' +
          escapeHtml(data.error || 'unbekannter Fehler') +
          '</div>';
        return;
      }
"""

if old not in s:
    raise SystemExit("Abbruch: erwarteter Fehlerblock nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '379,407p' /opt/struktur/dashboard/app-vpsverwaltung.py
systemctl restart vpsverwaltung-dashboard.service
sleep 2
systemctl status vpsverwaltung-dashboard.service --no-pager -l | head -n 20
printf '%s\n' '--- AKTIVE RESTIC-PROZESSE ---'
pgrep -af "restic .*snapshots --json"
sed -i "s/width=760,height=975/width=760,height=1200/"   /opt/struktur/dashboard/app-vpsverwaltung.py
python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
grep -n "width=760" /opt/struktur/dashboard/app-vpsverwaltung.py
systemctl restart vpsverwaltung-dashboard.service
sleep 2
systemctl status vpsverwaltung-dashboard.service --no-pager -l | head -n 20
printf '%s\n' '--- NAS MOUNT ---'
mountpoint /mnt/mybooklive-vps
printf '%s\n' '--- RESTIC API ---'
time curl -sS --max-time 40   http://127.0.0.1:5002/api/restic/snapshots   | python3 -c "import sys,json; d=json.load(sys.stdin); print('ok=',d.get('ok')); print('snapshots=',len(d.get('snapshots',[]))); print('error=',d.get('error'))"
printf '%s\n' '--- AKTIVE RESTIC-PROZESSE ---'
pgrep -af "restic .*snapshots --json" || echo "kein Restic-Prozess"
printf '%s\n' '--- DASHBOARD LOG ---'
journalctl -u vpsverwaltung-dashboard.service -n 20 --no-pager
cp /opt/struktur/dashboard/app-vpsverwaltung.py   /opt/struktur/dashboard/app-vpsverwaltung.py.bak_20260817_restic-badge-fix
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """    data.forEach(s => {
      if (s.id === 'security') {
"""

new = """    data.forEach(s => {
      if (s.id === 'restic-backup') {
        return;
      }

      if (s.id === 'security') {
"""

if old not in s:
    raise SystemExit("Abbruch: erwartete Stelle nicht gefunden")

p.write_text(s.replace(old, new, 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '420,435p' /opt/struktur/dashboard/app-vpsverwaltung.py
sed -n '432,445p' /opt/struktur/dashboard/app-vpsverwaltung.py
systemctl restart vpsverwaltung-dashboard.service
sleep 2
systemctl status vpsverwaltung-dashboard.service --no-pager -l | head -n 20
printf '%s\n' '--- NEUESTE DATEIEN UNTER /opt/backups ---'
find /opt/backups -type f -printf '%T@ %TY-%Tm-%Td %TH:%TM:%TS %p\n'   2>/dev/null   | sort -nr   | head -n 20
cp /opt/struktur/dashboard/app-vpsverwaltung.py   /opt/struktur/dashboard/app-vpsverwaltung.py.bak_20260817_remove-backups-tile
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = "    {'id': 'backups',         'name': 'Backups',            'desc': 'Temporäre Kontrolle lokaler Sicherungen unter /opt/backups.',     'icon': '\\U0001f4be', 'url': None, 'container': None, 'compose': None, 'host_check': 'backups'},\n"

if old not in s:
    raise SystemExit("Abbruch: Backups-Kachel nicht gefunden")

p.write_text(s.replace(old, '', 1))
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
grep -n "'id': 'backups'"   /opt/struktur/dashboard/app-vpsverwaltung.py   || echo "Backups-Kachel entfernt"
systemctl restart vpsverwaltung-dashboard.service
sleep 2
systemctl status vpsverwaltung-dashboard.service --no-pager -l | head -n 20
printf '%s\n' '--- KACHELN IM CODE ---'
grep -n "{'id':" /opt/struktur/dashboard/app-vpsverwaltung.py | head -n 20
printf '%s\n' '--- TOP-LEVEL /opt/backups ---'
find /opt/backups -mindepth 1 -maxdepth 1   -printf '%TY-%Tm-%Td %TH:%TM  %y  %p\n'   | sort
printf '%s\n' '--- GRÖSSTE EINTRÄGE ---'
du -sh /opt/backups/* 2>/dev/null | sort -h | tail -n 20
printf '%s\n' '--- DOKUMENTATIONS-/REFERENZDATEIEN ---'
find /opt/backups -type f   \( -iname '*.md' -o -iname '*.txt' -o -iname '*.json' \)   -printf '%TY-%Tm-%Td %TH:%TM  %p\n'   2>/dev/null   | sort
printf '%s\n' '--- AKTUELLE VERGLEICHSDATEIEN AUSSERHALB /opt/backups ---'
find /opt/struktur /home/hermes/.hermes /opt/obsidian-vault   -type f   \(     -name 'LP - Vento Salesbot.json'     -o -name 'VENTO WhatsApp Gateway (Text + Voice).json'     -o -name 'communication-checklist.md'     -o -name 'communication_style.md'     -o -name 'SKILL.md'     -o -name 'AENDERUNGSPROTOKOLL.md'   \)   2>/dev/null   | grep -v '^/opt/backups/'   | sort
cat /opt/backups/cockpit-carlo-kachelkorrektur/20260801_151352/AENDERUNGSPROTOKOLL.md
printf '%s\n' '--- AKTIVE REFERENZEN AUF /opt/backups ---'
grep -RIn '/opt/backups'   /etc/systemd/system   /etc/cron.d   /etc/cron.daily   /etc/cron.hourly   /etc/cron.weekly   /etc/cron.monthly   /var/spool/cron/crontabs   2>/dev/null
rm -rf /opt/backups
if [ -e /opt/backups ]; then   echo "FEHLER: /opt/backups existiert noch"; else   echo "/opt/backups vollständig entfernt"; fi
RESTIC_REPOSITORY=/mnt/mybooklive-vps/RESTIC RESTIC_PASSWORD_FILE=/etc/restic/nas-password restic backup   /home/hermes/.hermes   /opt/struktur/dashboard   /opt/struktur/portal   /opt/struktur/projects-dashboard   /opt/struktur/agent-solutions-cockpit   /opt/struktur/00_INFRASTRUKTUR   /opt/struktur/youtube-research   /etc/nginx   /etc/systemd/system   --tag pre-buzz-granitstand-20260817
mkdir -p /opt/struktur/buzz
ls -ld /opt/struktur/buzz
curl -s http://127.0.0.1:8644/openapi.json | python3 -m json.tool | grep -n -A 6 -B 2 'episodes'
curl -s http://127.0.0.1:8644/episodes/inventory | python3 -m json.tool
curl -s http://127.0.0.1:8644/episodes/inventory | python3 -c 'import sys,json; d=json.load(sys.stdin); print(len(d.get("episodes", d) if isinstance(d,dict) else d))'
curl -s http://127.0.0.1:8644/episodes/inventory | python3 - <<'PY'
import sys, json
d=json.load(sys.stdin)
eps=d.get("episodes", d) if isinstance(d,dict) else d

obs=0
yt=0
other=0

for e in eps:
    name=str(e.get("name",""))
    meta=e.get("metadata") or {}
    source=str(meta.get("source",""))

    if name.startswith("obsidian_") or source=="obsidian":
        obs+=1
    elif name.startswith("youtube_") or source=="youtube-research":
        yt+=1
    else:
        other+=1

print("gesamt:", len(eps))
print("obsidian:", obs)
print("youtube:", yt)
print("andere:", other)
PY

curl -s http://127.0.0.1:8644/episodes/inventory | python3 -c '
import sys,json
d=json.load(sys.stdin)
eps=d.get("episodes", d) if isinstance(d,dict) else d

obs=yt=other=0
for e in eps:
    name=str(e.get("name",""))
    meta=e.get("metadata") or {}
    source=str(meta.get("source",""))
    if name.startswith("obsidian_") or source=="obsidian":
        obs+=1
    elif name.startswith("youtube_") or source=="youtube-research":
        yt+=1
    else:
        other+=1

print("gesamt:", len(eps))
print("obsidian:", obs)
print("youtube:", yt)
print("andere:", other)
'
curl -s http://127.0.0.1:8644/episodes/inventory | python3 -c '
import sys,json
d=json.load(sys.stdin)
eps=d.get("episodes",d) if isinstance(d,dict) else d
needle="Hermes-n8n-MCP-Installation-und-Fehlerbehebung.md"

hits=[]
for e in eps:
    if needle.lower() in json.dumps(e,ensure_ascii=False).lower():
        hits.append(e)

print("Treffer:",len(hits))
for e in hits[:20]:
    print("name:",e.get("name"))
    print("uuid:",e.get("uuid"))
    print("metadata:",e.get("metadata"))
    print("---")
'
grep -n -A 40 -B 5 '^def post' /opt/struktur/obsidian-graphiti-import/importer.py
curl -s http://127.0.0.1:8644/episodes/inventory | python3 -c '
import sys,json
d=json.load(sys.stdin)
eps=d.get("episodes",d) if isinstance(d,dict) else d
for e in eps:
    name=str(e.get("name",""))
    meta=e.get("metadata") or {}
    source=str(meta.get("source",""))
    if name.startswith("obsidian_") or source=="obsidian":
        print(json.dumps(e,ensure_ascii=False,indent=2)[:12000])
        break
'
docker inspect graphiti-service --format '{{json .Mounts}}' | python3 -m json.tool
docker inspect graphiti-service --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -Ei 'NEO4J|GRAPH_DB|DATABASE|BOLT|URI|HOST|PORT'
docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}' | grep -i neo4j
docker exec graphiti-neo4j cypher-shell -u neo4j -p "$(docker inspect graphiti-neo4j --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^NEO4J_AUTH=neo4j\///p')" "CALL db.labels() YIELD label RETURN label ORDER BY label;"
docker exec graphiti-neo4j cypher-shell -u neo4j -p "$(docker inspect graphiti-neo4j --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^NEO4J_AUTH=neo4j\///p')" "MATCH (n)
 WHERE n:ObsidianDocument OR n:ObsidianChunk OR n:Episodic
 UNWIND labels(n) AS label
 WITH label, collect(DISTINCT keys(n)) AS keysets, count(n) AS nodes
 WHERE label IN ['ObsidianDocument','ObsidianChunk','Episodic']
 RETURN label, nodes, keysets
 ORDER BY label;"
docker exec graphiti-neo4j cypher-shell -u neo4j -p "$(docker inspect graphiti-neo4j --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^NEO4J_AUTH=neo4j\///p')" "MATCH (a)-[r]->(b)
 WHERE (a:ObsidianChunk OR a:ObsidianDocument OR a:Entity OR a:KnowledgeClaim OR a:KnowledgeEvent OR a:KnowledgeRule)
   AND (b:ObsidianChunk OR b:ObsidianDocument OR b:Entity OR b:KnowledgeClaim OR b:KnowledgeEvent OR b:KnowledgeRule)
 RETURN labels(a) AS from_labels, type(r) AS relation, labels(b) AS to_labels, count(*) AS n
 ORDER BY n DESC
 LIMIT 50;"
docker exec graphiti-neo4j cypher-shell -u neo4j -p "$(docker inspect graphiti-neo4j --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^NEO4J_AUTH=neo4j\///p')" "MATCH (n)
 WHERE n:Entity OR n:KnowledgeClaim OR n:KnowledgeEvent OR n:KnowledgeRule OR n:KnowledgeSubject
 UNWIND labels(n) AS label
 WITH label, count(*) AS n
 WHERE label IN ['Entity','KnowledgeClaim','KnowledgeEvent','KnowledgeRule','KnowledgeSubject']
 RETURN label,n
 ORDER BY label;"
docker exec graphiti-neo4j cypher-shell -u neo4j -p "$(docker inspect graphiti-neo4j --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^NEO4J_AUTH=neo4j\///p')" "MATCH (a:Entity)-[r:RELATES_TO]->(b:Entity)
 RETURN a.name AS von,
        r.name AS beziehung,
        b.name AS nach,
        r.fact AS fakt
 LIMIT 20;"
sqlite3 -header -column /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "SELECT id, relative_path, status, attempts, last_attempt, imported_at, episode_ref, graphiti_episode_name, graphiti_episode_uuid, graphiti_status, last_error, last_error_phase, supersedes_id, import_key, updated_at FROM files WHERE id=184;"
python3 /opt/struktur/obsidian-graphiti-import/importer.py --help
python3 /opt/struktur/obsidian-graphiti-import/importer.py --force-id 184
grep -n -B 12 -A 30 "force-id\|force_id" /opt/struktur/obsidian-graphiti-import/importer.py
cp -a /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db.bak-20260817-pre-id184-retry
ls -lh /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db.bak-20260817-pre-id184-retry
sqlite3 /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "UPDATE files
 SET status='retry',
     graphiti_status='retry',
     last_error=NULL,
     last_error_phase=NULL
 WHERE id=184;
 SELECT id,status,attempts,graphiti_status,last_error,last_error_phase
 FROM files
 WHERE id=184;"
python3 /opt/struktur/obsidian-graphiti-import/importer.py --ids 184 --force-id 184
sqlite3 -header -column /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "SELECT id,status,attempts,last_attempt,graphiti_status,graphiti_episode_name,graphiti_episode_uuid,last_error,last_error_phase,updated_at
 FROM files
 WHERE id=184;"
root@ubuntu:~#
root@ubuntu:~# sqlite3 -header -column /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "SELECT id,status,attempts,last_attempt,graphiti_status,graphiti_episode_name,graphiti_episode_uuid,last_error,last_error_phase,updated_at
 FROM files
 WHERE id=184;"
id   status  attempts  last_attempt                      graphiti_status  graphiti_episode_name  graphiti_episode_uuid  last_error                                                                                                                                                                                                                                                                      last_error_phase  updated_at
---  ------  --------  --------------------------------  ---------------  ---------------------  ---------------------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  ----------------  --------------------------------
184  failed  6         2026-08-17T11:11:09.469586+00:00  failed                                                         server_timeout: {"error_type":"ProviderTimeout","error_phase":"edge_extraction","terminal_reason":"llm_call_timeout","request_id":"req-3ce97943f2e67f848a1b590e","run_id":"run-2026-08-17T11:03:26.690690+00:00","retryable":true,"episode_created":false,"episode_uuid":null}  server_timeout    2026-08-16T09:17:13.414980+00:00
root@ubuntu:~#
journalctl --since "2026-08-17 11:00:00" --no-pager | grep -C 20 "req-3ce97943f2e67f848a1b590e"
journalctl --since "2026-08-17 11:00:00" --no-pager | grep -C 20 "req-3ce97943f2e67f848a1b590e"
journalctl --since "2026-08-17 11:00:00" --no-pager | grep -Ei -C 20 "ProviderTimeout|edge_extraction|llm_call_timeout"
systemctl status graphiti --no-pager -l
systemctl list-units --type=service --state=running --no-pager | grep -Ei "graphiti|8644|knowledge|memory"
ss -ltnp | grep ':8644'
docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Ports}}' | grep '8644'
docker logs --since "2026-08-17T11:00:00" graphiti-service 2>&1 | grep -Ei -C 30 "req-3ce97943f2e67f848a1b590e|ProviderTimeout|edge_extraction|llm_call_timeout"
docker logs --timestamps   --since "2026-08-17T11:05:00Z"   --until "2026-08-17T11:17:00Z"   graphiti-service 2>&1docker logs --timestamps   --since "2026-08-17T11:05:00Z"   --until "2026-08-17T11:17:00Z"   graphiti-service 2>&1
docker logs --timestamps   --since "2026-08-17T11:05:00Z"   --until "2026-08-17T11:17:00Z"   graphiti-service 2>&1
docker inspect graphiti-service --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -Ei 'OPENAI|ANTHROPIC|GROQ|MODEL|LLM|PROVIDER|API_KEY'
docker inspect graphiti-service --format '{{range .Config.Env}}{{println .}}{{end}}' | grep '^OPENROUTER_API_KEY=' | sed -E 's#(OPENROUTER_API_KEY=).{8}.*(.{4})$#\1********\2#'
sqlite3 /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "UPDATE files
 SET status='retry',
     graphiti_status='retry',
     last_error=NULL,
     last_error_phase=NULL
 WHERE id=184;
 SELECT id,status,attempts,graphiti_status
 FROM files
 WHERE id=184;"
python3 /opt/struktur/obsidian-graphiti-import/importer.py --ids 184 --force-id 184
sqlite3 -header -column /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "SELECT id,status,attempts,last_attempt,graphiti_status,graphiti_episode_name,graphiti_episode_uuid,last_error,last_error_phase
 FROM files
 WHERE id=184;"
sqlite3 /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "UPDATE files
 SET status='retry',
     graphiti_status='retry',
     last_error=NULL,
     last_error_phase=NULL
 WHERE id=184;
 SELECT id,status,attempts,graphiti_status
 FROM files
 WHERE id=184;"
python3 - <<'PY'
import sqlite3, json, urllib.request

db="/opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db"
row=sqlite3.connect(db).execute(
    "SELECT import_key,relative_path,content_hash FROM files WHERE id=184"
).fetchone()

payload={
    "name":"obsidian_"+row[0],
    "import_key":row[0],
    "obsidian_path":row[1],
    "content_hash":row[2],
}

req=urllib.request.Request(
    "http://127.0.0.1:8644/episodes/check",
    json.dumps(payload).encode(),
    {"Content-Type":"application/json"}
)

with urllib.request.urlopen(req,timeout=30) as r:
    print(r.read().decode())
PY

docker exec graphiti-service python - <<'PY'
import os, json, urllib.request

url = os.environ["LLM_BASE_URL"].rstrip("/") + "/chat/completions"
payload = {
    "model": os.environ["LLM_MODEL"],
    "messages": [{"role": "user", "content": "Reply only with OK"}],
    "max_tokens": 10
}

req = urllib.request.Request(
    url,
    data=json.dumps(payload).encode(),
    headers={
        "Authorization": "Bearer " + os.environ["OPENROUTER_API_KEY"],
        "Content-Type": "application/json"
    }
)

try:
    with urllib.request.urlopen(req, timeout=30) as r:
        data = json.loads(r.read())
        print("HTTP", r.status)
        print("MODEL", data.get("model"))
        print("ANSWER", data.get("choices", [{}])[0].get("message", {}).get("content"))
except Exception as e:
    print(type(e).__name__ + ":", str(e))
PY

docker exec -i graphiti-service python - <<'PY'
import os, json, urllib.request

url = os.environ["LLM_BASE_URL"].rstrip("/") + "/chat/completions"
payload = {
    "model": os.environ["LLM_MODEL"],
    "messages": [{"role": "user", "content": "Reply only with OK"}],
    "max_tokens": 10
}

req = urllib.request.Request(
    url,
    data=json.dumps(payload).encode(),
    headers={
        "Authorization": "Bearer " + os.environ["OPENROUTER_API_KEY"],
        "Content-Type": "application/json"
    }
)

try:
    with urllib.request.urlopen(req, timeout=30) as r:
        data = json.loads(r.read())
        print("HTTP", r.status)
        print("MODEL", data.get("model"))
        print("ANSWER", data.get("choices", [{}])[0].get("message", {}).get("content"))
except Exception as e:
    print(type(e).__name__ + ":", str(e))
PY

python3 /opt/struktur/obsidian-graphiti-import/importer.py --ids 184 --force-id 184
sqlite3 -header -column /opt/struktur/obsidian-graphiti-import/obsidian_graphiti_import.db "SELECT id,status,attempts,last_attempt,graphiti_status,graphiti_episode_name,graphiti_episode_uuid,last_error,last_error_phase
 FROM files
 WHERE id=184;"
docker logs --timestamps   --since "2026-08-17T12:03:30Z"   --until "2026-08-17T12:05:30Z"   graphiti-service 2>&1
docker logs --tail 300 graphiti-service 2>&1
docker inspect graphiti-service --format 'Entrypoint={{json .Config.Entrypoint}}
Cmd={{json .Config.Cmd}}
WorkingDir={{.Config.WorkingDir}}
Image={{.Config.Image}}'
docker exec graphiti-service sh -lc 'grep -n -B 15 -A 80 "episodes" /app/main.py | head -n 220'
docker exec graphiti-service sh -lc 'grep -n -B 10 -A 25 "def _request_db\|REQUEST_DB\|graphiti_requests" /app/main.py | head -n 120'
docker exec -i graphiti-service python - <<'PY'
import sqlite3

db="/var/lib/graphiti/requests.db"
c=sqlite3.connect(db)
c.row_factory=sqlite3.Row

rows=c.execute("""
SELECT
    request_id,
    queue_id,
    episode_name,
    started_at,
    completed_at,
    status,
    http_status,
    error_phase,
    error_message,
    terminal_reason,
    llm_request_count,
    model,
    provider
FROM graphiti_requests
WHERE queue_id='184'
ORDER BY started_at DESC
LIMIT 5
""").fetchall()

for r in rows:
    print("----")
    for k in r.keys():
        print(f"{k}: {r[k]}")
PY

graphiti_worker_failed:
Unterminated string starting at: line 45 column 27 (char 1925)
docker exec graphiti-service sh -lc 'grep -n -B 20 -A 40 "json.loads\|JSONDecodeError\|parse" /app/graphiti_worker.py | head -n 240'
docker exec graphiti-service sh -lc 'sed -n "1,260p" /app/graphiti_worker.py'
docker exec graphiti-service sh -lc 'grep -n "PROCESSING_EVENTS" /app/main.py'
docker exec graphiti-service sh -lc 'grep "req-690cf5fd4255019f779dd7cd" /var/lib/graphiti/processing-events.jsonl'
docker exec graphiti-service sh -lc 'grep -Rni "4096\|max_tokens" /app /usr/local/lib/python*/site-packages/graphiti_core 2>/dev/null | head -n 120'
docker exec graphiti-service sh -lc 'sed -n "410,470p" /app/main.py'
docker exec graphiti-service sh -lc 'sed -n "1,180p" /app/llm_call_worker.py'
docker exec graphiti-service sh -lc 'sed -n "220,330p" /app/main.py'
docker exec graphiti-service sh -lc 'tail -n 20 /tmp/graphiti-llm-diagnostics.jsonl'
docker exec graphiti-service python - <<'PY'
import os, json, urllib.request

url="https://openrouter.ai/api/v1/models"
with urllib.request.urlopen(url, timeout=30) as r:
    data=json.load(r)

target=os.environ["LLM_MODEL"]
for m in data.get("data",[]):
    if m.get("id")==target:
        print(json.dumps({
            "id":m.get("id"),
            "context_length":m.get("context_length"),
            "top_provider":m.get("top_provider"),
            "architecture":m.get("architecture")
        }, indent=2))
        break
PY

codex --version && codex login status
codex exec -m gpt-5.6-luna 'Return exactly this JSON and nothing else: {"ok":true}'
codex exec --skip-git-repo-check -m gpt-5.6-luna 'Return exactly this JSON and nothing else: {"ok":true}'
codex exec --help | sed -n '1,240p'
cat >/tmp/codex-test-schema.json <<'EOF'
{
  "type": "object",
  "properties": {
    "ok": {
      "type": "boolean"
    }
  },
  "required": ["ok"],
  "additionalProperties": false
}
EOF

rm -f /tmp/codex-test-result.json
codex exec   --skip-git-repo-check   --ephemeral   --sandbox read-only   -m gpt-5.6-luna   --output-schema /tmp/codex-test-schema.json   --output-last-message /tmp/codex-test-result.json   'Return an object with ok set to true.'
echo
echo '=== RESULT FILE ==='
cat /tmp/codex-test-result.json
echo '=== RESTIC UNITS ==='
systemctl list-unit-files --type=service --type=timer | grep -Ei 'restic|backup' || true
echo
echo '=== ACTIVE/FAILED RESTIC/BACKUP UNITS ==='
systemctl --no-pager --type=service --state=running,failed | grep -Ei 'restic|backup' || true
echo
echo '=== RESTIC PROCESSES ==='
ps auxww | grep -Ei '[r]estic|[b]ackup' || true
echo
echo '=== RESTIC CONFIG REFERENCES ==='
grep -RIlE 'RESTIC_REPOSITORY|restic'   /etc/systemd/system   /opt/struktur   /usr/local/bin   2>/dev/null | head -80
PID=$(pgrep -f '^restic -r /mnt/mybooklive-vps/RESTIC .*snapshots --json' | head -1)
echo '=== RESTIC PROCESS ==='
ps -o pid,ppid,etime,state,wchan:40,cmd -p "$PID"
echo
echo '=== REPOSITORY MOUNT ==='
findmnt -T /mnt/mybooklive-vps/RESTIC -o TARGET,SOURCE,FSTYPE,OPTIONS
echo
echo '=== MOUNTPOINT TEST ==='
timeout 5 stat /mnt/mybooklive-vps/RESTIC
echo "stat exit=$?"
echo
echo '=== DIRECTORY TEST ==='
timeout 5 ls -ld /mnt/mybooklive-vps /mnt/mybooklive-vps/RESTIC
echo "ls exit=$?"
echo
echo '=== DASHBOARD RESTIC LOG ==='
grep -iE 'restic|repository|snapshot|timeout|error|fail'   /opt/struktur/dashboard/dashboard.log | tail -60
echo
echo '=== KERNEL NAS/MOUNT ERRORS ==='
dmesg --ctime | grep -Ei 'cifs|nfs|mybook|I/O error|timed out|server not responding' | tail -40
echo '=== NAS NETWORK ==='
ping -c 4 -W 2 192.168.1.66
echo
echo '=== SMB PORT ==='
timeout 5 bash -c '</dev/tcp/192.168.1.66/445'
echo "SMB 445 exit=$?"
echo
echo '=== CIFS MOUNT DETAILS ==='
cat /proc/mounts | grep '/mnt/mybooklive-vps'
echo
echo '=== CURRENT RESTIC PROCESSES ==='
ps -eo pid,ppid,etime,state,wchan:35,cmd | grep '[r]estic'
echo
echo '=== CIFS ERRORS LAST 10 MIN ==='
journalctl -k --since '10 minutes ago' --no-pager   | grep -Ei 'CIFS|SMB|192\.168\.1\.66' | tail -80
echo '=== RESTIC ENDPOINT CODE ==='
grep -n -B15 -A45 -E '/api/restic/snapshots|def .*restic|restic.*snapshots|subprocess.*restic' /opt/struktur/dashboard/app-vpsverwaltung.py
echo
echo '=== TIMEOUTS IM RESTIC-BEREICH ==='
grep -n -E 'timeout|TimeoutExpired|504|snapshots' /opt/struktur/dashboard/app-vpsverwaltung.py | head -120
echo
echo '=== FRONTEND POLLING ==='
grep -n -E 'restic/snapshots|setInterval|30000|30_000|fetch.*restic' /opt/struktur/dashboard/app-vpsverwaltung.py | head -120
systemctl stop vpsverwaltung-dashboard.service
echo '===== 1. KEINE DASHBOARD-RESTIC-PROZESSE ====='
pgrep -af 'restic.*RESTIC' || echo 'keine Restic-Prozesse'
echo
echo '===== 2. CIFS BASISZUGRIFF ====='
/usr/bin/time -f 'stat: %e s' stat /mnt/mybooklive-vps/RESTIC >/dev/null
/usr/bin/time -f 'list root: %e s' ls /mnt/mybooklive-vps/RESTIC >/dev/null
echo
echo '===== 3. REPOSITORY-CONFIG ROH LESEN ====='
/usr/bin/time -f 'config file read: %e s'   dd if=/mnt/mybooklive-vps/RESTIC/config of=/dev/null bs=64K count=1 status=none
echo "exit=$?"
echo
echo '===== 4. RESTIC REPOSITORY ÖFFNEN ====='
START=$(date +%s)
timeout 60 restic   -r /mnt/mybooklive-vps/RESTIC   --password-file /etc/restic/nas-password   cat config >/dev/null 2>/tmp/restic-cat.err
RC=$?
END=$(date +%s)
echo "restic cat config: exit=$RC duration=$((END-START))s"
cat /tmp/restic-cat.err
echo
echo '===== 5. SNAPSHOT-VERZEICHNIS ====='
START=$(date +%s)
timeout 60 find /mnt/mybooklive-vps/RESTIC/snapshots   -maxdepth 1 -type f -printf '.' 2>/tmp/restic-find.err | wc -c
RC=${PIPESTATUS[0]}
END=$(date +%s)
echo "snapshot directory: exit=$RC duration=$((END-START))s"
cat /tmp/restic-find.err
echo
echo '===== 6. ECHTE RESTIC SNAPSHOT-ABFRAGE ====='
START=$(date +%s)
timeout 180 restic   -r /mnt/mybooklive-vps/RESTIC   --password-file /etc/restic/nas-password   snapshots --json   >/tmp/restic-snapshots-diagnose.json   2>/tmp/restic-snapshots-diagnose.err
RC=$?
END=$(date +%s)
echo "restic snapshots: exit=$RC duration=$((END-START))s"
echo "json bytes=$(wc -c </tmp/restic-snapshots-diagnose.json)"
echo 'stderr:'
cat /tmp/restic-snapshots-diagnose.err
echo
echo '===== 7. CIFS-FEHLER WÄHREND DIESES TESTS ====='
journalctl -k --since '5 minutes ago' --no-pager   | grep -Ei 'CIFS|SMB|192\.168\.1\.66' | tail -100
echo
echo '===== 8. RESTIC-PROZESSE DANACH ====='
pgrep -af 'restic.*RESTIC' || echo 'keine Restic-Prozesse'
FILE=/opt/struktur/dashboard/app-vpsverwaltung.py
BACKUP="${FILE}.bak_20260817_restic-timeout-repair"
echo '=== BACKUP ==='
cp -a "$FILE" "$BACKUP"
sha256sum "$FILE" "$BACKUP"
echo
echo '=== PATCH ==='
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

changes = [
    (
        "timeout=30\n        )",
        "timeout=120\n        )"
    ),
    (
        "badge.textContent = 'Repository nicht erreichbar';",
        "badge.textContent = 'Snapshot-Abfrage fehlgeschlagen';"
    ),
    (
        "setInterval(updateResticSnapshots, 30000);",
        "setInterval(updateResticSnapshots, 300000);"
    ),
]

for old, new in changes:
    count = s.count(old)
    if count != 1:
        raise SystemExit(
            f'ABBRUCH: Erwartete Stelle nicht eindeutig gefunden: {old!r} count={count}'
        )
    s = s.replace(old, new, 1)

p.write_text(s)
print('PATCH OK')
PY

echo
echo '=== DIFF ==='
diff -u "$BACKUP" "$FILE" || true
echo
echo '=== PYTHON SYNTAX ==='
python3 -m py_compile "$FILE"
echo "py_compile exit=$?"
echo '=== DASHBOARD SERVICE ==='
systemctl --no-pager --type=service | grep -Ei 'dashboard|vpsverwaltung|struktur'
echo
echo '=== DASHBOARD PROCESS ==='
ps -ef | grep '[a]pp-vpsverwaltung.py'
echo '=== RESTART DASHBOARD ==='
systemctl restart vpsverwaltung-dashboard.service
echo
echo '=== SERVICE STATUS ==='
systemctl --no-pager --full status vpsverwaltung-dashboard.service | head -30
echo
echo '=== LIVE RESTIC ENDPOINT ==='
time curl -sS -D - http://127.0.0.1:5002/api/restic/snapshots -o /tmp/restic-snapshots-test.json
echo
echo '=== RESPONSE BODY ==='
head -c 500 /tmp/restic-snapshots-test.json
echo
echo '=== RESTIC PROCESSES AFTER TEST ==='
ps -eo pid,ppid,etime,state,wchan:35,cmd | grep '[r]estic'
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = "timeout=120\n        )"
new = "timeout=30\n        )"

count = s.count(old)
if count != 1:
    raise SystemExit(f'ABBRUCH: timeout=120 nicht eindeutig gefunden, count={count}')

p.write_text(s.replace(old, new, 1))
print('TIMEOUT ZURÜCK AUF 30 SEKUNDEN')
PY

python3 -m py_compile /opt/struktur/dashboard/app-vpsverwaltung.py
echo "py_compile exit=$?"
echo '=== DASHBOARD POLLING VOR TEST STOPPEN ==='
systemctl stop vpsverwaltung-dashboard.service
echo
echo '=== EINMALIGER RESTIC-LAUF OHNE DASHBOARD-TIMEOUT ==='
START=$(date +%s)
restic   -r /mnt/mybooklive-vps/RESTIC   --password-file /etc/restic/nas-password   snapshots --json   >/tmp/restic-snapshots-manual.json   2>/tmp/restic-snapshots-manual.err
RC=$?
END=$(date +%s)
echo
echo "restic exit=$RC"
echo "duration=$((END-START)) seconds"
echo
echo '=== STDERR ==='
cat /tmp/restic-snapshots-manual.err
echo
echo '=== JSON SIZE ==='
wc -c /tmp/restic-snapshots-manual.json
echo
echo '=== JSON START ==='
head -c 300 /tmp/restic-snapshots-manual.json
echo
time restic   -r /mnt/mybooklive-vps/RESTIC   --password-file /etc/restic/nas-password   snapshots --latest 1 >/dev/null
systemctl start vpsverwaltung-dashboard.service
systemctl --no-pager --full status vpsverwaltung-dashboard.service | head -30
curl -I http://127.0.0.1:5002/
echo '=== 0. DASHBOARD MUSS LAUFEN ==='
systemctl start vpsverwaltung-dashboard.service
systemctl is-active vpsverwaltung-dashboard.service
echo
echo '=== 1. STRACE VERFÜGBAR? ==='
command -v strace || {   echo 'ABBRUCH: strace nicht installiert – nichts installieren.';   exit 1; }
echo
echo '=== 2. LAUFENDEN RESTIC-PROZESS SUCHEN ==='
PID=$(pgrep -f '^restic -r /mnt/mybooklive-vps/RESTIC .*snapshots' | head -1)
if [ -n "$PID" ]; then   echo "Nutze bereits laufenden Restic-Prozess PID=$PID";   MODE=attach; else   echo "Kein Restic-Prozess aktiv – starte EINEN 20-Sekunden-Diagnoselauf.";   MODE=start; fi
rm -f /tmp/restic-io.trace
if [ "$MODE" = attach ]; then   timeout 20 strace     -f -tt -T     -e trace=openat,read,pread64,close,newfstatat,statx,getdents64     -p "$PID"     -o /tmp/restic-io.trace     2>/tmp/restic-strace.err || true; else   timeout 20 strace     -f -tt -T     -e trace=openat,read,pread64,close,newfstatat,statx,getdents64     -o /tmp/restic-io.trace     restic       -r /mnt/mybooklive-vps/RESTIC       --password-file /etc/restic/nas-password       snapshots --json       >/dev/null       2>/tmp/restic-strace.err || true; fi
echo
echo '=== 3. LANGSAME DATEISYSTEM-OPERATIONEN > 50 ms ==='
python3 - <<'PY'
import re

p='/tmp/restic-io.trace'
rows=[]

try:
    for line in open(p, errors='replace'):
        m=re.search(r'<([0-9.]+)>$', line.rstrip())
        if not m:
            continue
        t=float(m.group(1))
        if t >= 0.050:
            rows.append((t,line.rstrip()))
except FileNotFoundError:
    pass

rows.sort(reverse=True)

print("Anzahl Operationen >=50ms:", len(rows))
for t,line in rows[:40]:
    print(f"{t:8.3f}s  {line}")
PY

echo
echo '=== 4. WELCHE REPOSITORY-DATEIEN WERDEN ANGEFASST? ==='
grep -E '/mnt/mybooklive-vps/RESTIC|RESTIC/(snapshots|index|keys|config)'   /tmp/restic-io.trace | tail -60
echo
echo '=== 5. SYSCALL-GESAMTBILD ==='
awk '
/openat\(/     {openat++}
/read\(/       {read++}
/pread64\(/    {pread++}
/close\(/      {close++}
/newfstatat\(/ {stat++}
/statx\(/      {statx++}
/getdents64\(/ {dir++}
END {
 print "openat="openat,
       "read="read,
       "pread64="pread,
       "close="close,
       "newfstatat="stat,
       "statx="statx,
       "getdents64="dir
}' /tmp/restic-io.trace
echo
echo '=== 6. CIFS-FEHLER WÄHREND DER 20 SEKUNDEN ==='
journalctl -k --since '30 seconds ago' --no-pager   | grep -Ei 'CIFS|SMB|192\.168\.1\.66' | tail -50
echo
echo '=== 7. RESTIC-PROZESS JETZT ==='
ps -eo pid,etime,state,wchan:35,cmd | grep '[r]estic.*RESTIC' || echo 'kein Restic-Prozess aktiv'
echo '=== LOCK COUNT ==='
find /mnt/mybooklive-vps/RESTIC/locks -maxdepth 1 -type f | wc -l
echo
echo '=== TMP LOCK COUNT ==='
find /mnt/mybooklive-vps/RESTIC/locks -maxdepth 1 -type f -name '*-tmp-*' | wc -l
echo
echo '=== LOCK AGE DISTRIBUTION ==='
find /mnt/mybooklive-vps/RESTIC/locks -maxdepth 1 -type f -printf '%T@ %f\n' | awk -v now="$(date +%s)" '
{
  age=now-$1
  total++
  if(age<1800) lt30++
  else if(age<86400) d1++
  else dplus++
}
END {
  print "gesamt:",total
  print "<30 Minuten:",lt30+0
  print "30 Min bis 24h:",d1+0
  print ">24h:",dplus+0
}'
set -euo pipefail
DASHBOARD=vpsverwaltung-dashboard.service
REPO=/mnt/mybooklive-vps/RESTIC
PW=/etc/restic/nas-password
trap 'systemctl start "$DASHBOARD" >/dev/null 2>&1 || true' EXIT
echo '=== Dashboard kurz stoppen ==='
systemctl stop "$DASHBOARD"
echo '=== Sicherstellen, dass kein Restic läuft ==='
if pgrep -af 'restic.*RESTIC'; then     echo 'ABBRUCH: Es läuft noch ein Restic-Prozess. Keine Locks verändert.';     exit 1; fi
echo '=== Locks vorher ==='
find "$REPO/locks" -maxdepth 1 -type f | wc -l
echo '=== Verwaiste Restic-Locks entfernen ==='
restic -r "$REPO" --password-file "$PW" unlock
echo '=== Locks danach ==='
find "$REPO/locks" -maxdepth 1 -type f | wc -l
echo '=== Snapshot-Abfrage messen ==='
START=$(date +%s%3N)
restic   -r "$REPO"   --password-file "$PW"   snapshots --json   >/tmp/restic-snapshots-after-unlock.json
RC=$?
END=$(date +%s%3N)
echo "restic exit=$RC"
echo "duration_ms=$((END-START))"
echo "json_bytes=$(wc -c </tmp/restic-snapshots-after-unlock.json)"
echo '=== Dashboard wieder starten ==='
systemctl start "$DASHBOARD"
trap - EXIT
echo '=== Dashboard Status ==='
systemctl is-active "$DASHBOARD"
set -euo pipefail
REPO=/mnt/mybooklive-vps/RESTIC
APP=/opt/struktur/dashboard/app-vpsverwaltung.py
DASHBOARD=vpsverwaltung-dashboard.service
echo '=== Kein Restic darf laufen ==='
if pgrep -af '[r]estic'; then     echo 'ABBRUCH: Restic läuft noch.';     exit 1; fi
echo '=== Verbleibende Locks vorher ==='
find "$REPO/locks" -maxdepth 1 -type f -printf '%f\n'
echo '=== Nur verbliebene TMP-Locks direkt löschen ==='
find "$REPO/locks" -maxdepth 1 -type f -name '*-tmp-*' -delete
echo '=== Locks danach ==='
find "$REPO/locks" -maxdepth 1 -type f | wc -l
echo '=== Dashboard-Timeout auf 30 Sekunden sicherstellen ==='
sed -i 's/timeout=120/timeout=30/' "$APP"
python3 -m py_compile "$APP"
echo '=== Dashboard neu starten ==='
systemctl restart "$DASHBOARD"
echo '=== Live-Test ==='
START=$(date +%s%3N)
curl -sS -o /tmp/restic-live.json   http://127.0.0.1:5002/api/restic/snapshots
echo '=== DASHBOARD DIAGNOSE ==='
systemctl start vpsverwaltung-dashboard.service
for i in {1..10}; do     if ss -ltnp | grep -q ':5002 '; then         echo "Port 5002 ist nach ${i}s erreichbar";         break;     fi;     sleep 1; done
echo
echo '=== SERVICE ==='
systemctl --no-pager --full status vpsverwaltung-dashboard.service | head -35
echo
echo '=== PORT 5002 ==='
ss -ltnp | grep ':5002 ' || true
echo
echo '=== LETZTE LOGS ==='
journalctl -u vpsverwaltung-dashboard.service -n 30 --no-pager
echo
echo '=== RESTIC ENDPOINT ==='
START=$(date +%s%3N)
HTTP=$(curl -sS -o /tmp/restic-live.json -w '%{http_code}' \
    http://127.0.0.1:5002/api/restic/snapshots)
RC=$?
END=$(date +%s%3N)
echo "curl_exit=$RC"
echo "http=$HTTP"
echo "duration_ms=$((END-START))"
if [ "$RC" -eq 0 ]; then
python3 - <<'PY'
import json
d=json.load(open('/tmp/restic-live.json'))
print("ok:", d.get("ok"))
print("snapshots:", len(d.get("snapshots", [])))
print("error:", d.get("error"))
PY
 fi
restic   --no-lock   -r /mnt/mybooklive-vps/RESTIC   --password-file /etc/restic/nas-password   snapshots --json
set -euo pipefail
APP=/opt/struktur/dashboard/app-vpsverwaltung.py
DASHBOARD=vpsverwaltung-dashboard.service
REPO=/mnt/mybooklive-vps/RESTIC
BACKUP="${APP}.bak_20260817_restic-no-lock"
echo '=== Backup ==='
cp -a "$APP" "$BACKUP"
echo '=== Locks vorher ==='
BEFORE=$(find "$REPO/locks" -maxdepth 1 -type f | wc -l)
echo "$BEFORE"
echo '=== Dashboard-Restic-Abfrage auf --no-lock umstellen ==='
python3 - <<'PY'
from pathlib import Path

p = Path('/opt/struktur/dashboard/app-vpsverwaltung.py')
s = p.read_text()

old = """[
                'restic',
                '-r', '/mnt/mybooklive-vps/RESTIC',"""

new = """[
                'restic',
                '--no-lock',
                '-r', '/mnt/mybooklive-vps/RESTIC',"""

count = s.count(old)

if count != 1:
    raise SystemExit(
        f'ABBRUCH: erwarteter Restic-Snapshot-Aufruf nicht eindeutig gefunden, count={count}'
    )

p.write_text(s.replace(old, new, 1))
print('PATCH OK')
PY

echo '=== Diff ==='
diff -u "$BACKUP" "$APP" || true
echo '=== Syntax ==='
python3 -m py_compile "$APP"
echo '=== Dashboard neu starten ==='
systemctl restart "$DASHBOARD"
echo '=== Auf Port 5002 warten ==='
for i in {1..15}; do     if ss -ltn | grep -q ':5002 '; then         break;     fi;     sleep 1; done
systemctl is-active "$DASHBOARD"
echo '=== Dashboard-Snapshot-Abfrage messen ==='
START=$(date +%s%3N)
HTTP=$(curl -sS \
    -o /tmp/restic-dashboard-no-lock.json \
    -w '%{http_code}' \
    http://127.0.0.1:5002/api/restic/snapshots)
END=$(date +%s%3N)
echo "HTTP=$HTTP"
echo "duration_ms=$((END-START))"
python3 - <<'PY'
import json
d=json.load(open('/tmp/restic-dashboard-no-lock.json'))
print("ok:", d.get("ok"))
print("snapshots:", len(d.get("snapshots", [])))
print("error:", d.get("error"))
PY

echo '=== Locks danach ==='
AFTER=$(find "$REPO/locks" -maxdepth 1 -type f | wc -l)
echo "$AFTER"
echo '=== Ergebnis ==='
echo "locks_before=$BEFORE"
echo "locks_after=$AFTER"
if [ "$BEFORE" = "$AFTER" ]; then     echo 'OK: Dashboard-Abfrage hat keine neuen Restic-Locks erzeugt.'; else     echo 'ACHTUNG: Lock-Anzahl hat sich verändert.'; fi
docker inspect buzz-prod-relay-1   --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}'
cd /opt/struktur/buzz/deploy/compose
grep -RniE 'communities\.buzz\.xyz|relay|public.*url|base.*url|domain|host'   . --exclude-dir=.git 2>/dev/null | head -n 80
cd /opt/struktur/buzz/deploy/compose
grep -E '^(BUZZ_DOMAIN|RELAY_URL|BUZZ_MEDIA_BASE_URL|BUZZ_MEDIA_SERVER_DOMAIN)=' .env
docker inspect buzz-prod-relay-1   --format '{{range .Config.Env}}{{println .}}{{end}}'   | grep -E '^(BUZZ_DOMAIN|RELAY_URL|BUZZ_MEDIA_BASE_URL|BUZZ_MEDIA_SERVER_DOMAIN)='
grep -RIlE 'buzz|nostr|nsec'   /home/hermes/.hermes/profiles/hermesvps   /home/hermes/.hermes/hermes-agent 2>/dev/null   | head -n 50
find /home/hermes/.hermes/profiles/hermesvps -maxdepth 2 -type f   \( -name '.env' -o -name '*.json' -o -name '*.yaml' -o -name '*.yml' -o -name '*.toml' \)   -printf '%p\n' | sort
grep -hEi 'buzz|nostr|nsec'   /home/hermes/.hermes/profiles/hermesvps/.env   /home/hermes/.hermes/profiles/hermesvps/config.yaml   /home/hermes/.hermes/profiles/hermesvps/profile.yaml   /home/hermes/.hermes/profiles/hermesvps/gateway_state.json 2>/dev/null | sed -E 's/(:|=).*/\1 [REDACTED]/'
docker exec buzz-prod-relay-1 buzz-admin --help
docker exec buzz-prod-relay-1 buzz-admin generate-key --help
umask 077
docker exec buzz-prod-relay-1 buzz-admin generate-key > /root/hermes-buzz-keypair.txt
chmod 600 /root/hermes-buzz-keypair.txt
ls -l /root/hermes-buzz-keypair.txt
grep -Ei 'public|pubkey|npub' /root/hermes-buzz-keypair.txt
PUBKEY="$(grep -Ei 'public|pubkey|npub' /root/hermes-buzz-keypair.txt | head -n1 | sed -E 's/.*[:=][[:space:]]*//')"
docker exec buzz-prod-relay-1 buzz-admin add-member --pubkey "$PUBKEY"
grep -Ei 'private|nsec' /root/hermes-buzz-keypair.txt
sed -E   -e 's/nsec1[0-9a-z]+/[REDACTED_NSEC]/g'   -e 's/\b[0-9a-fA-F]{64}\b/[REDACTED_HEX]/g'   /root/hermes-buzz-keypair.txt
grep '^Secret key:' /root/hermes-buzz-keypair.txt
docker exec buzz-prod-relay-1 buzz-admin list-members
hermes --version
hermes gateway status
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
ps aux | grep -i '[h]ermes'
find /opt /var/lib /root -maxdepth 3 \( -iname '*hermes*' -o -iname 'hermes' \) 2>/dev/null | sort
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps gateway --help
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps gateway status
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps gateway setup --help
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps gateway setup
for f in /home/hermes/.hermes/.env /home/hermes/.hermes/profiles/hermesvps/.env; do   [ -f "$f" ] || continue;   echo "=== $f ===";   grep -E '^BUZZ_' "$f" | sed -E 's/(PRIVATE_KEY|SECRET|TOKEN)=.*/\1=[REDACTED]/'; done
for f in /home/hermes/.hermes/.env /home/hermes/.hermes/profiles/hermesvps/.env; do   [ -f "$f" ] || continue;   echo "=== $f ===";   grep -E '^BUZZ_' "$f"     | sed -E       -e 's/^(BUZZ_PRIVATE_KEY|BUZZ_SECRET|BUZZ_TOKEN)=.*/\1=[REDACTED]/'       -e 's/^(BUZZ_ALLOWED_USERS)=.*/\1=[REDACTED]/'; done
cp -a /home/hermes/.hermes/profiles/hermesvps/.env   /home/hermes/.hermes/profiles/hermesvps/.env.before-buzz-20260818
ls -l /home/hermes/.hermes/profiles/hermesvps/.env*
sudo systemctl restart hermes-gateway-hermesvps
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 17:06:30"   --no-pager -l
find /usr/local/bin /usr/bin /opt /home/hermes -type f -name 'buzz' -perm -111 2>/dev/null
find /opt/struktur/buzz -maxdepth 3 -type f   \( -name 'Cargo.toml' -o -name 'Cargo.lock' \)   -printf '%p\n'
cd /opt/struktur/buzz
git rev-parse --short HEAD
git status --short
grep -nE '^(name|version)\s*=' crates/buzz-cli/Cargo.toml
cargo --version
cd /opt/struktur/buzz
cargo metadata --no-deps --format-version 1   | python3 -c 'import sys,json; d=json.load(sys.stdin); print([(p["name"], p["version"]) for p in d["packages"] if p["name"]=="buzz-cli"])'
cd /opt/struktur/buzz
grep -RniE 'buzz-cli|cargo build --release -p buzz-cli|releases|install.*buzz'   README.md docs crates/buzz-cli 2>/dev/null | head -n 80
find /opt /usr /home -type f   \( -name 'buzz' -o -name 'buzz-*' \)   -perm -111 2>/dev/null | head -n 100
cd /opt/struktur/buzz
sed -n '112,160p' README.md
hermit --version
uname -m
docker images --format '{{.Repository}}:{{.Tag}}' | grep -E '^rust:' || true
cd /opt/struktur/buzz
find . -maxdepth 2 -type f   \( -name 'rust-toolchain' -o -name 'rust-toolchain.toml' -o -name 'hermit.hcl' \)   -print -exec sed -n '1,120p' {} \;
docker pull rust:1.95.0
docker run --rm   -v /opt/struktur/buzz:/src   -w /src   rust:1.95.0   cargo metadata --no-deps --format-version 1   | python3 -c 'import sys,json; d=json.load(sys.stdin); print([(p["name"], p["version"]) for p in d["packages"] if p["name"]=="buzz-cli"])'
cd /opt/struktur/buzz
docker run --rm   -v /opt/struktur/buzz:/src   -w /src   rust:1.95.0   cargo build --release -p buzz-cli
ls -lh /opt/struktur/buzz/target/release/buzz
/opt/struktur/buzz/target/release/buzz --help | head -n 40
grep -RniE 'BUZZ_CLI_PATH|which.*buzz|bin/buzz|buzz CLI binary'   /home/hermes/.hermes/hermes-agent 2>/dev/null | head -n 80
install -d -o hermes -g hermes -m 755 /home/hermes/bin
install -o hermes -g hermes -m 755   /opt/struktur/buzz/target/release/buzz   /home/hermes/bin/buzz
ls -lh /home/hermes/bin/buzz
sudo -u hermes bash -lc 'command -v buzz && buzz --help | head -n 5'
sudo systemctl restart hermes-gateway-hermesvps
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
sudo -u hermes bash -lc 'buzz users set-profile --help'
sudo -u hermes bash -lc '
set -a
source /home/hermes/.hermes/profiles/hermesvps/.env
set +a
buzz users set-profile \
  --name "Hermes" \
  --about "Hermes Agent"
'
sudo systemctl restart hermes-gateway-hermesvps
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
sudo journalctl -u hermes-gateway-hermesvps   -n 40   --no-pager -l
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 17:23:29"   --no-pager -l
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
sudo -u hermes bash -lc '
set -a
source /home/hermes/.hermes/profiles/hermesvps/.env
set +a
buzz users get
'
sudo systemctl restart hermes-gateway-hermesvps
sudo journalctl -u hermes-gateway-hermesvps   --since "1 minute ago"   --no-pager -l | grep -iE 'buzz|connected|failed|error|warning'
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 17:26:21"   --no-pager -l   | grep -iE 'buzz|platform|connected|retry|adapter'
grep -RniE 'buzz.*connected|connected.*buzz|✓ buzz|Buzz:.*connect'   /home/hermes/.hermes/hermes-agent/gateway   /home/hermes/.hermes/hermes-agent/plugins/platforms/buzz 2>/dev/null | head -n 30
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 17:26:21"   --no-pager -l   | grep -F 'Buzz: connected to'
sed -n '520,575p'   /home/hermes/.hermes/hermes-agent/plugins/platforms/buzz/adapter.py
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 17:26:21"   --no-pager -l   | grep -F 'Buzz: no channels to watch'
sudo -u hermes bash -lc '
set -a
source /home/hermes/.hermes/profiles/hermesvps/.env
set +a
buzz channels list
'
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps gateway status
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 17:33:00"   --no-pager -l   | grep -iE 'sethome|home channel|buzz|error|warning'
grep -Rni -A25 -B10 'sethome'   /home/hermes/.hermes/hermes-agent/plugins/platforms/buzz/adapter.py
grep -RniE 'BUZZ_HOME|HOME_CHANNEL|home_channel'   /home/hermes/.hermes/hermes-agent   --exclude-dir=.git   --exclude='*.pyc'   2>/dev/null | head -n 50
grep -Rni 'BUZZ_HOME_CHANNEL'   /home/hermes/.hermes/hermes-agent   --exclude-dir=.git   --exclude='*.pyc'   2>/dev/null
grep -q '^BUZZ_HOME_CHANNEL=' /home/hermes/.hermes/profiles/hermesvps/.env   && sed -i 's|^BUZZ_HOME_CHANNEL=.*|BUZZ_HOME_CHANNEL=60c3bfc2-a1ec-5a13-aa77-bcac5ad2dd85|' /home/hermes/.hermes/profiles/hermesvps/.env   || echo 'BUZZ_HOME_CHANNEL=60c3bfc2-a1ec-5a13-aa77-bcac5ad2dd85' >> /home/hermes/.hermes/profiles/hermesvps/.env
grep '^BUZZ_HOME_CHANNEL=' /home/hermes/.hermes/profiles/hermesvps/.env
sudo systemctl restart hermes-gateway-hermesvps
sudo journalctl -u hermes-gateway-hermesvps   --since "1 minute ago"   --no-pager -l   | grep -iE 'Buzz: connected|buzz|error|warning'
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 17:39:12"   --no-pager -l
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
sed -n '1368,1405p'   /home/hermes/.hermes/hermes-agent/plugins/platforms/buzz/adapter.py
sudo -u hermes bash -lc '
set -a
source /home/hermes/.hermes/profiles/hermesvps/.env
set +a
printf "%s" "HOME-CHANNEL-TEST" | buzz messages send \
  --channel "$BUZZ_HOME_CHANNEL" \
  --content -
'
echo "=== BUZZ CLI ==="
sha256sum /home/hermes/bin/buzz
echo
echo "=== HERMES ENV ==="
ls -l   /home/hermes/.hermes/profiles/hermesvps/.env   /home/hermes/.hermes/profiles/hermesvps/.env.before-buzz-20260818
echo
echo "=== BUZZ CONFIG ==="
grep -E '^(BUZZ_RELAY_URL|BUZZ_HOME_CHANNEL|BUZZ_ALLOW_ALL_USERS)='   /home/hermes/.hermes/profiles/hermesvps/.env
echo
echo "=== GATEWAY ==="
systemctl is-active hermes-gateway-hermesvps
systemctl is-enabled hermes-gateway-hermesvps
cp -a   /home/hermes/.hermes/profiles/hermesvps/.env   /home/hermes/.hermes/profiles/hermesvps/.env.after-buzz-20260818
ls -l /home/hermes/.hermes/profiles/hermesvps/.env.after-buzz-20260818
for f in   /home/hermes/.hermes/profiles/hermesvps/.env   /home/hermes/.hermes/profiles/hermesvps/.env.after-buzz-20260818; do   printf '%s: ' "$f";   grep -qE '^BUZZ_PRIVATE_KEY=.+$' "$f" && echo "KEY VORHANDEN" || echo "KEY FEHLT"; done
shred -u /root/hermes-buzz-keypair.txt
test ! -e /root/hermes-buzz-keypair.txt && echo "Schlüsseldatei entfernt"
grep -nE 'mention|mentioned|trigger|allowed_users|allow_all|author|sender'   /home/hermes/.hermes/hermes-agent/plugins/platforms/buzz/adapter.py   | head -n 80
grep -q '^BUZZ_REQUIRE_MENTION=' /home/hermes/.hermes/profiles/hermesvps/.env   && sed -i 's/^BUZZ_REQUIRE_MENTION=.*/BUZZ_REQUIRE_MENTION=false/' /home/hermes/.hermes/profiles/hermesvps/.env   || echo 'BUZZ_REQUIRE_MENTION=false' >> /home/hermes/.hermes/profiles/hermesvps/.env
grep '^BUZZ_REQUIRE_MENTION=' /home/hermes/.hermes/profiles/hermesvps/.env
sudo systemctl restart hermes-gateway-hermesvps
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
cd /opt/struktur/buzz
grep -RniE 'require_mention|mention.*required|agent.*mention|trigger.*mention|listen.*channel|respond.*channel'   crates   --exclude-dir=target   2>/dev/null | head -n 80
/home/hermes/bin/buzz agents --help
/home/hermes/bin/buzz agents draft-update --help
grep -RniE 'all_messages|triggers:|respond_to|persona'   /opt/struktur/buzz   --exclude-dir=target   2>/dev/null | grep -Ei 'agent|runtime|persona|config' | head -n 80
grep -RniE 'team.*mention|mention.*team|teamPersonas|create.*team|agent team'   /opt/struktur/buzz/desktop/src/features/agents   /opt/struktur/buzz/desktop/src/features/messages   2>/dev/null | head -n 60
sed -n '110,155p'   /opt/struktur/buzz/desktop/src/features/messages/lib/mentionCandidates.ts
sed -n '110,155p'   /opt/struktur/buzz/desktop/src/features/messages/lib/mentionCandidates.ts
sed -i 's/^BUZZ_REQUIRE_MENTION=.*/BUZZ_REQUIRE_MENTION=true/'   /home/hermes/.hermes/profiles/hermesvps/.env
grep -E '^(BUZZ_REQUIRE_MENTION|BUZZ_ALLOW_ALL_USERS)='   /home/hermes/.hermes/profiles/hermesvps/.env
sudo systemctl restart hermes-gateway-hermesvps
sudo systemctl status hermes-gateway-hermesvps --no-pager -l
TEST-1-OHNE-MENTION
Hermes soll auf diese Nachricht NICHT antworten.
grep -RniE 'conversation_loop|empty response|gateway.*model|messaging.*model|response.*content'   /home/hermes/.hermes/hermes-agent/gateway   /home/hermes/.hermes/hermes-agent/agent   2>/dev/null | head -n 80
sed -n '19750,19820p;26070,26120p'   /home/hermes/.hermes/hermes-agent/gateway/run.py
grep -n -A80 -B10 'def _normalize_empty_agent_response'   /home/hermes/.hermes/hermes-agent/gateway/run.py
sed -n '3830,3865p'   /home/hermes/.hermes/hermes-agent/gateway/run.py
sed -n '3865,3915p'   /home/hermes/.hermes/hermes-agent/gateway/run.py
grep -Rni -A50 -B20   'Empty response (no content or reasoning)'   /home/hermes/.hermes/hermes-agent/agent   2>/dev/null
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 19:31:00"   --no-pager -l   | grep -iE 'approve|reset|new session|session|confirm|error|warning'
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 19:33:00"   --no-pager -l   | grep -iE 'approve|new session|reset|confirmation|session|error|warning'
sed -n '1028,1060p'   /home/hermes/.hermes/hermes-agent/plugins/platforms/buzz/adapter.py
grep -RniE 'session_id|session_key|buzz'   /home/hermes/.hermes/profiles/hermesvps   2>/dev/null | tail -n 80
find /home/hermes/.hermes/profiles/hermesvps   -maxdepth 3   \( -type d -o -type f \)   \( -iname '*session*' -o -iname '*history*' \)   -printf '%p\n' 2>/dev/null | head -n 80
python3 - <<'PY'
import json
p="/home/hermes/.hermes/profiles/hermesvps/sessions/sessions.json"
with open(p) as f:
    d=json.load(f)

print(type(d).__name__)
if isinstance(d, dict):
    items=list(d.items())
    for k,v in items[-20:]:
        print("KEY:", k)
        print("VAL:", v)
        print("---")
elif isinstance(d, list):
    for v in d[-20:]:
        print(v)
        print("---")
PY

sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps sessions list
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps sessions --help
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps sessions delete --help
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps sessions delete   20260818_173236_4ec80f65 --yes
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps sessions list
sudo journalctl -u hermes-gateway-hermesvps   --since "2026-08-18 19:18:00"   --no-pager -l   | grep -iE 'empty response|model|provider|fallback|retry|error'
grep -nEi 'gpt-5\.6-luna|openai-codex|MODEL|PROVIDER'   /home/hermes/.hermes/profiles/hermesvps/.env   /home/hermes/.hermes/profiles/hermesvps/config.yaml   2>/dev/null   | sed -E 's/(KEY|TOKEN|SECRET|PASSWORD)=.*/\1=[REDACTED]/I'
sed -n '1,15p;530,565p;600,630p'   /home/hermes/.hermes/profiles/hermesvps/config.yaml
sudo -u hermes /home/hermes/.hermes/hermes-agent/venv/bin/python   -m hermes_cli.main --profile hermesvps chat
sudo systemctl restart hermes-dashboard.service
taskkill /PID 18064 /F
ssh -i "$HOME\.ssh\vps_key" root@31.70.68.228
exit
command -v hermes
systemctl cat hermes-dashboard.service | grep -E 'ExecStart|WorkingDirectory'
/home/hermes/.local/bin/hermes profile list
ls -la /home/hermes/.hermes/profiles/
sudo -u hermes -H /home/hermes/.local/bin/hermes profile list
ps -ef | grep '[h]ermes'
mkdir -p /home/hermes/.ssh
chmod 700 /home/hermes/.ssh
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOX038Fd+jSuXwGZQxy+dMCmAvtho2PGH6m/f+sLwEK2 karlo@i5-9400' >> /home/hermes/.ssh/authorized_keys
chmod 600 /home/hermes/.ssh/authorized_keys
chown -R hermes:hermes /home/hermes/.ssh
taskkill /PID 12000 /PID 16788 /F
hermes model
systemctl cat hermes-dashboard.service
sudo -u hermes -H /home/hermes/.local/bin/hermes -p hermesvps model
sqlite3 /opt/struktur/youtube-research/knowledge.db ".schema graphiti_import_queue"
id hermes
sudo -l -U hermes
grep -R "8765" /etc/nginx/sites-available/ /etc/nginx/sites-enabled/ 2>/dev/null
grep -n -B5 -A12 "8765" /etc/nginx/sites-available/social-media-agent
systemctl status social-media-agent.service --no-pager
systemctl cat social-media-agent.service
grep -Rni "cockpit\|Version\|version" /opt/struktur/social-media-agent/templates /opt/struktur/social-media-agent/app.py 2>/dev/null | head -80
find /opt/struktur/social-media-agent -maxdepth 2 -type f \( -name "VERSION" -o -name "version.txt" -o -name "pyproject.toml" -o -name "package.json" \) -print
grep -Rni --exclude='*.bak*' "14\.0" /opt/struktur/social-media-agent 2>/dev/null | head -50
cd /opt/struktur/social-media-agent
git rev-parse --is-inside-work-tree 2>/dev/null && git describe --tags --always --dirty 2>/dev/null
mkdir -p /home/hermes/Antworten/testdata
cp /var/lib/sma-data/signals.db /home/hermes/Antworten/testdata/signals.db
cp /opt/struktur/social-media-agent/cases.db /home/hermes/Antworten/testdata/cases.db
chown -R hermes:hermes /home/hermes/Antworten/testdata
chmod 600 /home/hermes/Antworten/testdata/signals.db /home/hermes/Antworten/testdata/cases.db
ls -lah /home/hermes/Antworten/testdata/
echo 'hermes ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/hermes
chmod 440 /etc/sudoers.d/hermes
visudo -cf /etc/sudoers.d/hermes
sudo -l -U hermes
ssh -L 5900:localhost:5900 hermes@31.70.68.228
read -rsp "Supadata API-Key: " SUPADATA_API_KEY; echo; install -m 600 /dev/null /opt/struktur/youtube-research/.env.supadata && printf 'SUPADATA_API_KEY=%s\n' "$SUPADATA_API_KEY" > /opt/struktur/youtube-research/.env.supadata; unset SUPADATA_API_KEY
stat -c '%a %U:%G %n' /opt/struktur/youtube-research/.env.supadata && test -s /opt/struktur/youtube-research/.env.supadata && echo "Key-Datei vorhanden"
set -a
source /opt/struktur/youtube-research/.env.supadata
set +a
HTTP=$(curl -sS -o /tmp/supadata_test.json -w '%{http_code}' \
  -G 'https://api.supadata.ai/v1/transcript' \
  --data-urlencode 'url=https://www.youtube.com/watch?v=Rl-G5GKrql8' \
  --data-urlencode 'mode=native' \
  --data-urlencode 'lang=de' \
  --data-urlencode 'text=false' \
  -H "x-api-key: $SUPADATA_API_KEY")
unset SUPADATA_API_KEY
echo "HTTP: $HTTP"
python3 - <<'PY'
import json
p="/tmp/supadata_test.json"
d=json.load(open(p, encoding="utf-8"))

print("lang:", d.get("lang"))
print("availableLangs:", d.get("availableLangs"))

c=d.get("content")
if isinstance(c, list):
    print("segments:", len(c))
    print("text_chars:", sum(len(str(x.get("text",""))) for x in c))
elif isinstance(c, str):
    print("text_chars:", len(c))
else:
    print("content:", c)

print("jobId:", d.get("jobId"))
print("error:", d.get("error") or d.get("message"))
PY

set -a
source /opt/struktur/youtube-research/.env.supadata
set +a
for VID in   wmORMDVRXjg   mHj_MgTeJ-M   2x_-YgGMoZk   S7yg98I6L7k; do   OUT="/tmp/supadata_${VID}.json";    HTTP=$(curl -sS -o "$OUT" -w '%{http_code}' \
    -G 'https://api.supadata.ai/v1/transcript' \
    --data-urlencode "url=https://www.youtube.com/watch?v=${VID}" \
    --data-urlencode 'mode=native' \
    --data-urlencode 'lang=de' \
    --data-urlencode 'text=false' \
    -H "x-api-key: $SUPADATA_API_KEY");    echo "=== $VID ===";   echo "HTTP: $HTTP"; 
  python3 - "$OUT" <<'PY'
import json, sys
d=json.load(open(sys.argv[1], encoding="utf-8"))
c=d.get("content")

print("lang:", d.get("lang"))
if isinstance(c, list):
    print("segments:", len(c))
    print("text_chars:", sum(len(str(x.get("text",""))) for x in c))
else:
    print("content_type:", type(c).__name__)

print("error:", d.get("error") or d.get("message"))
PY
    echo; done
unset SUPADATA_API_KEY
cp -p /opt/struktur/youtube-research/.env.supadata       /opt/struktur/youtube-research/.env.supadata.account1
read -rsp "Neuer Supadata API-Key: " NEWKEY
echo
install -m 600 /dev/null /opt/struktur/youtube-research/.env.supadata
printf 'SUPADATA_API_KEY=%s\n' "$NEWKEY"   > /opt/struktur/youtube-research/.env.supadata
unset NEWKEY
stat -c '%a %U:%G %n'   /opt/struktur/youtube-research/.env.supadata.account1   /opt/struktur/youtube-research/.env.supadata
set -a
source /opt/struktur/youtube-research/.env.supadata
set +a
curl -sS   -H "x-api-key: $SUPADATA_API_KEY"   https://api.supadata.ai/v1/me | python3 -m json.tool
unset SUPADATA_API_KEY
set -e
ACTIVE="/opt/struktur/youtube-research/.env.supadata"
BACKUP="/opt/struktur/youtube-research/.env.supadata.account2"
if [ -e "$BACKUP" ]; then   echo "STOPP: $BACKUP existiert bereits.";   exit 1; fi
cp -p "$ACTIVE" "$BACKUP"
read -rsp "Neuer Supadata API-Key: " NEWKEY
echo
install -m 600 /dev/null "$ACTIVE"
printf 'SUPADATA_API_KEY=%s\n' "$NEWKEY" > "$ACTIVE"
unset NEWKEY
echo "Neuer Key gespeichert."
stat -c '%a %U:%G %n' "$BACKUP" "$ACTIVE"
set -a
source /opt/struktur/youtube-research/.env.supadata
set +a
curl -sS   -H "x-api-key: $SUPADATA_API_KEY"   https://api.supadata.ai/v1/me | python3 -m json.tool
unset SUPADATA_API_KEY
ssh vps "grep -RInF --include='*.html' --include='*.js' --include='*.py' --include='*.json' 'http://localhost:5099/start' /home/hermes /opt/struktur /var/www /etc/nginx 2>/dev/null"
grep -RInF   --include='*.html'   --include='*.js'   --include='*.py'   --include='*.json'   'http://localhost:5099/start'   /home/hermes /opt/struktur /var/www /etc/nginx 2>/dev/null
grep -n -A25 -B5 'launchLocalHermes' /opt/struktur/portal/app.py
cp /opt/struktur/portal/app.py /opt/struktur/portal/app.py.bak-before-hermes-protocol
python3 - <<'PY'
from pathlib import Path

p = Path("/opt/struktur/portal/app.py")
s = p.read_text()

old = '<a class="card-action-btn" href="http://localhost:5099/start" onclick="event.preventDefault(); launchLocalHermes(this.href);">Desktop</a>'
new = '<a class="card-action-btn" href="hermes-scout://start">Desktop</a>'

if old not in s:
    raise SystemExit("STOPP: erwartete alte Desktop-Zeile nicht gefunden")

s = s.replace(old, new, 1)
p.write_text(s)

print("Desktop-Link geändert.")
PY

grep -n 'card-action-btn.*Desktop' /opt/struktur/portal/app.py
echo "=== SYSTEMD ==="
grep -RInE '/opt/struktur/portal|portal/app\.py|127\.0\.0\.1:5000'   /etc/systemd/system /lib/systemd/system 2>/dev/null
echo
echo "=== LAUFENDER PROZESS ==="
ps -ef | grep -E '/opt/struktur/portal|portal/app\.py|gunicorn' | grep -v grep
systemctl restart portal.service
echo "=== STATUS ==="
systemctl is-active portal.service
echo "=== AUSGELIEFERTER DESKTOP-LINK ==="
curl -s http://127.0.0.1:5000/ | grep -o 'href="[^"]*"[^>]*>Desktop</a>'
curl -sS -D /tmp/portal-headers.txt http://127.0.0.1:5000/ -o /tmp/portal-body.html
echo "=== HEADERS ==="
cat /tmp/portal-headers.txt
echo
echo "=== DESKTOP/HERMES-TREFFER ==="
grep -nE 'Desktop|hermes-scout|localhost:5099' /tmp/portal-body.html
