tuxflotte-installer/scripts/build_customer_iso.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

110 lines
4.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Personalisiert das eigenstaendige Boot-Medium (scripts/build_boot_medium.sh)
# fuer eine bestimmte Kundenorganisation - befuellt config/installer.conf mit
# Aktivierungscode + WLAN-Zugangsdaten. Hostname/Geraete-ID/Partitionierung
# werden weiterhin erst live auf dem Zielgeraet erzeugt (siehe
# backend_generate_config() in backends/mint-image/backend.sh).
#
# Anders als das alte, Casper-basierte Verfahren (Xorriso-Extraktion der
# ganzen ISO + Initrd-Hook-Injection, siehe scripts/lib/initrd.sh) kann hier
# NICHT einfach eine einzelne Datei direkt auf der ISO9660-Ebene ersetzt
# werden: /opt/tuxflotte/ liegt innerhalb von /live/filesystem.squashfs
# (read-only, von live-boot gemountet). Ein direktes
# `xorriso -update datei /opt/tuxflotte/config/installer.conf` traf live
# getestet ins Leere - das gebootete System sah weiterhin die leere
# Platzhalter-Konfiguration aus dem Basis-Image, weil dieser Pfad ausserhalb
# des Squashfs liegt und live-boot ihn nie liest (31.08.2026, live in der
# enterprise-QEMU-VM gefunden: Aktivierungscode-Prompt erschien trotz
# TUXFLOTTE_AUTO_MODE=true in der vermeintlich aktualisierten ISO).
#
# Stattdessen: das Squashfs wird entpackt, installer.conf darin ersetzt, neu
# gepackt (mksquashfs -no-progress braucht dafuer nur reine Kompressionszeit,
# ~1 Minute - kein debootstrap/apt, kein Netzwerk) und als Ganzes per
# `xorriso -update` ins Ziel-ISO eingespielt (Boot-Sektoren/GRUB-Branding
# bleiben unangetastet, stecken schon im Basis-Image aus
# scripts/build_boot_medium.sh). mksquashfs kann ein bestehendes Squashfs
# zwar inkrementell erweitern, aber NICHT bereits vorhandene Dateien
# ueberschreiben (live getestet: identischer Dateiinhalt trotz
# augenscheinlich erfolgreichem Lauf) - deshalb der volle
# unsquashfs/mksquashfs-Zyklus statt eines inkrementellen Appends.
usage() {
echo "Usage: $0 <source.iso> <output.iso> <activation_code> [wifi_ssid] [wifi_psk] [volid]" >&2
}
SOURCE_ISO="${1:-}"
OUTPUT_ISO="${2:-}"
ACTIVATION_CODE="${3:-}"
WIFI_SSID="${4:-}"
WIFI_PSK="${5:-}"
# Vom Aufrufer (provisioning-server, compute_iso_volid()) bereits
# ISO9660-konform aufbereitet (Grossbuchstaben/Ziffern/Unterstrich, <=32
# Zeichen) - dieses Skript validiert/sanitisiert das bewusst nicht nochmal,
# um die Sanitisierungslogik nicht doppelt zu pflegen. Fallback nur fuer
# Aufrufe ohne sechsten Parameter (z.B. manuelle Tests).
VOLID="${6:-TUXFLOTTE}"
if [[ -z "${SOURCE_ISO}" || -z "${OUTPUT_ISO}" || -z "${ACTIVATION_CODE}" ]]; then
usage
exit 1
fi
[[ -f "${SOURCE_ISO}" ]] ||
{ echo "Error: Quell-ISO nicht gefunden: ${SOURCE_ISO}" >&2; exit 1; }
if [[ "${EUID}" -ne 0 ]]; then
echo "Error: muss als root laufen (unsquashfs/mksquashfs brauchen root, um" >&2
echo " Datei-Eigentuemer im Squashfs korrekt zu erhalten - ohne root" >&2
echo " gehen z.B. root:shadow-Zuordnungen verloren, live gefunden)." >&2
exit 1
fi
for cmd in xorriso unsquashfs mksquashfs; do
command -v "${cmd}" >/dev/null 2>&1 ||
{ echo "Error: fehlendes Werkzeug: ${cmd}" >&2; exit 1; }
done
WORK_DIR="$(mktemp -d)"
cleanup() {
# unsquashfs uebernimmt Original-Rechte (teils ohne Write-Bit) - ohne
# chmod schlaegt rm -rf darauf fehl.
chmod -R u+w "${WORK_DIR}" 2>/dev/null || true
rm -rf "${WORK_DIR}"
}
trap cleanup EXIT
echo "Extrahiere Squashfs aus Quell-ISO..."
xorriso -indev "${SOURCE_ISO}" -osirrox on -extract /live/filesystem.squashfs \
"${WORK_DIR}/orig.squashfs" >/dev/null
echo "Entpacke Squashfs..."
unsquashfs -d "${WORK_DIR}/root" "${WORK_DIR}/orig.squashfs" >/dev/null
echo "Schreibe personalisierte installer.conf..."
mkdir -p "${WORK_DIR}/root/opt/tuxflotte/config"
{
echo "export TUXFLOTTE_AUTO_MODE=true"
printf 'export TUXFLOTTE_ACTIVATION_CODE=%q\n' "${ACTIVATION_CODE}"
[[ -n "${WIFI_SSID}" ]] && printf 'export TUXFLOTTE_WIFI_SSID=%q\n' "${WIFI_SSID}"
[[ -n "${WIFI_PSK}" ]] && printf 'export TUXFLOTTE_WIFI_PSK=%q\n' "${WIFI_PSK}"
} >"${WORK_DIR}/root/opt/tuxflotte/config/installer.conf"
echo "Packe Squashfs neu (nur Kompression, kein apt/debootstrap)..."
mksquashfs "${WORK_DIR}/root" "${WORK_DIR}/new.squashfs" -comp xz -no-progress >/dev/null
chmod 444 "${WORK_DIR}/new.squashfs"
echo "Erzeuge Kunden-ISO..."
rm -f "${OUTPUT_ISO}"
xorriso \
-indev "${SOURCE_ISO}" \
-outdev "${OUTPUT_ISO}" \
-volid "${VOLID}" \
-update "${WORK_DIR}/new.squashfs" /live/filesystem.squashfs \
-boot_image any replay
echo "Kunden-ISO erstellt: ${OUTPUT_ISO}"