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>
88 lines
4.1 KiB
Python
88 lines
4.1 KiB
Python
"""Software-Stand (24.09.2026): welcher Commit seit wann live ist — Stand-Datei, git-Rückfall, /api/instanz,
|
|
und wie deploy.sh und ausrollen.sh die Datei schreiben (deploy/stand-schreiben.py)."""
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
from routers import partner as partner_router
|
|
from services import software_stand
|
|
|
|
DEPLOY = Path(__file__).resolve().parents[2] / "deploy"
|
|
|
|
|
|
@pytest.fixture
|
|
def stand_datei(tmp_path, monkeypatch):
|
|
datei = tmp_path / "mc2-stand.json"
|
|
monkeypatch.setenv("MC_STAND_DATEI", str(datei))
|
|
monkeypatch.setitem(software_stand._git_cache, "ts", 0.0)
|
|
return datei
|
|
|
|
|
|
def _schreiben(ziel: Path, eingabe: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run([sys.executable, str(DEPLOY / "stand-schreiben.py"), str(ziel)],
|
|
input=eingabe.encode("utf-8"), capture_output=True, timeout=60)
|
|
|
|
|
|
def test_stand_schreiben_wie_der_deploy(stand_datei):
|
|
lauf = _schreiben(stand_datei, "40d2840\n2026-09-24T20:46:03+02:00\ndoku: Phase 4 ehrlich, Rückweg über Nacht\n")
|
|
assert lauf.returncode == 0, lauf.stderr
|
|
daten = json.loads(stand_datei.read_text(encoding="utf-8"))
|
|
assert daten["commit"] == "40d2840" and daten["commit_datum"] == "2026-09-24T20:46:03+02:00"
|
|
assert daten["betreff"] == "doku: Phase 4 ehrlich, Rückweg über Nacht"
|
|
assert re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d[+-]\d\d:\d\d", daten["deploy_zeit"])
|
|
assert not list(stand_datei.parent.glob(".mc2-stand-*")) # keine Reste der atomaren Schreibweise
|
|
assert software_stand.stand() == {"quelle": "deploy", **daten}
|
|
|
|
|
|
def test_stand_schreiben_ohne_commit_schreibt_nichts(stand_datei):
|
|
assert _schreiben(stand_datei, "").returncode == 1
|
|
assert not stand_datei.exists()
|
|
|
|
|
|
def test_ohne_datei_aus_git(stand_datei, monkeypatch):
|
|
monkeypatch.setattr(software_stand, "_git_log", lambda: "abc1234\n2026-09-24T19:00:00+02:00\nhomelab: Ausführer\n")
|
|
assert software_stand.stand() == {"quelle": "git", "commit": "abc1234", "betreff": "homelab: Ausführer",
|
|
"commit_datum": "2026-09-24T19:00:00+02:00", "deploy_zeit": None}
|
|
|
|
|
|
@pytest.mark.parametrize("datei_inhalt", [None, "kaputt", '{"commit": ""}', "[1, 2]"])
|
|
def test_ohne_datei_und_ohne_git_unbekannt(stand_datei, monkeypatch, datei_inhalt):
|
|
if datei_inhalt is not None:
|
|
stand_datei.write_text(datei_inhalt, encoding="utf-8")
|
|
monkeypatch.setattr(software_stand, "_git_log", lambda: None)
|
|
assert software_stand.stand() == {"quelle": "unbekannt", "commit": None, "betreff": None,
|
|
"commit_datum": None, "deploy_zeit": None}
|
|
|
|
|
|
def test_git_wird_nicht_bei_jeder_anfrage_gefragt(stand_datei, monkeypatch):
|
|
aufrufe = []
|
|
monkeypatch.setattr(software_stand, "_git_log", lambda: aufrufe.append(1) or "abc\n\nx\n")
|
|
software_stand.stand()
|
|
software_stand.stand()
|
|
assert len(aufrufe) == 1
|
|
|
|
|
|
def test_instanz_liefert_den_stand(monkeypatch):
|
|
monkeypatch.setattr(software_stand, "stand", lambda: {"quelle": "deploy", "commit": "40d2840"})
|
|
app = FastAPI()
|
|
app.include_router(partner_router.router)
|
|
assert TestClient(app).get("/api/instanz").json()["stand"] == {"quelle": "deploy", "commit": "40d2840"}
|
|
|
|
|
|
def test_deploy_und_ausrollen_schreiben_den_stand_erst_nach_dem_erfolg():
|
|
deploy = (DEPLOY / "deploy.sh").read_text(encoding="utf-8")
|
|
stufe2 = deploy[deploy.index("stufe2() {"):deploy.index("homelab_mitziehen() {")]
|
|
assert stufe2.index("trap - ERR") < stufe2.index("stand_schreiben") < stufe2.index("ist live")
|
|
assert "stand-schreiben.py \"$STAND_DATEI\"" in deploy and 'STAND_DATEI="/srv/models/mc2-stand.json"' in deploy
|
|
|
|
ausrollen = (DEPLOY / "homelab" / "ausrollen.sh").read_text(encoding="utf-8")
|
|
assert ausrollen.index("einrichten.sh") < ausrollen.index("stand-schreiben.py /var/lib/mc2/mc2-stand.json")
|
|
# Scheitert nur der Stand, gilt das Ausrollen trotzdem (sonst käme ein Alarm für eine Nebensache).
|
|
assert "|| echo \"Hinweis: Die Stand-Datei im Container" in ausrollen
|