einstellungen: Zeitfenster, Radar-Schalter, Telegram-Test, Software-Stand und Altreste

Einstellungen, die etwas einstellen (services/einstellungen.py, /api/einstellungen):
- Update-Zeitfenster: Wochentag und Uhrzeit des Update-Laufs als Drop-in
  mc2-autoupdate.timer.d/zeitfenster.conf. Gegen die Nachhol-Falle von
  Persistent=true (24.09.): Timer vor dem daemon-reload stoppen, Stempel auf
  jetzt, dann starten. Bei Fehler zurück auf das alte Drop-in. Während eines
  Update-Laufs abgelehnt.
- Neustart nur im eingestellten Fenster (deploy/wartungsfenster.sh, drei
  Stunden ab der vollen Stunde des Beginns; Standard So 04:00-06:59).
- Radar an/aus: disable --now bzw. enable + Stempel + start (kein Nachholen).
  deploy.sh respektiert den Schalter und startet den Radar-Timer nur mit
  Stempel; das Zeitfenster-Drop-in fasst er nie an.
- Wächter: schläft der Timer eines Timer-Dienstes, ist ein alter Fehlschlag
  kein Befund mehr (der Dienst selbst ist static und schläft nie).
- Telegram-Test je Instanz (/api/einstellungen/telegram-test und
  /api/homelab/einstellungen/telegram-test) über notify.sh -d; Ergebnis und
  Grund aus dem Melde-Log. notify.sh schreibt jetzt auch den Grund des
  gescheiterten Zweitwegs in den FALLBACK-Eintrag.
- Einstellungsdatei mc2-einstellungen.json im Datenordner.

Software-Stand: deploy.sh und homelab/ausrollen.sh schreiben nach dem
Umschalten mc2-stand.json (deploy/stand-schreiben.py); /api/instanz liefert
den Stand, die Einstellungen zeigen beide Instanzen und warnen bei Abweichung.
Ohne Datei aus git, sonst unbekannt.

Aufräumen: Altreste neben dem Betrieb aus einer festen, auf der Box geprüften
Liste (Größe, Hinweis, nur was da und nicht in Gebrauch ist). Löschen nur per
Kennung und Klick mit Rückfrage; Worktrees mit git worktree prune, Alt-Units
mit daemon-reload, /opt/llamacpp notfalls mit sudo -n.

Tests: pytest mit einem systemd-Abbild samt Nachhol-Regel, echtem notify.sh
(Ersatz-Hermes, Tmp-Zugang, lokale Bot-API), Shell-Funktionen des
Wartungsfensters und von deploy.sh; vitest für die Rückfragen der Schublade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-24 20:37:30 +02:00
co-authored by Claude Opus 5.5
parent 40d2840b16
commit 3d8df1c6fd
24 changed files with 2640 additions and 85 deletions
+58
View File
@@ -0,0 +1,58 @@
"""stand-schreiben.py — den Software-Stand einer Instanz festhalten (seit 24.09.2026).
deploy.sh (KI-Box) und homelab/ausrollen.sh (Container des Homelab-Teils) rufen es nach einem erfolgreichen
Umschalten auf. Es liest drei Zeilen von stdin, genau wie `git log -1 --format='%h%n%cI%n%s'` sie liefert:
Commit kurz, Commit-Datum (ISO), Betreff. Dazu kommt der Zeitpunkt des Deploys, und alles landet als JSON in der
angegebenen Datei (atomar). Die Oberfläche zeigt daraus „Software-Stand“ (backend/services/software_stand.py).
Nur Standardbibliothek: Es läuft mit dem python3 des Systems, auch im Container ohne git.
Nutzung: git log -1 --format='%h%n%cI%n%s' HEAD | python3 deploy/stand-schreiben.py /srv/models/mc2-stand.json
"""
import json
import os
import sys
import tempfile
from datetime import datetime
from pathlib import Path
def stand_aus(zeilen: list[str], jetzt: datetime) -> dict:
"""Die drei git-Zeilen und der Zeitpunkt des Deploys als Stand. Leere Felder werden None."""
commit, datum, betreff = ([z.strip() for z in zeilen] + ["", "", ""])[:3]
return {"commit": commit or None, "betreff": betreff or None, "commit_datum": datum or None,
"deploy_zeit": jetzt.isoformat(timespec="seconds")}
def schreiben(ziel: Path, stand: dict) -> None:
"""Atomar: erst eine Nachbardatei, dann umbenennen — ein Leser sieht nie eine halbe Datei."""
ziel.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(prefix=".mc2-stand-", suffix=".tmp", dir=ziel.parent)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(stand, f, ensure_ascii=False, indent=1)
os.chmod(tmp, 0o644)
os.replace(tmp, ziel)
except BaseException:
Path(tmp).unlink(missing_ok=True)
raise
def main(argv: list[str]) -> int:
if len(argv) != 2:
print("Nutzung: git log -1 --format='%h%n%cI%n%s' | python3 stand-schreiben.py <datei>", file=sys.stderr)
return 2
# Bytes lesen und selbst als UTF-8 deuten: Im Container ist die Locale oft C/POSIX.
zeilen = sys.stdin.buffer.read().decode("utf-8", "replace").splitlines()
stand = stand_aus(zeilen, datetime.now().astimezone())
if not stand["commit"]:
print("Kein Commit auf stdin — Stand nicht geschrieben.", file=sys.stderr)
return 1
schreiben(Path(argv[1]), stand)
print(f"Stand {stand['commit']} festgehalten in {argv[1]}.")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))