feat(boot-medium): systemd-Autostart, Fortschrittsanzeige, SSH-Dev-Modus (Phase 2)

Neuer Service tuxflotte-installer.service startet installer.sh beim Boot
direkt auf tty1 (Type=idle, Conflicts=getty@tty1.service) - kein manuelles
Anmelden/sudo mehr noetig, egal ob Auto-Modus oder interaktiver
Test-/Entwicklungslauf. Wrapper boot-autostart.sh spiegelt die Ausgabe
zusaetzlich nach /var/log/tuxflotte-installer.log.

log_step() (scripts/lib/logging.sh) gibt vor jedem Modul einen klar
abgesetzten "[n/12] <Label>"-Block aus - gilt fuer Dev- UND Produktiv-Build
gleichermassen (die tty-Konsole ist die einzige UI, die eine Person vor
dem Geraet sieht, kein Kiosk-Browser mehr).

scripts/build_boot_medium.sh buendelt lb clean/config/build (loeste die
bisherigen Hand-Aufrufe ab) und steuert per --dev-Flag einen SSH-
Debug-Zugang (tuxflotte/test123) - ohne --dev bleibt SSH deaktiviert,
keine gebackenen Zugangsdaten (Produktiv-Default).

Zwei echte, live gefundene Bugs unterwegs behoben:
- Debians trixie-sshd_config deaktiviert PasswordAuthentication per
  Default - fuer den Dev-Testzugang explizit wieder aktiviert.
- Ein ExecStopPost, der getty@tty1.service nach Dienstende neu startet,
  ist unzuverlaessig (Race mit dem eigenen TTY-Teardown, Job wurde
  angestossen, blieb aber inactive/dead). Geloest durch einen
  deterministischen Fallback in boot-autostart.sh selbst: die Konsole
  faellt nach installer.sh in eine interaktive Shell statt auf einen
  eventuell nie zurueckkehrenden getty zu warten.

Live verifiziert in der enterprise-QEMU-VM (zwei frische Enrollment-
Sessions, echte anode-Aktivierung, "Bekanntes Geraet erkannt"-Pfad):
Boot laeuft ohne jede manuelle Anmeldung direkt in installer.sh, alle
12 Fortschritts-Banner erscheinen korrekt nummeriert, Commit-Gate und
kontrollierter Abbruch funktionieren, SSH-Login mit Passwort
funktioniert, Fallback-Shell nach Programmende reagiert auf Eingaben.
This commit is contained in:
Thomas Stallinger 2026-08-31 14:40:39 +02:00
parent 2190d6f403
commit bc13169418
12 changed files with 311 additions and 1 deletions

1
.gitignore vendored
View File

@ -21,6 +21,7 @@ boot-medium/*.files
boot-medium/*.packages boot-medium/*.packages
boot-medium/*.zsync boot-medium/*.zsync
boot-medium/binary.modified_timestamps boot-medium/binary.modified_timestamps
boot-medium/config/includes.chroot/etc/tuxflotte-dev-build
boot-medium/chroot.packages.install boot-medium/chroot.packages.install
boot-medium/chroot.packages.live boot-medium/chroot.packages.live

View File

@ -10,7 +10,7 @@ LB_BINARY_FILESYSTEM="fat32"
LB_APT_INDICES="true" LB_APT_INDICES="true"
# Set boot parameters # 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 # Set boot parameters
LB_BOOTAPPEND_INSTALL="" LB_BOOTAPPEND_INSTALL=""

View File

@ -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

View File

@ -0,0 +1 @@
../tuxflotte-installer.service

View File

@ -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

View File

@ -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

View File

@ -15,3 +15,57 @@ log_error() {
log_success() { log_success() {
echo "[ OK ] $*" 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}))"
}

View File

@ -18,6 +18,7 @@ run_module() {
[[ -f "$module" ]] || error_exit "Modul nicht gefunden: $module" [[ -f "$module" ]] || error_exit "Modul nicht gefunden: $module"
log_step "$(basename "$module")"
log_info "Starte Modul: $(basename "$module")" log_info "Starte Modul: $(basename "$module")"
if [[ "${DRY_RUN:-false}" == true && "$mode" != "always" ]]; then if [[ "${DRY_RUN:-false}" == true && "$mode" != "always" ]]; then

36
scripts/boot-autostart.sh Executable file
View File

@ -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

79
scripts/build_boot_medium.sh Executable file
View File

@ -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"

View File

@ -15,3 +15,57 @@ log_error() {
log_success() { log_success() {
echo "[ OK ] $*" 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}))"
}

View File

@ -18,6 +18,7 @@ run_module() {
[[ -f "$module" ]] || error_exit "Modul nicht gefunden: $module" [[ -f "$module" ]] || error_exit "Modul nicht gefunden: $module"
log_step "$(basename "$module")"
log_info "Starte Modul: $(basename "$module")" log_info "Starte Modul: $(basename "$module")"
if [[ "${DRY_RUN:-false}" == true && "$mode" != "always" ]]; then if [[ "${DRY_RUN:-false}" == true && "$mode" != "always" ]]; then