diff --git a/.gitignore b/.gitignore index ce14514..5d3a80f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ boot-medium/*.files boot-medium/*.packages boot-medium/*.zsync boot-medium/binary.modified_timestamps +boot-medium/config/includes.chroot/etc/tuxflotte-dev-build boot-medium/chroot.packages.install boot-medium/chroot.packages.live diff --git a/boot-medium/config/binary b/boot-medium/config/binary index 0b79363..0fffd72 100644 --- a/boot-medium/config/binary +++ b/boot-medium/config/binary @@ -10,7 +10,7 @@ LB_BINARY_FILESYSTEM="fat32" LB_APT_INDICES="true" # Set boot parameters -LB_BOOTAPPEND_LIVE="boot=live components quiet splash" +LB_BOOTAPPEND_LIVE="boot=live components quiet splash vconsole.keymap=de" # Set boot parameters LB_BOOTAPPEND_INSTALL="" diff --git a/boot-medium/config/hooks/live/0900-tuxflotte-ssh-mode.hook.chroot b/boot-medium/config/hooks/live/0900-tuxflotte-ssh-mode.hook.chroot new file mode 100755 index 0000000..34b36d9 --- /dev/null +++ b/boot-medium/config/hooks/live/0900-tuxflotte-ssh-mode.hook.chroot @@ -0,0 +1,24 @@ +#!/bin/sh +set -e + +# Steuert SSH-Zugang ueber einen Build-Parameter (scripts/build_boot_medium.sh +# --dev), nicht ueber einen fest gebackenen Zustand. Der Marker +# /etc/tuxflotte-dev-build wird von build_boot_medium.sh vor dem lb-build-Lauf +# gesetzt/entfernt - siehe dort. + +if [ -e /etc/tuxflotte-dev-build ]; then + echo "Dev-Build: SSH-Debug-Zugang wird aktiviert (tuxflotte/test123)." + id tuxflotte >/dev/null 2>&1 || useradd -m -s /bin/bash tuxflotte + echo "tuxflotte:test123" | chpasswd + usermod -aG sudo tuxflotte + # Debians trixie-sshd_config-Default hat PasswordAuthentication live + # entdeckt bereits deaktiviert (nur publickey angeboten) - fuer den + # Testzugang per Passwort explizit wieder aktivieren. + mkdir -p /etc/ssh/sshd_config.d + echo "PasswordAuthentication yes" > /etc/ssh/sshd_config.d/90-tuxflotte-dev.conf + rm -f /etc/tuxflotte-dev-build +else + echo "Produktiv-Build: SSH bleibt deaktiviert, keine gebackenen Zugangsdaten." + rm -f /etc/systemd/system/multi-user.target.wants/ssh.service + ln -sf /dev/null /etc/systemd/system/ssh.service +fi diff --git a/boot-medium/config/includes.chroot/etc/systemd/system/multi-user.target.wants/tuxflotte-installer.service b/boot-medium/config/includes.chroot/etc/systemd/system/multi-user.target.wants/tuxflotte-installer.service new file mode 120000 index 0000000..7946b2b --- /dev/null +++ b/boot-medium/config/includes.chroot/etc/systemd/system/multi-user.target.wants/tuxflotte-installer.service @@ -0,0 +1 @@ +../tuxflotte-installer.service \ No newline at end of file diff --git a/boot-medium/config/includes.chroot/etc/systemd/system/tuxflotte-installer.service b/boot-medium/config/includes.chroot/etc/systemd/system/tuxflotte-installer.service new file mode 100644 index 0000000..9adfadb --- /dev/null +++ b/boot-medium/config/includes.chroot/etc/systemd/system/tuxflotte-installer.service @@ -0,0 +1,23 @@ +[Unit] +Description=Tuxflotte Installer Auto-Start +Conflicts=getty@tty1.service +After=getty@tty1.service NetworkManager.service +Wants=NetworkManager.service + +[Service] +Type=idle +ExecStart=/opt/tuxflotte/scripts/boot-autostart.sh +StandardInput=tty-force +StandardOutput=journal+console +StandardError=journal+console +TTYPath=/dev/tty1 +TTYReset=yes +TTYVHangup=yes +# Live gefunden (31.08.2026): ein ExecStopPost, der getty@tty1.service beim +# Dienstende neu startet, ist unzuverlaessig (Race mit dem eigenen +# TTY-Teardown). Geloest stattdessen in boot-autostart.sh selbst: die +# Konsole faellt nach installer.sh deterministisch in eine interaktive +# Shell, kein getty-Neustart noetig. + +[Install] +WantedBy=multi-user.target diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/boot-autostart.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/boot-autostart.sh new file mode 100755 index 0000000..1661530 --- /dev/null +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/boot-autostart.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Wird von tuxflotte-installer.service beim Boot direkt auf tty1 gestartet - +# unabhaengig davon, ob config/installer.conf einen Auto-Modus- +# Aktivierungscode mitbringt (personalisierte Kunden-ISO, siehe +# scripts/build_customer_iso.sh) oder nicht (generisches Test-/ +# Entwicklungsabbild): installer.sh fragt in letzterem Fall selbst +# interaktiv nach (12_enrollment_auth.sh, 05_network.sh-WLAN-Auswahl), auf +# genau dieser Konsole - der Service leitet dafuer TTYPath=/dev/tty1 durch. +# +# Schreibt zusaetzlich zur journal-Ausgabe (StandardOutput=journal+console +# im systemd-Unit) in eine Logdatei - per SSH/Konsole nachtraeglich +# einsehbar, ohne journalctl zu brauchen. +LOG_FILE="/var/log/tuxflotte-installer.log" + +/opt/tuxflotte/scripts/installer.sh 2>&1 | tee -a "${LOG_FILE}" +STATUS="${PIPESTATUS[0]}" + +# Live gefunden (31.08.2026): ein ExecStopPost, der beim Dienstende +# getty@tty1.service neu startet, ist unzuverlaessig (Race mit dem eigenen +# TTY-Teardown dieses Dienstes - der Job wurde angestossen, blieb aber +# "inactive/dead"). Stattdessen: diese Konsole faellt nach installer.sh +# (egal ob Erfolg, kontrollierter Abbruch an einem Commit-Gate, oder +# Fehler) deterministisch in eine interaktive Root-Shell - kein Race, +# garantiert nutzbare Konsole fuer Fehlersuche/manuellen Neustart. Im +# Erfolgsfall mit tatsaechlichem Deployment loest 40_backend.sh ohnehin +# selbst einen Reboot aus, wodurch diese Shell nie sichtbar wird. +{ + echo + echo "Tuxflotte-Installer beendet (Exit-Code: ${STATUS})." + echo "Diese Konsole steht fuer Fehlersuche/manuellen Neustart zur Verfuegung." + echo +} | tee -a "${LOG_FILE}" + +exec /bin/bash --login diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/logging.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/logging.sh index 971cc60..bd8bedc 100644 --- a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/logging.sh +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/logging.sh @@ -15,3 +15,57 @@ log_error() { log_success() { echo "[ OK ] $*" } + +# Klar abgesetzte Fortschrittsanzeige fuer die tty-Konsole des Boot-Mediums +# (kein Desktop, kein Kiosk-Browser - die Terminal-Ausgabe selbst ist die +# einzige UI, die eine Person vor dem Geraet sieht). Gilt fuer Dev- UND +# Produktiv-Build gleichermassen, nicht nur zum Debuggen - siehe Plan +# "Klare Fortschrittsanzeige auf der Konsole". Format/Stil bewusst an die +# bestehenden Banner in 17_device_status.sh angelehnt (Titel + "="-Linie +# gleicher Laenge, keine Sonderzeichen, die auf einer rohen VT-Konsole +# fehlen koennten). +declare -A TUXFLOTTE_STEP_LABELS=( + [00_preflight.sh]="Vorbereitung" + [05_network.sh]="Netzwerkverbindung wird hergestellt" + [10_hardware.sh]="Hardware wird erkannt" + [12_enrollment_auth.sh]="Aktivierungscode wird geprüft" + [15_server_handshake.sh]="Verbindung zu anode wird hergestellt" + [17_device_status.sh]="Gerätestatus wird abgerufen" + [20_profile_selection.sh]="Bereitstellungsvorlage wird ausgewählt" + [25_installation_confirm.sh]="Installationsbestätigung" + [30_runtime_blueprint.sh]="Installationsplan wird aufgelöst" + [40_backend.sh]="Golden Image wird geladen und installiert" + [20_storage.sh]="Speicher-Zusatzprüfung" + [99_finish.sh]="Abschluss" +) +readonly -a TUXFLOTTE_STEP_ORDER=( + 00_preflight.sh 05_network.sh 10_hardware.sh 12_enrollment_auth.sh + 15_server_handshake.sh 17_device_status.sh 20_profile_selection.sh + 25_installation_confirm.sh 30_runtime_blueprint.sh 40_backend.sh + 20_storage.sh 99_finish.sh +) +TUXFLOTTE_STEP_COUNT=0 + +log_step() { + local module_name="$1" + local label="${TUXFLOTTE_STEP_LABELS[$module_name]:-$module_name}" + local total="${#TUXFLOTTE_STEP_ORDER[@]}" + local bold="" reset="" + + TUXFLOTTE_STEP_COUNT=$((TUXFLOTTE_STEP_COUNT + 1)) + + # Klartext-Fallback ohne Terminal (z.B. wenn stdout in eine Log-Datei + # umgeleitet wird) - kein tput-Aufruf, ncurses-bin ist bewusst nicht Teil + # des schlanken Paketsatzes, rohe ANSI-Codes reichen und sind ueberall + # ohne Zusatzpaket verfuegbar. + if [[ -t 1 ]]; then + bold=$'\033[1m' + reset=$'\033[0m' + fi + + local title + title="[${TUXFLOTTE_STEP_COUNT}/${total}] ${label}" + + printf '\n%s%s%s\n' "${bold}" "${title}" "${reset}" + printf '%s\n\n' "$(printf '=%.0s' $(seq 1 ${#title}))" +} diff --git a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/utils.sh b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/utils.sh index d2ff045..c87359a 100644 --- a/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/utils.sh +++ b/boot-medium/config/includes.chroot/opt/tuxflotte/scripts/lib/utils.sh @@ -18,6 +18,7 @@ run_module() { [[ -f "$module" ]] || error_exit "Modul nicht gefunden: $module" + log_step "$(basename "$module")" log_info "Starte Modul: $(basename "$module")" if [[ "${DRY_RUN:-false}" == true && "$mode" != "always" ]]; then diff --git a/scripts/boot-autostart.sh b/scripts/boot-autostart.sh new file mode 100755 index 0000000..1661530 --- /dev/null +++ b/scripts/boot-autostart.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Wird von tuxflotte-installer.service beim Boot direkt auf tty1 gestartet - +# unabhaengig davon, ob config/installer.conf einen Auto-Modus- +# Aktivierungscode mitbringt (personalisierte Kunden-ISO, siehe +# scripts/build_customer_iso.sh) oder nicht (generisches Test-/ +# Entwicklungsabbild): installer.sh fragt in letzterem Fall selbst +# interaktiv nach (12_enrollment_auth.sh, 05_network.sh-WLAN-Auswahl), auf +# genau dieser Konsole - der Service leitet dafuer TTYPath=/dev/tty1 durch. +# +# Schreibt zusaetzlich zur journal-Ausgabe (StandardOutput=journal+console +# im systemd-Unit) in eine Logdatei - per SSH/Konsole nachtraeglich +# einsehbar, ohne journalctl zu brauchen. +LOG_FILE="/var/log/tuxflotte-installer.log" + +/opt/tuxflotte/scripts/installer.sh 2>&1 | tee -a "${LOG_FILE}" +STATUS="${PIPESTATUS[0]}" + +# Live gefunden (31.08.2026): ein ExecStopPost, der beim Dienstende +# getty@tty1.service neu startet, ist unzuverlaessig (Race mit dem eigenen +# TTY-Teardown dieses Dienstes - der Job wurde angestossen, blieb aber +# "inactive/dead"). Stattdessen: diese Konsole faellt nach installer.sh +# (egal ob Erfolg, kontrollierter Abbruch an einem Commit-Gate, oder +# Fehler) deterministisch in eine interaktive Root-Shell - kein Race, +# garantiert nutzbare Konsole fuer Fehlersuche/manuellen Neustart. Im +# Erfolgsfall mit tatsaechlichem Deployment loest 40_backend.sh ohnehin +# selbst einen Reboot aus, wodurch diese Shell nie sichtbar wird. +{ + echo + echo "Tuxflotte-Installer beendet (Exit-Code: ${STATUS})." + echo "Diese Konsole steht fuer Fehlersuche/manuellen Neustart zur Verfuegung." + echo +} | tee -a "${LOG_FILE}" + +exec /bin/bash --login diff --git a/scripts/build_boot_medium.sh b/scripts/build_boot_medium.sh new file mode 100755 index 0000000..09153f1 --- /dev/null +++ b/scripts/build_boot_medium.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Baut das eigenstaendige Tuxflotte-Boot-Medium (Debian trixie, per +# Debian-live-build) - siehe Plan ~/.claude/plans/inherited-floating-treasure.md +# und Memory project_independent_boot_medium.md. +# +# Ersetzt langfristig scripts/build.sh/build_customer_iso.sh (Casper-Patchen +# einer echten Linux-Mint-Live-ISO): dieses Boot-Medium enthaelt nur +# Tuxflottes eigenen, headless-tauglichen Code (Module 00-40, +# backends/mint-image/) und beruehrt niemals Ubiquity/Casper/eine +# Distributions-Live-Session - macht die Bug-Klasse, die zu dieser +# Architektur gefuehrt hat (siehe Memory-Datei), strukturell unmoeglich. +# +# --dev aktiviert einen SSH-Debug-Zugang (Testzugang tuxflotte/test123, +# analog bestehender Test-Fixtures wie WLAN-Test-Org) - ohne --dev bleibt +# SSH deaktiviert, keine gebackenen Zugangsdaten (Produktiv-Default, +# konsistent damit, dass auch das Golden Image selbst keine Zugangsdaten +# enthaelt). Siehe boot-medium/config/hooks/live/0900-tuxflotte-ssh-mode.hook.chroot. + +usage() { + echo "Usage: $0 [--dev]" >&2 + echo " --dev SSH-Debug-Zugang aktivieren (tuxflotte/test123)" >&2 +} + +DEV_BUILD=false +for arg in "$@"; do + case "$arg" in + --dev) DEV_BUILD=true ;; + -h|--help) usage; exit 0 ;; + *) echo "Error: Unbekannter Parameter: $arg" >&2; usage; exit 1 ;; + esac +done + +if [[ "${EUID}" -ne 0 ]]; then + echo "Error: muss als root laufen (live-build braucht root fuer chroot/mount)." >&2 + exit 1 +fi + +for cmd in lb; do + command -v "${cmd}" >/dev/null 2>&1 || + { echo "Error: fehlendes Werkzeug: ${cmd} (apt-get install live-build)" >&2; exit 1; } +done + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BOOT_MEDIUM_DIR="${REPO_DIR}/boot-medium" +DEV_MARKER="${BOOT_MEDIUM_DIR}/config/includes.chroot/etc/tuxflotte-dev-build" + +[[ -d "${BOOT_MEDIUM_DIR}/config" ]] || + { echo "Error: ${BOOT_MEDIUM_DIR}/config fehlt - lb config wurde noch nie ausgefuehrt." >&2; exit 1; } + +cd "${BOOT_MEDIUM_DIR}" + +if [[ "${DEV_BUILD}" == true ]]; then + echo "==> Dev-Build: SSH-Debug-Zugang wird aktiviert (tuxflotte/test123)." + mkdir -p "$(dirname "${DEV_MARKER}")" + touch "${DEV_MARKER}" +else + echo "==> Produktiv-Build: SSH bleibt deaktiviert." + rm -f "${DEV_MARKER}" +fi + +echo "==> lb clean" +lb clean + +echo "==> lb config" +lb config \ + --distribution trixie \ + --architectures amd64 \ + --archive-areas "main contrib non-free non-free-firmware" \ + --binary-images iso-hybrid \ + --bootloaders "grub-efi,syslinux" \ + --debian-installer none \ + --bootappend-live "boot=live components quiet splash vconsole.keymap=de" + +echo "==> lb build" +lb build + +echo "==> Boot-Medium gebaut: ${BOOT_MEDIUM_DIR}/live-image-amd64.hybrid.iso" diff --git a/scripts/lib/logging.sh b/scripts/lib/logging.sh index 971cc60..bd8bedc 100644 --- a/scripts/lib/logging.sh +++ b/scripts/lib/logging.sh @@ -15,3 +15,57 @@ log_error() { log_success() { echo "[ OK ] $*" } + +# Klar abgesetzte Fortschrittsanzeige fuer die tty-Konsole des Boot-Mediums +# (kein Desktop, kein Kiosk-Browser - die Terminal-Ausgabe selbst ist die +# einzige UI, die eine Person vor dem Geraet sieht). Gilt fuer Dev- UND +# Produktiv-Build gleichermassen, nicht nur zum Debuggen - siehe Plan +# "Klare Fortschrittsanzeige auf der Konsole". Format/Stil bewusst an die +# bestehenden Banner in 17_device_status.sh angelehnt (Titel + "="-Linie +# gleicher Laenge, keine Sonderzeichen, die auf einer rohen VT-Konsole +# fehlen koennten). +declare -A TUXFLOTTE_STEP_LABELS=( + [00_preflight.sh]="Vorbereitung" + [05_network.sh]="Netzwerkverbindung wird hergestellt" + [10_hardware.sh]="Hardware wird erkannt" + [12_enrollment_auth.sh]="Aktivierungscode wird geprüft" + [15_server_handshake.sh]="Verbindung zu anode wird hergestellt" + [17_device_status.sh]="Gerätestatus wird abgerufen" + [20_profile_selection.sh]="Bereitstellungsvorlage wird ausgewählt" + [25_installation_confirm.sh]="Installationsbestätigung" + [30_runtime_blueprint.sh]="Installationsplan wird aufgelöst" + [40_backend.sh]="Golden Image wird geladen und installiert" + [20_storage.sh]="Speicher-Zusatzprüfung" + [99_finish.sh]="Abschluss" +) +readonly -a TUXFLOTTE_STEP_ORDER=( + 00_preflight.sh 05_network.sh 10_hardware.sh 12_enrollment_auth.sh + 15_server_handshake.sh 17_device_status.sh 20_profile_selection.sh + 25_installation_confirm.sh 30_runtime_blueprint.sh 40_backend.sh + 20_storage.sh 99_finish.sh +) +TUXFLOTTE_STEP_COUNT=0 + +log_step() { + local module_name="$1" + local label="${TUXFLOTTE_STEP_LABELS[$module_name]:-$module_name}" + local total="${#TUXFLOTTE_STEP_ORDER[@]}" + local bold="" reset="" + + TUXFLOTTE_STEP_COUNT=$((TUXFLOTTE_STEP_COUNT + 1)) + + # Klartext-Fallback ohne Terminal (z.B. wenn stdout in eine Log-Datei + # umgeleitet wird) - kein tput-Aufruf, ncurses-bin ist bewusst nicht Teil + # des schlanken Paketsatzes, rohe ANSI-Codes reichen und sind ueberall + # ohne Zusatzpaket verfuegbar. + if [[ -t 1 ]]; then + bold=$'\033[1m' + reset=$'\033[0m' + fi + + local title + title="[${TUXFLOTTE_STEP_COUNT}/${total}] ${label}" + + printf '\n%s%s%s\n' "${bold}" "${title}" "${reset}" + printf '%s\n\n' "$(printf '=%.0s' $(seq 1 ${#title}))" +} diff --git a/scripts/lib/utils.sh b/scripts/lib/utils.sh index d2ff045..c87359a 100644 --- a/scripts/lib/utils.sh +++ b/scripts/lib/utils.sh @@ -18,6 +18,7 @@ run_module() { [[ -f "$module" ]] || error_exit "Modul nicht gefunden: $module" + log_step "$(basename "$module")" log_info "Starte Modul: $(basename "$module")" if [[ "${DRY_RUN:-false}" == true && "$mode" != "always" ]]; then