diff --git a/backend/tests/test_homelab.py b/backend/tests/test_homelab.py index 1225dd7..0d4a27c 100644 --- a/backend/tests/test_homelab.py +++ b/backend/tests/test_homelab.py @@ -277,3 +277,15 @@ def test_docker_ueber_arcane_erst_als_probelauf(ausfuehrer, monkeypatch): assert meldungen[-1][0] == "Arcane-Probelauf: 1 von 7 Containern würden aktualisiert (nerdquiz). Geändert wurde nichts." monkeypatch.delenv("MC_ARCANE_KEY") assert updates.starten("vm-106", "docker")["ok"] is False + + +def test_ausfuehrer_nur_lesen(monkeypatch): + a = _ausfuehrer_modul() + monkeypatch.setattr(a, "NUR_LESEN", True) + monkeypatch.setattr(a, "_pvesh", lambda pfad, *p: [{"vmid": 104, "type": "lxc", "tags": "community-script"}]) + ausgefuehrt: list[list[str]] = [] + monkeypatch.setattr(a, "_laufen", lambda befehl, zeitlimit=60: ausgefuehrt.append(befehl) or (0, "ok")) + antwort = a.ausfuehren({"aktion": "update", "parameter": {"vmid": 104}}) + assert antwort["code"] == 2 and "Nur-Lesen" in antwort["text"] and ausgefuehrt == [] + monkeypatch.setattr(a, "bericht", lambda: {"gaeste": []}) + assert a.ausfuehren({"aktion": "bericht", "parameter": {}})["code"] == 0 diff --git a/deploy/homelab/ausfuehrer-einrichten.sh b/deploy/homelab/ausfuehrer-einrichten.sh index 36f9425..ff4d530 100644 --- a/deploy/homelab/ausfuehrer-einrichten.sh +++ b/deploy/homelab/ausfuehrer-einrichten.sh @@ -5,10 +5,14 @@ # Proxmox-Host und startet den Dienst. Das gemeinsame Geheimnis wandert vom Container des Homelab-Teils # per Pipe direkt auf den Host (0600) — es steht in keiner Befehlszeile. # -# Nutzung: bash deploy/homelab/ausfuehrer-einrichten.sh +# Nutzung: bash deploy/homelab/ausfuehrer-einrichten.sh [--nur-lesen] +# --nur-lesen: nur der Bericht, jeder andere Auftrag wird abgelehnt (umschalten in +# /etc/mc2-ausfuehrer.json, "nur_lesen": false, dann systemctl restart mc2-ausfuehrer). set -euo pipefail cd "$(dirname "$0")/../.." -IP="${1:?Nutzung: ausfuehrer-einrichten.sh }" +IP="${1:?Nutzung: ausfuehrer-einrichten.sh [--nur-lesen]}" +NUR_LESEN=false +[ "${2:-}" = "--nur-lesen" ] && NUR_LESEN=true PVE="${MC_PVE:-pve}" ssh -o BatchMode=yes "$PVE" "install -d -m 755 /usr/local/lib/mc2" @@ -18,7 +22,7 @@ ssh -o BatchMode=yes "$PVE" "cat > /etc/systemd/system/mc2-ausfuehrer.service" < # shellcheck disable=SC2029 # $IP soll hier (am PC) eingesetzt werden ssh -o BatchMode=yes "root@$IP" "cat /var/lib/mc2/ausfuehrer.token" \ | ssh -o BatchMode=yes "$PVE" "umask 077; read -r t; [ -n \"\$t\" ] || exit 1; \ - printf '{\"server\": \"http://%s:9001\", \"token\": \"%s\", \"node\": \"%s\"}\n' '$IP' \"\$t\" \"\$(hostname)\" \ + printf '{\"server\": \"http://%s:9001\", \"token\": \"%s\", \"node\": \"%s\", \"nur_lesen\": %s}\n' '$IP' \"\$t\" \"\$(hostname)\" '$NUR_LESEN' \ > /etc/mc2-ausfuehrer.json" ssh -o BatchMode=yes "$PVE" "systemctl daemon-reload && systemctl enable --now mc2-ausfuehrer.service \ && systemctl restart mc2-ausfuehrer.service && sleep 5 && systemctl is-active mc2-ausfuehrer.service" diff --git a/deploy/homelab/ausfuehrer.py b/deploy/homelab/ausfuehrer.py index 2141e2c..6827a9f 100644 --- a/deploy/homelab/ausfuehrer.py +++ b/deploy/homelab/ausfuehrer.py @@ -13,7 +13,8 @@ Container des Homelab-Teils braucht keinen Proxmox-Schlüssel. vertraut — er kann nur aus der Liste wählen. Nur die Standardbibliothek (Debian-Python des Hosts). Konfiguration: /etc/mc2-ausfuehrer.json - {"server": "http://192.168.178.x:9001", "token": "", "node": "pve"} + {"server": "http://192.168.178.x:9001", "token": "", "node": "pve", "nur_lesen": false} +„nur_lesen": true liefert nur den Bericht und lehnt jeden anderen Auftrag ab. Aufruf: ausfuehrer.py Dauerbetrieb (systemd) ausfuehrer.py --bericht Bericht einmal auf die Konsole (nur lesend, zum Prüfen) @@ -327,6 +328,9 @@ def ausfuehren(auftrag: dict) -> dict: aktion = AKTIONEN.get(str(auftrag.get("aktion"))) if aktion is None: return {"code": 2, "text": f"Unbekannte Aktion: {auftrag.get('aktion')!r}"} + # Nur-Lesen-Modus ("nur_lesen": true in der Konfiguration): Bericht ja, Änderungen nein. + if NUR_LESEN and aktion is not a_bericht: + return {"code": 2, "text": "Abgelehnt: Der Ausführer läuft im Nur-Lesen-Modus."} try: code, text = aktion(auftrag.get("parameter") or {}) except (ValueError, RuntimeError) as exc: @@ -379,6 +383,7 @@ _konfig = _konfig_laden() SERVER = str(_konfig.get("server") or "").rstrip("/") TOKEN = str(_konfig.get("token") or "") NODE = str(_konfig.get("node") or platform.node()) +NUR_LESEN = bool(_konfig.get("nur_lesen")) def main() -> int: