tuxflotte-installer/scripts/package_golden_image.sh
Thomas Stallinger 27d6fa0f47 feat(boot-medium): build_customer_iso.sh ans neue Struktur anpassen (Phase 3) + vier echte Deployment-Bugs behoben
Phase 3: Personalisierung (Aktivierungscode/WLAN) funktioniert jetzt ohne
Xorriso-Extraktion der ganzen ISO / Casper-Hook-Injection. /opt/tuxflotte/
liegt innerhalb von /live/filesystem.squashfs (read-only) - ein direktes
xorriso -update auf eine Datei darin traf live getestet ins Leere.
Stattdessen: Squashfs entpacken (unsquashfs), installer.conf ersetzen, neu
packen (mksquashfs -comp xz, ~1 Minute reine Kompression, kein
debootstrap/apt), als Ganzes per xorriso -update einspielen. Muss als root
laufen (Datei-Eigentuemer im Squashfs sonst falsch).

Beim ersten echten End-to-End-Testlauf mit dem vollen Golden-Image-Archiv
(vorher nie bis zum Ende gekommen - immer am Ubiquity-Bug oder an einem
Commit-Gate gestoppt) vier echte, bisher unentdeckte Bugs gefunden und
behoben:

1. Golden-Image-Download landete in /run/tuxflotte/backend/ (RAM-Tmpfs,
   hier 392 MB) statt auf echtem Speicher - "curl: (23) Failure writing
   output to destination" bei 2,3 GB Archiv, unabhaengig von RAM-Groesse.
   Fix: image_deploy_extract_image_from_url() streamt Download direkt in
   die Extraktion (curl | tar), kein Zwischenspeichern mehr noetig.
   backend_launch() partitioniert/formatiert/mountet jetzt VOR dem
   Download-Versuch statt danach.

2. /etc/resolv.conf im Zielbaum ist bei der echten Referenz-VM ein von
   NetworkManager verwalteter Symlink - "cp" verweigerte das Schreiben
   "through a dangling symlink". Fix: Ziel vor dem Kopieren explizit
   entfernen.

3. mount --bind auf /dev, /proc, /sys schlug fehl, weil diese Verzeichnisse
   nach dem Entpacken gar nicht existierten (package_golden_image.sh
   schliesst sie bewusst aus) - und zwar SILENT, weil das bisherige
   "cmd && stack_ref+=(...)"-Muster einen Fehlschlag unter "set -e" nicht
   als Statement-Fehler wertet. Fiel dadurch erst beim naechsten
   chroot-Aufruf auf ("ssh-keygen -A: Couldn't open /dev/null"), weit weg
   von der eigentlichen Ursache. Fix: neue image_deploy_restore_excluded_dirs()
   legt alle sechs von package_golden_image.sh ausgeschlossenen
   Verzeichnisse (proc/sys/dev/run/tmp/var-tmp) direkt nach der Extraktion
   wieder an; jeder Mount wird jetzt einzeln explizit geprueft statt auf
   &&-Verkettung zu vertrauen.

4. package_golden_image.sh: "--exclude=dev" (ohne "./"-Praefix) matcht bei
   GNU tar gegen JEDEN Verzeichnis-Basisnamen im ganzen Baum, nicht nur
   den Top-Level-Eintrag - hat dadurch auch kernel/drivers/net/can/dev/
   (ein zufaellig gleichnamiger, voellig unverwandter Kernelmodul-Ordner)
   aus dem Archiv gerissen, update-initramfs scheiterte an fehlenden
   can-dev.ko.zst-Abhaengigkeiten. Lokal mit einem Wegwerf-Testbaum
   verifiziert (wie beim "run"-Fund vom selben Tag): "./"-Praefix
   verankert das Muster auf den exakten Top-Level-Pfad, gleichnamige
   verschachtelte Ordner bleiben erhalten. Betrifft das AKTUELL AUF ANODE
   GEHOSTETE Archiv - muss mit dem korrigierten Skript auf der
   Referenz-VM neu gepackt und hochgeladen werden (steht noch aus,
   Nutzer verschlankt die Referenz-VM gerade zusaetzlich).

Live-Fortschritt in der enterprise-QEMU-VM: nach Fix 1-3 kam der
Deployment-Versuch bis kurz vor update-initramfs durch (Partitionierung,
Formatierung, Mounten, Download+Extraktion, chroot-Fixup bis machine-id +
SSH-Hostkeys liefen alle sauber) - Fund 4 (das defekte Archiv) ist der
letzte noch offene Blocker fuer einen vollstaendig erfolgreichen
End-to-End-Durchlauf.
2026-08-31 16:36:35 +02:00

100 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
# Baut das Golden Image aus einer bereits fertig installierten
# Referenz-VM (siehe ADR-0024-Nachtrag "Referenz-VM statt debootstrap") -
# laeuft DIREKT AUF der Referenz-VM (als root), anders als
# build_golden_image.sh, das auf einem separaten Build-Host ein
# Basissystem von Grund auf neu baut. Bereinigt zuerst alle maschinen-/
# betriebsspezifischen Spuren, packt danach den verbleibenden Dateibaum
# in genau dem Format, das image_deploy_extract_image()
# (scripts/lib/image_deploy.sh, Phase 1) erwartet - tar/zstd, keine
# Blockgeraete-Kopie.
#
# WICHTIG: entfernt als Teil der Bereinigung auch den bisherigen
# Betriebs-/Testzugang zu dieser Maschine (Testkonto, SSH-Autorisierung,
# Root-Passwort) - das ist beabsichtigt (jede damit ausgerollte
# Kundeninstallation wuerde sonst dieselben, bekannten Zugangsdaten
# tragen), nicht versehentlich. Das Ergebnis-Archiv muss ueber dieselbe,
# bereits authentifizierte Verbindung abgeholt werden, mit der dieses
# Skript gestartet wurde (z.B. per SSH-ControlMaster) - eine NEUE
# Verbindung nach dem Lauf funktioniert nicht mehr. Ein Proxmox-Snapshot
# von VOR diesem Lauf ist die vorgesehene Rueckfallebene fuer kuenftige
# Wartung/Versions-Upgrades der Referenz-VM (siehe Plan).
OUTPUT_FILE="${1:-/root/golden-image.tar.zst}"
if [[ "${EUID}" -ne 0 ]]; then
echo "Fehler: muss als root laufen." >&2
exit 1
fi
echo "==> Entferne Betriebs-/Testzugang (nicht Teil des Kundenimages)"
if id tuxflotte >/dev/null 2>&1; then
userdel -r tuxflotte 2>/dev/null || userdel tuxflotte 2>/dev/null || true
fi
mkdir -p /root/.ssh
: > /root/.ssh/authorized_keys
passwd -l root >/dev/null
echo "==> Entferne maschinenspezifische Identität"
rm -f /etc/ssh/ssh_host_*
: > /etc/machine-id
# Manche Distributionen legen /var/lib/dbus/machine-id als echte Kopie an
# statt als Symlink auf /etc/machine-id (siehe ADR-0024-Fund, 29.08.2026) -
# beide Fälle abdecken, ein Symlink zeigt ohnehin automatisch auf die
# gerade geleerte Datei.
if [[ -e /var/lib/dbus/machine-id && ! -L /var/lib/dbus/machine-id ]]; then
: > /var/lib/dbus/machine-id
fi
echo "==> Entferne Netzwerk-Zugangsdaten der Referenz-VM"
rm -f /etc/NetworkManager/system-connections/*
echo "==> Bereinige Caches/Logs/temporäre Dateien"
apt-get clean
rm -rf /var/lib/apt/lists/*
find /var/log -type f -exec truncate -s0 {} \;
rm -rf /tmp/* /var/tmp/*
rm -f /root/.bash_history
rm -f /home/*/.bash_history 2>/dev/null || true
# Paketauswahl eindampfen (Browser, Office-Pakete o.Ä.) bewusst NICHT im
# ersten Lauf - siehe Plan ("erster Durchlauf zeigt, wie groß das Archiv
# real wird, dann gezielt nachschärfen"). Blindes Purgen ohne die
# tatsächliche Paketliste gesehen zu haben, riskiert eher kaputte
# Abhängigkeiten als sinnvolle Einsparung. Nach diesem Lauf:
# `dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -rn | head -30`
# auf der Referenz-VM zeigt die groessten Kandidaten fuer einen gezielten
# zweiten Durchlauf.
echo "==> Packe nach ${OUTPUT_FILE}"
# WICHTIG: tar -C / erzeugt relative Elementnamen (./proc/..., ./sys/...),
# nicht absolute (/proc/...) - ein --exclude mit fuehrendem Slash matcht
# dagegen NICHT (live am 31.08.2026 entdeckt: das lief zunaechst
# unbemerkt gegen die echte Referenz-VM und haette u.a. /proc/kcore mit
# eingepackt).
#
# ZWEITER FUND (31.08.2026, spaeter am selben Tag): ein --exclude OHNE
# fuehrenden Slash UND ohne "./" matcht bei GNU tar gegen den Basisnamen
# JEDER Datei/jedes Verzeichnisses im ganzen Baum, nicht nur den
# Top-Level-Eintrag - "--exclude=dev" hat dadurch auch
# kernel/drivers/net/can/dev/ (ein voellig unverwandter, aber zufaellig
# gleichnamiger Kernelmodul-Ordner) mit ausgeschlossen und damit das
# gesamte CAN-Bus-Treibermodul aus dem Archiv gerissen - live erst beim
# tatsaechlichen Deployment aufgefallen (update-initramfs scheiterte an
# fehlenden can-dev.ko.zst-Abhaengigkeiten). Ein Praefix "./" verankert
# das Muster auf den exakten Top-Level-Pfad (ein "/" im Muster laesst tar
# gegen den vollen Namen matchen statt nur den Basisnamen) - lokal mit
# einem Wegwerf-Testbaum verifiziert: ./dev/./proc/./run schliessen nur
# die Top-Level-Eintraege aus, kernel/drivers/net/can/dev/ und
# nested/proc/... bleiben erhalten.
tar --numeric-owner --xattrs \
--exclude=./proc --exclude=./sys --exclude=./dev \
--exclude=./run --exclude=./tmp --exclude=./var/tmp \
--exclude="./${OUTPUT_FILE#/}" \
-C / -cf - . | zstd -f -T0 -19 -o "${OUTPUT_FILE}"
echo "==> Fertig: ${OUTPUT_FILE} ($(du -h "${OUTPUT_FILE}" | cut -f1))"
echo "==> Root-Zugang zu dieser Maschine ist jetzt gesperrt (Testkonto entfernt, SSH-Autorisierung geleert, Root-Passwort gesperrt) - Proxmox-Snapshot ist die Rueckfallebene fuer kuenftige Laeufe."