diff --git a/agent.py b/agent.py index cb03ddc..3458edc 100644 --- a/agent.py +++ b/agent.py @@ -20,6 +20,15 @@ FALLBACK_INTERVAL_SECONDS = 60 # schnellen Debug-Zyklus untergraben. JITTER_THRESHOLD_SECONDS = 60 * 60 JITTER_MAX_SECONDS = 30 * 60 +# Kunden-ISO-Test 02.10.2026 (R6): nach einem fehlgeschlagenen +# ansible-pull-Lauf galt bisher einfach das Server-Intervall weiter - +# ausserhalb des Debug-Modus 6h, ein fehlgeschlagener Erstlauf (z.B. kurzer +# Netzaussetzer) liess das Geraet also stundenlang ohne Benutzer/Software. +RETRY_AFTER_FAILURE_SECONDS = 5 * 60 +# Statusdateien fuer die Erststart-Anzeige (tuxflotte-firstboot.sh): "laeuft" +# fluechtig unter /run, "erster erfolgreicher Lauf" dauerhaft unter /var/lib. +STATUS_PATH = "/run/tuxflotte/agent/status" +FIRST_SUCCESS_MARKER = "/var/lib/tuxflotte/agent/first-run-ok" def log(message): @@ -138,6 +147,26 @@ def report(device_id, agent_secret, results): ) +def write_status(status): + try: + os.makedirs(os.path.dirname(STATUS_PATH), exist_ok=True) + with open(STATUS_PATH, "w", encoding="utf-8") as f: + f.write(status + "\n") + except OSError as exc: + log(f"Status konnte nicht geschrieben werden: {exc}") + + +def mark_first_success(): + if os.path.exists(FIRST_SUCCESS_MARKER): + return + try: + os.makedirs(os.path.dirname(FIRST_SUCCESS_MARKER), exist_ok=True) + with open(FIRST_SUCCESS_MARKER, "w", encoding="utf-8") as f: + f.write(time.strftime("%Y-%m-%dT%H:%M:%S%z") + "\n") + except OSError as exc: + log(f"Erstlauf-Marker konnte nicht geschrieben werden: {exc}") + + def apply_roles(ansible_repo, blueprints, auftraege, webapps, hostname): """ Führt sowohl die additiv über den Workspace zugewiesenen Blueprints als @@ -254,7 +283,11 @@ def run_once(device_id, agent_secret): # der frueher hier stand: ein Geraet ohne Blueprints/Auftraege, aber # mit soeben entfernten Webapps oder einer neu gesetzten # Hostnamenkonvention, muesste sonst faelschlich uebersprungen werden. + write_status("running") pull_succeeded = apply_roles(result["ansible_repo"], blueprints, auftraege, webapps, hostname) + write_status("ok" if pull_succeeded else "failed") + if pull_succeeded: + mark_first_success() if auftraege: try: @@ -262,7 +295,11 @@ def run_once(device_id, agent_secret): except (urllib.error.URLError, OSError) as exc: log(f"Report-Fehler: {exc}") - return result.get("poll_interval_seconds", FALLBACK_INTERVAL_SECONDS), agent_secret + interval = result.get("poll_interval_seconds", FALLBACK_INTERVAL_SECONDS) + if not pull_succeeded: + interval = min(interval, RETRY_AFTER_FAILURE_SECONDS) + + return interval, agent_secret def main(): diff --git a/firstboot.sh b/firstboot.sh new file mode 100644 index 0000000..a7f3970 --- /dev/null +++ b/firstboot.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Erststart-Anzeige (Kunden-ISO-Test 02.10.2026, R6): nach der Installation +# legt erst der erste Agent-Lauf per Ansible Benutzer und Software an. Bis +# dahin zeigte der Anmeldebildschirm ein Geraet ohne nutzbaren Login. Dieses +# Skript laeuft einmalig vor dem Display-Manager (tuxflotte-firstboot.service), +# zeigt den Fortschritt auf einer eigenen Konsole und gibt die Anmeldung +# frei, sobald der Agent seinen ersten erfolgreichen Lauf gemeldet hat +# (agent.py, mark_first_success()) - spaetestens nach TIMEOUT_SECONDS, damit +# ein dauerhaft scheiternder Lauf das Geraet nie blockiert. Der Agent +# arbeitet danach im Hintergrund weiter. +set -uo pipefail + +FIRST_SUCCESS_MARKER="${TUXFLOTTE_FIRST_SUCCESS_MARKER:-/var/lib/tuxflotte/agent/first-run-ok}" +DONE_MARKER="${TUXFLOTTE_FIRSTBOOT_DONE_MARKER:-/var/lib/tuxflotte/firstboot-done}" +STATUS_PATH="/run/tuxflotte/agent/status" +TIMEOUT_SECONDS="${TUXFLOTTE_FIRSTBOOT_TIMEOUT:-1800}" +VT=12 + +finish() { + mkdir -p "$(dirname "${DONE_MARKER}")" + date --iso-8601=seconds > "${DONE_MARKER}" + exit 0 +} + +[[ -e "${FIRST_SUCCESS_MARKER}" ]] && finish + +# Ohne das bliebe der Plymouth-Bootsplash stehen und verdeckte die Konsole. +if command -v plymouth >/dev/null 2>&1 && plymouth --ping 2>/dev/null; then + plymouth quit +fi +chvt "${VT}" 2>/dev/null || true +setterm --cursor off 2>/dev/null || true + +current_step() { + journalctl -u tuxflotte-agent.service -b -o cat -n 300 --no-pager 2>/dev/null | + sed -n 's/^TASK \[\([^]]*\)\].*/\1/p' | tail -n 1 | cut -c1-70 +} + +start="$(date +%s)" +while true; do + [[ -e "${FIRST_SUCCESS_MARKER}" ]] && break + + elapsed=$(( $(date +%s) - start )) + if (( elapsed >= TIMEOUT_SECONDS )); then + printf '\033[2J\033[H' + printf '\n \033[1mTuxflotte\033[0m\n\n' + printf ' Die Einrichtung ist noch nicht abgeschlossen.\n' + printf ' Die Anmeldung wird jetzt trotzdem freigegeben, die Einrichtung\n' + printf ' läuft im Hintergrund weiter.\n\n' + printf ' Fehlen Programme oder Benutzer, bitte die Administration informieren.\n' + sleep 15 + break + fi + + status="$(cat "${STATUS_PATH}" 2>/dev/null || echo "")" + step="$(current_step)" + + printf '\033[2J\033[H' + printf '\n \033[1mTuxflotte richtet dieses Gerät ein\033[0m\n\n' + printf ' Software und Einstellungen werden installiert. Bitte das Gerät\n' + printf ' eingeschaltet und mit dem Netzwerk verbunden lassen.\n' + printf ' Die Anmeldung erscheint automatisch, sobald alles fertig ist.\n\n' + printf ' Vergangene Zeit: %02d:%02d\n' $((elapsed / 60)) $((elapsed % 60)) + case "${status}" in + running) printf ' Status: Einrichtung läuft\n' ;; + failed) printf ' Status: \033[1;33mein Schritt ist fehlgeschlagen, neuer Versuch in wenigen Minuten\033[0m\n' ;; + *) printf ' Status: Verbindung zum Server wird hergestellt\n' ;; + esac + [[ -n "${step}" ]] && printf ' Aktueller Schritt: %s\n' "${step}" + + sleep 2 +done + +finish diff --git a/tuxflotte-firstboot.service b/tuxflotte-firstboot.service new file mode 100644 index 0000000..9a2d9f0 --- /dev/null +++ b/tuxflotte-firstboot.service @@ -0,0 +1,20 @@ +[Unit] +Description=Tuxflotte Ersteinrichtung abwarten (Hinweis am Bildschirm) +# Einmalig: nach dem ersten Durchlauf legt firstboot.sh DONE_MARKER an. +ConditionPathExists=!/var/lib/tuxflotte/firstboot-done +After=tuxflotte-agent.service systemd-user-sessions.service plymouth-start.service +Before=display-manager.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/bash /opt/tuxflotte/agent/firstboot.sh +StandardOutput=tty +StandardError=journal +TTYPath=/dev/tty12 +TTYReset=yes +# firstboot.sh begrenzt die Wartezeit selbst (TIMEOUT_SECONDS). +TimeoutStartSec=infinity + +[Install] +WantedBy=graphical.target