Drei zusammenhaengende Punkte aus dem zweiten echten Hardware-Test
(02.09.2026):
1. "Das backend40-Script/Partitionierung zeigt viele Zeilen Ausgaben...
Nach dem Entpacken kommen wieder sehr viele Zeilen Ausgaben" - neue
run_module_backend() (utils.sh) faengt jetzt auch 40_backend.sh ab
(Partitionieren/Formatieren/chroot-Fixup/Bootloader/Postinstall
verborgen wie jedes andere Modul), OHNE animierten Kreisel (statischer
Titel, da das Modul seinen eigenen Fortschritt zeigt). Der Download-
Fortschrittsbalken selbst bleibt sichtbar: curl schreibt ihn bei
aktivem TUXFLOTTE_TTY_LIVE direkt auf /dev/tty1, umgeht damit gezielt
die stdout/stderr-Umleitung des Moduls (image_deploy.sh).
2. "Wenn ich... mit Enter den Neustart auslösen möchte, kommt es zu
zahlreichen squashfs-Fehlern, weil das Dateisystem nicht mehr da ist."
- "toram" im Bootappend (build_boot_medium.sh) kopiert das gesamte
Boot-Medium beim Start einmalig in eine Tmpfs; danach ist der Stick fuer
den laufenden Betrieb komplett irrelevant, ein Ziehen jederzeit
gefahrlos moeglich - vorher blieb das laufende System per Squashfs+
Loop-Mount direkt vom physischen Medium abhaengig.
3. Neues Boot-Banner (pics/tuxflotte-terminal-80neu.txt).
Beim eigenen Testen von Punkt 1 zwei echte Bugs gefunden+behoben (siehe
Kommentare in image_deploy.sh): "pipeline || true" ueberschreibt
PIPESTATUS mit dem einelementigen Ergebnis von "true", sobald die
Pipeline fehlschlaegt - traf live bei einer echten Netzwerkunterbrechung
waehrend des Downloads auf ("pipe_status[1]: unbound variable" unter
"set -u"). Der Wechsel auf eine "if"-Bedingung (set -e-Ausnahme ohne ein
zweites Kommando) reichte allein nicht, PIPESTATUS blieb bei einem
echten, mitten im Transfer abgebrochenen curl|tar manchmal trotzdem
unvollstaendig - jetzt zusaetzlich robust mit ":-1"-Fallback direkt auf
den einzelnen Indizes statt einer vorausgesetzten vollstaendigen Kopie.
Live in QEMU verifiziert: neues Banner + verborgenes Partitionier-Rauschen
+ sichtbarer Fortschrittsbalken bestaetigt (Screenshot). Eine echte,
waehrend des Testens aufgetretene Netzwerk-Durchsatzverschlechterung
(anode nur noch ~4,6 MB/s, unabhaengig von diesem Code) verhinderte einen
weiteren vollstaendigen Erfolgsdurchlauf in dieser Session - der
pipe_status-Fix selbst wurde aber genau durch diese Unterbrechung bestaetigt
(sauberer [FEHLER]-Abbruch mit klarer Meldung statt Bash-Crash). toram
selbst (Boot bis zum Auto-Modus-Start, Konfiguration/Partitionierung liefen
durch) und die Rauschen-Unterdrueckung sind bestaetigt; die volle
Stick-waehrend-Reboot-Sequenz bleibt fuer den naechsten echten
Hardware-Test zu bestaetigen.
458 lines
20 KiB
Bash
458 lines
20 KiB
Bash
#!/usr/bin/env bash
|
|
# Kernmechanik des Golden-Image-Deployments (siehe ADR-0024 und
|
|
# platform-docs-Plan "Golden-Image-Deployment statt Ubiquity-
|
|
# Automatisierung"). Wird von source eingebunden - nicht eigenstaendig
|
|
# ausfuehrbar. Ersetzt den bisherigen Ansatz "nativen Distributions-
|
|
# Installer automatisieren" (Ubiquity/Anaconda) durch das curtin/FAI-
|
|
# Muster: Zieldatentraeger direkt partitionieren, ein fertiges
|
|
# Root-Filesystem-Image entpacken, per chroot nacharbeiten
|
|
# (fstab/initramfs/machine-id/SSH-Hostkeys/Bootloader).
|
|
#
|
|
# Bewusst als eigenstaendige Funktionsbibliothek (wie lib/initrd.sh,
|
|
# lib/checks.sh) statt einer einzigen "mach alles"-Funktion - sowohl der
|
|
# isolierte Phase-1-Testtreiber als auch das spaetere
|
|
# backends/mint-image/backend.sh rufen dieselben Bausteine in derselben
|
|
# Reihenfolge auf.
|
|
|
|
image_deploy_log() {
|
|
printf '[image_deploy] %s\n' "$*" >&2
|
|
}
|
|
|
|
image_deploy_fatal() {
|
|
printf '[image_deploy] FEHLER: %s\n' "$*" >&2
|
|
return 1
|
|
}
|
|
|
|
# Nvme/mmcblk-Geraete brauchen ein "p" vor der Partitionsnummer
|
|
# (/dev/nvme0n1p1), sd/vd-Geraete nicht (/dev/sda1) - Heuristik: Geraete-
|
|
# name endet auf eine Ziffer -> braucht "p".
|
|
_image_deploy_part_suffix() {
|
|
local disk="$1"
|
|
if [[ "${disk}" =~ [0-9]$ ]]; then
|
|
echo "p"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
image_deploy_partition_path() {
|
|
local disk="$1"
|
|
local num="$2"
|
|
echo "${disk}$(_image_deploy_part_suffix "${disk}")${num}"
|
|
}
|
|
|
|
# Partitioniert den Zieldatentraeger komplett neu (GPT, wie schon in
|
|
# _tuxflotte_render_partman_recipe() begruendet - auch ohne EFI legt GPT
|
|
# an, siehe dortiger Kommentar zu bios_grub). Gibt "BOOT_PART ROOT_PART"
|
|
# auf stdout aus - BOOT_PART ist bei BIOS leer (die biosgrub-Partition
|
|
# braucht kein Dateisystem/keinen Mountpunkt).
|
|
# Vorhandene Partitionen der Zielplatte vor dem Neupartitionieren freigeben
|
|
# (unmounten, Swap deaktivieren) - live auf echter Hardware gefunden
|
|
# (01.09.2026): parted schreibt eine neue Partitionstabelle zwar trotzdem
|
|
# auf die Platte, meldet aber einen Fehler ("...have been unable to inform
|
|
# the Kernel of the change... probably because it/they are in use"), wenn
|
|
# eine alte Partition (z.B. Swap oder ein gemountetes Dateisystem vom
|
|
# vorherigen Betriebssystem auf dieser Platte) noch belegt ist. In QEMU mit
|
|
# stets frischen Testplatten nie aufgetreten - reale Kundenhardware hat
|
|
# aber fast immer schon ein Vorleben auf der Platte.
|
|
_image_deploy_release_disk() {
|
|
local disk="$1"
|
|
local part
|
|
|
|
for part in "${disk}"?*; do
|
|
[[ -b "${part}" ]] || continue
|
|
swapoff "${part}" 2>/dev/null || true
|
|
umount --recursive "${part}" 2>/dev/null || true
|
|
done
|
|
}
|
|
|
|
# Manche Kernel-/Platten-Kombinationen brauchen mehrere Anlaeufe, bis das
|
|
# Neueinlesen der Partitionstabelle (BLKRRPART) greift, gerade direkt nach
|
|
# einem mklabel auf einer zuvor belegten Platte - deshalb mehrfach
|
|
# probieren statt nach einem einzigen partprobe-Aufruf aufzugeben.
|
|
_image_deploy_settle_partitions() {
|
|
local disk="$1"
|
|
local attempt
|
|
|
|
for attempt in 1 2 3 4 5; do
|
|
if partprobe "${disk}" 2>/dev/null; then
|
|
udevadm settle
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
udevadm settle
|
|
return 1
|
|
}
|
|
|
|
image_deploy_partition() {
|
|
local disk="$1"
|
|
local is_efi="$2"
|
|
|
|
_image_deploy_release_disk "${disk}"
|
|
|
|
if ! parted --script "${disk}" mklabel gpt; then
|
|
image_deploy_log "parted meldete einen Fehler beim Anlegen der Partitionstabelle auf ${disk} (haeufig: eine alte Partition war noch belegt) - pruefe, ob der Kernel die neue Tabelle trotzdem uebernimmt."
|
|
fi
|
|
|
|
_image_deploy_settle_partitions "${disk}" ||
|
|
{ image_deploy_fatal "Kernel konnte die neue Partitionstabelle auf ${disk} auch nach mehreren Versuchen nicht uebernehmen."; return 1; }
|
|
|
|
parted --script "${disk}" print 2>/dev/null | grep -q "Partition Table: gpt" ||
|
|
{ image_deploy_fatal "Nach dem Partitionieren liegt kein gueltiges GPT-Label auf ${disk} vor."; return 1; }
|
|
|
|
if [[ "${is_efi}" == "true" ]]; then
|
|
parted --script "${disk}" mkpart ESP fat32 1MiB 513MiB &&
|
|
parted --script "${disk}" set 1 esp on &&
|
|
parted --script "${disk}" mkpart root ext4 513MiB 100% ||
|
|
{ image_deploy_fatal "Partitionierung (UEFI) fehlgeschlagen auf ${disk}"; return 1; }
|
|
else
|
|
parted --script "${disk}" mkpart biosgrub 1MiB 2MiB &&
|
|
parted --script "${disk}" set 1 bios_grub on &&
|
|
parted --script "${disk}" mkpart root ext4 2MiB 100% ||
|
|
{ image_deploy_fatal "Partitionierung (BIOS) fehlgeschlagen auf ${disk}"; return 1; }
|
|
fi
|
|
|
|
_image_deploy_settle_partitions "${disk}" ||
|
|
{ image_deploy_fatal "Kernel konnte die neuen Partitionen auf ${disk} nicht uebernehmen."; return 1; }
|
|
|
|
local part1 part2
|
|
part1="$(image_deploy_partition_path "${disk}" 1)"
|
|
part2="$(image_deploy_partition_path "${disk}" 2)"
|
|
|
|
[[ -b "${part1}" && -b "${part2}" ]] ||
|
|
{ image_deploy_fatal "Partitionen nach partprobe nicht gefunden (${part1}, ${part2})"; return 1; }
|
|
|
|
if [[ "${is_efi}" == "true" ]]; then
|
|
echo "${part1} ${part2}"
|
|
else
|
|
echo " ${part2}"
|
|
fi
|
|
}
|
|
|
|
image_deploy_format() {
|
|
local boot_part="$1"
|
|
local root_part="$2"
|
|
local root_fs="$3"
|
|
|
|
if [[ -n "${boot_part}" ]]; then
|
|
mkfs.vfat -F32 -n ESP "${boot_part}" ||
|
|
{ image_deploy_fatal "mkfs.vfat auf ${boot_part} fehlgeschlagen"; return 1; }
|
|
fi
|
|
|
|
case "${root_fs}" in
|
|
ext4) mkfs.ext4 -F -L root "${root_part}" ;;
|
|
btrfs) mkfs.btrfs -f -L root "${root_part}" ;;
|
|
*) image_deploy_fatal "Nicht unterstuetztes Root-Dateisystem: ${root_fs}"; return 1 ;;
|
|
esac || { image_deploy_fatal "mkfs auf ${root_part} fehlgeschlagen"; return 1; }
|
|
}
|
|
|
|
image_deploy_mount() {
|
|
local target="$1"
|
|
local boot_part="$2"
|
|
local root_part="$3"
|
|
|
|
install -d "${target}"
|
|
mount "${root_part}" "${target}" ||
|
|
{ image_deploy_fatal "Root-Partition ${root_part} konnte nicht auf ${target} gemountet werden"; return 1; }
|
|
|
|
if [[ -n "${boot_part}" ]]; then
|
|
install -d "${target}/boot/efi"
|
|
mount "${boot_part}" "${target}/boot/efi" ||
|
|
{ image_deploy_fatal "ESP ${boot_part} konnte nicht gemountet werden"; return 1; }
|
|
fi
|
|
}
|
|
|
|
# package_golden_image.sh schliesst /proc, /sys, /dev, /run, /tmp, /var/tmp
|
|
# bewusst aus dem Archiv aus (Laufzeit-Zustand der Referenz-VM, nicht Teil
|
|
# eines portablen Images). Nach dem Entpacken fehlen diese Verzeichnisse
|
|
# deshalb komplett - live gefunden (31.08.2026, erster echter Lauf mit dem
|
|
# echten Referenz-VM-Archiv): erst "mount --bind /dev" ("mount point does
|
|
# not exist"), dann - nach Behebung dessen - "mktemp: failed to create
|
|
# directory via template '/var/tmp/mkinitramfs_XXXXXX'" bei
|
|
# update-initramfs. Statt jedes Fehlen einzeln zu jagen: alle sechs
|
|
# ausgeschlossenen Verzeichnisse direkt nach dem Entpacken einmalig mit
|
|
# sinnvollen Rechten wiederherstellen. /dev, /proc, /sys werden gleich
|
|
# danach ohnehin ueberbindmounted (image_deploy_bind_mounts) - schadet
|
|
# aber nicht, sie schon hier als leere Verzeichnisse anzulegen.
|
|
image_deploy_restore_excluded_dirs() {
|
|
local target="$1"
|
|
|
|
mkdir -p "${target}"/{proc,sys,dev,run,tmp,var/tmp}
|
|
chmod 1777 "${target}/tmp" "${target}/var/tmp"
|
|
}
|
|
|
|
# Entpackt das per build_golden_image.sh gebaute Golden Image direkt in
|
|
# den gemounteten Zielbaum. -p erhaelt Rechte/Eigentuemer, --zstd deckt
|
|
# sowohl .tar.zst als auch (falls spaeter gewechselt) andere
|
|
# zstd-komprimierte Varianten ab.
|
|
image_deploy_extract_image() {
|
|
local image_path="$1"
|
|
local target="$2"
|
|
|
|
[[ -r "${image_path}" ]] ||
|
|
{ image_deploy_fatal "Golden Image nicht lesbar: ${image_path}"; return 1; }
|
|
|
|
tar --zstd -xpf "${image_path}" -C "${target}" ||
|
|
{ image_deploy_fatal "Golden Image konnte nicht nach ${target} entpackt werden"; return 1; }
|
|
|
|
image_deploy_restore_excluded_dirs "${target}"
|
|
}
|
|
|
|
# Streamt das Golden Image direkt vom Server in die Extraktion, ohne es
|
|
# jemals als Ganzes zwischenzuspeichern - live gefunden (31.08.2026,
|
|
# erster echter End-to-End-Lauf mit dem vollen 2,3-GB-Referenz-VM-Archiv
|
|
# ueber das neue Boot-Medium): das bisherige "erst nach /run/tuxflotte/...
|
|
# herunterladen, dann entpacken" scheiterte an /run (RAM-Tmpfs, oft nur
|
|
# ein paar hundert MB gross, unabhaengig von der Geraete-RAM-Groesse) -
|
|
# "curl: (23) Failure writing output to destination". Direktes
|
|
# Zusammenrohren braucht dagegen nur ein paar MB Puffer, unabhaengig von
|
|
# der Archivgroesse. PIPESTATUS wird explizit geprueft statt sich auf
|
|
# "pipefail"-Semantik (rechteste fehlgeschlagene Stufe gewinnt) zu
|
|
# verlassen, damit die Fehlermeldung eindeutig zwischen Download- und
|
|
# Entpack-Fehler unterscheidet.
|
|
image_deploy_extract_image_from_url() {
|
|
local url="$1"
|
|
local target="$2"
|
|
|
|
# "--progress-bar" statt "--silent": mit Abstand der laengste Schritt
|
|
# der ganzen Installation (Minuten statt Sekunden) - Nutzer-Feedback
|
|
# (01.09.2026, echter Hardware-Test) wollte hier ausdruecklich eine
|
|
# sichtbare Fortschrittsanzeige statt einer stillen Wartezeit.
|
|
#
|
|
# run_module_backend() (utils.sh) faengt inzwischen auch die Ausgabe von
|
|
# 40_backend.sh ab (Partitionieren/Formatieren/chroot-Fixup/... sollen
|
|
# wie jedes andere Modul verborgen bleiben, zweites Nutzer-Feedback
|
|
# 01.09.2026) - der Fortschrittsbalken selbst soll trotzdem sichtbar
|
|
# bleiben, deshalb schreibt curl ihn hier direkt auf /dev/tty1, statt
|
|
# ueber die normale stderr-Umleitung des Moduls zu laufen (die landet
|
|
# sonst nur in der Logdatei, nicht auf der Konsole). Nur wenn
|
|
# TUXFLOTTE_TTY_LIVE gesetzt ist UND /dev/tty1 tatsaechlich beschreibbar
|
|
# ist (z.B. nicht bei einem manuellen Testlauf per SSH) - sonst normaler
|
|
# stderr-Weg. Nebenwirkung: curls eigene Fehlermeldung ("curl: (22)...")
|
|
# landet im Fehlerfall dann ebenfalls direkt auf der Konsole statt im
|
|
# Fehlschlag-Dump - image_deploy_fatal() liefert trotzdem einen klaren
|
|
# eigenen Fehlertext.
|
|
local progress_target="/dev/stderr"
|
|
if [[ "${TUXFLOTTE_TTY_LIVE:-}" == "true" && -w /dev/tty1 ]]; then
|
|
progress_target="/dev/tty1"
|
|
fi
|
|
|
|
# Echter Bug, zweimal live gefunden (02.09.2026, durch eine echte
|
|
# Netzwerkunterbrechung waehrend des Downloads aufgedeckt): "pipeline ||
|
|
# true" laesst "true" als eigene Ein-Kommando-Pipeline laufen, sobald die
|
|
# linke Seite fehlschlaegt - das ueberschreibt PIPESTATUS mit "(0)". Der
|
|
# Wechsel auf "if pipeline; then :; fi" (set -e-Ausnahme ohne ein
|
|
# zweites Kommando dazwischen) reichte allein aber NICHT aus: bei einem
|
|
# echten, mitten im Transfer abgebrochenen curl|tar blieb PIPESTATUS
|
|
# trotzdem manchmal unvollstaendig (genauer Mechanismus nicht abschliessend
|
|
# geklaert - vermutlich eine Rennsituation zwischen curls eigenem
|
|
# Fehler-Reporting und tars sofortigem "Unexpected EOF"-Abbruch). Deshalb
|
|
# jetzt zusaetzlich mit ":-1"-Fallback direkt auf den einzelnen
|
|
# PIPESTATUS-Indizes statt eine komplette Kopie vorauszusetzen - robust
|
|
# unabhaengig davon, wie viele Elemente PIPESTATUS tatsaechlich liefert.
|
|
if curl --progress-bar --show-error --fail --location "${url}" 2>"${progress_target}" |
|
|
tar --zstd -xpf - -C "${target}"; then
|
|
:
|
|
fi
|
|
local curl_status="${PIPESTATUS[0]:-1}"
|
|
local tar_status="${PIPESTATUS[1]:-1}"
|
|
|
|
[[ "${curl_status}" -eq 0 ]] ||
|
|
{ image_deploy_fatal "Golden Image konnte nicht geladen werden: ${url}"; return 1; }
|
|
[[ "${tar_status}" -eq 0 ]] ||
|
|
{ image_deploy_fatal "Golden Image konnte nicht nach ${target} entpackt werden"; return 1; }
|
|
|
|
image_deploy_restore_excluded_dirs "${target}"
|
|
}
|
|
|
|
# /etc/fstab aus den tatsaechlichen Partitions-UUIDs neu erzeugen - kein
|
|
# genfstab auf Debian/Mint verfuegbar (das ist ein Arch-Linux-Werkzeug),
|
|
# blkid reicht fuer unseren einfachen Fall (Root + optional ESP) locker.
|
|
# Uebernimmt das vom Netzwerkmodul exportierte, aktive NetworkManager-
|
|
# Verbindungsprofil (siehe export_connection_profile() in 05_network.sh)
|
|
# ins Zielsystem - ohne diesen Schritt bleibt das Profil nur unter
|
|
# /run/tuxflotte/network/... auf dem Boot-Medium liegen und geht beim
|
|
# Reboot ins frisch installierte System verloren. Live gefunden
|
|
# (01.09.2026, echter Hardware-Test): Kunde musste den WLAN-PSK nach dem
|
|
# ersten Boot erneut eingeben, weil genau dieser Kopierschritt fehlte.
|
|
# Keine Fatal-Bedingung, falls die Datei fehlt - das Provisionierungs-
|
|
# interface kann auch unmanaged/kabelgebunden ohne Profil gelaufen sein.
|
|
image_deploy_install_network_profile() {
|
|
local target="$1"
|
|
local profile_file="$2"
|
|
local dest_dir="${target}/etc/NetworkManager/system-connections"
|
|
local dest_name
|
|
|
|
[[ -n "${profile_file}" && -f "${profile_file}" ]] || return 0
|
|
|
|
dest_name="$(basename "${profile_file}")"
|
|
|
|
install -d --mode=0700 --owner=root --group=root "${dest_dir}" ||
|
|
{ image_deploy_fatal "Verzeichnis ${dest_dir} konnte nicht angelegt werden"; return 1; }
|
|
|
|
install --mode=0600 --owner=root --group=root \
|
|
"${profile_file}" "${dest_dir}/${dest_name}" ||
|
|
{ image_deploy_fatal "Verbindungsprofil konnte nicht nach ${dest_dir} kopiert werden"; return 1; }
|
|
}
|
|
|
|
image_deploy_write_fstab() {
|
|
local target="$1"
|
|
local boot_part="$2"
|
|
local root_part="$3"
|
|
local root_fs="$4"
|
|
|
|
local root_uuid
|
|
root_uuid="$(blkid -s UUID -o value "${root_part}")"
|
|
[[ -n "${root_uuid}" ]] ||
|
|
{ image_deploy_fatal "Keine UUID fuer ${root_part} gefunden"; return 1; }
|
|
|
|
{
|
|
echo "# Von image_deploy.sh generiert - siehe ADR-0024."
|
|
echo "UUID=${root_uuid} / ${root_fs} defaults 0 1"
|
|
if [[ -n "${boot_part}" ]]; then
|
|
local boot_uuid
|
|
boot_uuid="$(blkid -s UUID -o value "${boot_part}")"
|
|
[[ -n "${boot_uuid}" ]] ||
|
|
{ image_deploy_fatal "Keine UUID fuer ${boot_part} gefunden"; return 1; }
|
|
echo "UUID=${boot_uuid} /boot/efi vfat umask=0077 0 1"
|
|
fi
|
|
} > "${target}/etc/fstab"
|
|
}
|
|
|
|
# /dev, /proc, /sys, /dev/pts in den Zielbaum einbinden (fuer chroot-
|
|
# Operationen, die Geraeteknoten/Kernel-Interfaces brauchen - initramfs,
|
|
# grub-install) sowie eine funktionierende DNS-Aufloesung fuers
|
|
# apt-get-basierte Bootloader-Nachinstallieren. MOUNTED_STACK wird vom
|
|
# aufrufenden Skript vorgehalten (siehe image_deploy_unbind_mounts()).
|
|
image_deploy_bind_mounts() {
|
|
local target="$1"
|
|
local -n stack_ref="$2"
|
|
|
|
# Golden Image schliesst /dev, /proc, /sys bewusst aus
|
|
# (package_golden_image.sh --exclude=dev/sys/proc) - die Zielverzeichnisse
|
|
# existieren nach dem Entpacken deshalb noch nicht und muessen vor dem
|
|
# Bind-Mount erst angelegt werden. Live gefunden (31.08.2026, erster
|
|
# echter Lauf mit der echten Mint-Referenz-VM): ohne das schlaegt
|
|
# "mount --bind" mit "mount point does not exist" fehl - und zwar
|
|
# SILENT, weil das bisherige "cmd && stack_ref+=(...)"-Muster einen
|
|
# Fehlschlag von "cmd" unter "set -e" nicht als Statement-Fehler wertet
|
|
# (wohlbekannte Bash-Ausnahme fuer &&/||-Ketten) - der fehlende
|
|
# /dev-Bind-Mount fiel dann erst beim naechsten chroot-Aufruf auf
|
|
# ("ssh-keygen -A: Couldn't open /dev/null"), weit weg von der
|
|
# eigentlichen Ursache. Jetzt: Zielverzeichnisse anlegen, jeden
|
|
# Mount-Fehler explizit und einzeln pruefen statt auf &&-Verkettung
|
|
# zu vertrauen.
|
|
mkdir -p "${target}/dev" "${target}/proc" "${target}/sys" "${target}/dev/pts"
|
|
|
|
mount --bind /dev "${target}/dev" ||
|
|
{ image_deploy_fatal "/dev konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/dev")
|
|
|
|
mount -t proc proc "${target}/proc" ||
|
|
{ image_deploy_fatal "/proc konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/proc")
|
|
|
|
mount -t sysfs sysfs "${target}/sys" ||
|
|
{ image_deploy_fatal "/sys konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/sys")
|
|
|
|
mount -t devpts devpts "${target}/dev/pts" ||
|
|
{ image_deploy_fatal "/dev/pts konnte nicht eingebunden werden"; return 1; }
|
|
stack_ref+=("${target}/dev/pts")
|
|
|
|
# Live gefunden (31.08.2026, erster echter Lauf mit der echten
|
|
# Mint-Referenz-VM statt eines debootstrap-Test-Images): dort ist
|
|
# /etc/resolv.conf ein von NetworkManager verwalteter Symlink (z.B. auf
|
|
# /run/NetworkManager/resolv.conf) - im gerade erst entpackten,
|
|
# ungebooteten Zielbaum zeigt der ins Leere, "cp" verweigert dann das
|
|
# Schreiben "through a dangling symlink". Ziel deshalb vorher entfernen
|
|
# (Symlink oder Datei, beides abgedeckt), erst dann kopieren.
|
|
rm -f "${target}/etc/resolv.conf"
|
|
cp /etc/resolv.conf "${target}/etc/resolv.conf" ||
|
|
{ image_deploy_fatal "resolv.conf konnte nicht nach ${target} kopiert werden"; return 1; }
|
|
}
|
|
|
|
image_deploy_unbind_mounts() {
|
|
local -n stack_ref="$1"
|
|
local i mnt
|
|
|
|
for ((i = ${#stack_ref[@]} - 1; i >= 0; i--)); do
|
|
mnt="${stack_ref[i]}"
|
|
umount --recursive "${mnt}" 2>/dev/null || umount --lazy "${mnt}" 2>/dev/null || true
|
|
done
|
|
stack_ref=()
|
|
}
|
|
|
|
# machine-id + SSH-Hostkeys werden im Golden Image bewusst NICHT
|
|
# mitgeliefert (siehe build_golden_image.sh) - hier, pro tatsaechlich
|
|
# ausgerolltem Geraet, frisch erzeugt. update-initramfs regeneriert das
|
|
# initrd fuer die tatsaechliche Zielhardware (Golden Image wurde auf
|
|
# einem anderen System gebaut).
|
|
image_deploy_chroot_fixup() {
|
|
local target="$1"
|
|
|
|
chroot "${target}" systemd-machine-id-setup ||
|
|
{ image_deploy_fatal "machine-id konnte nicht erzeugt werden"; return 1; }
|
|
chroot "${target}" ssh-keygen -A ||
|
|
{ image_deploy_fatal "SSH-Hostkeys konnten nicht erzeugt werden"; return 1; }
|
|
chroot "${target}" update-initramfs -u -k all ||
|
|
{ image_deploy_fatal "initramfs-Regenerierung fehlgeschlagen"; return 1; }
|
|
}
|
|
|
|
# Grub wird bewusst NICHT ins Golden Image eingebaut (spart Platz, das
|
|
# Image kennt beim Bauen das Zielfirmware-Schema noch nicht) - stattdessen
|
|
# hier zur Deployment-Zeit nachinstalliert, wo bereits Netzwerk verfuegbar
|
|
# ist (Stufe-1-Umgebung hat sich schon fuer die Aktivierung verbunden).
|
|
image_deploy_install_bootloader() {
|
|
local target="$1"
|
|
local disk="$2"
|
|
local is_efi="$3"
|
|
|
|
if [[ "${is_efi}" == "true" ]]; then
|
|
chroot "${target}" /bin/bash -c '
|
|
set -e
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update
|
|
apt-get install -y grub-efi-amd64
|
|
' || { image_deploy_fatal "grub-efi-amd64-Installation fehlgeschlagen"; return 1; }
|
|
chroot "${target}" grub-install --target=x86_64-efi \
|
|
--efi-directory=/boot/efi --bootloader-id=tuxflotte --recheck ||
|
|
{ image_deploy_fatal "grub-install (UEFI) fehlgeschlagen"; return 1; }
|
|
# Zusaetzlich auf den Standard-Fallback-Pfad (EFI/BOOT/BOOTX64.EFI)
|
|
# installieren - real beim Testen entdeckt: grub-install kann in
|
|
# verschachtelten/eingeschraenkten Umgebungen keinen NVRAM-
|
|
# Booteintrag setzen ("EFI variables are not supported on this
|
|
# system"), ohne --removable bleibt dann NUR der benannte
|
|
# /EFI/tuxflotte/-Pfad uebrig, den die Firmware ohne NVRAM-Eintrag
|
|
# nie findet ("No bootable option or device was found"). Der
|
|
# Fallback-Pfad wird von JEDER UEFI-Firmware ohne NVRAM-Eintrag
|
|
# automatisch versucht - robuster fuer heterogene Zielhardware
|
|
# generell, nicht nur fuer dieses Testszenario.
|
|
chroot "${target}" grub-install --target=x86_64-efi \
|
|
--efi-directory=/boot/efi --removable --recheck ||
|
|
{ image_deploy_fatal "grub-install (UEFI, removable-Fallback) fehlgeschlagen"; return 1; }
|
|
else
|
|
chroot "${target}" /bin/bash -c '
|
|
set -e
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update
|
|
apt-get install -y grub-pc
|
|
' || { image_deploy_fatal "grub-pc-Installation fehlgeschlagen"; return 1; }
|
|
chroot "${target}" grub-install --target=i386-pc --recheck "${disk}" ||
|
|
{ image_deploy_fatal "grub-install (BIOS) fehlgeschlagen"; return 1; }
|
|
fi
|
|
|
|
chroot "${target}" update-grub ||
|
|
{ image_deploy_fatal "update-grub fehlgeschlagen"; return 1; }
|
|
}
|
|
|
|
image_deploy_set_hostname() {
|
|
local target="$1"
|
|
local hostname="$2"
|
|
|
|
echo "${hostname}" > "${target}/etc/hostname"
|
|
printf '127.0.1.1\t%s\n' "${hostname}" >> "${target}/etc/hosts"
|
|
}
|