#!/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). image_deploy_partition() { local disk="$1" local is_efi="$2" parted --script "${disk}" mklabel gpt || { image_deploy_fatal "mklabel gpt fehlgeschlagen auf ${disk}"; 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 partprobe "${disk}" 2>/dev/null || true udevadm settle 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 } # 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; } } # /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. 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" mount --bind /dev "${target}/dev" && stack_ref+=("${target}/dev") mount -t proc proc "${target}/proc" && stack_ref+=("${target}/proc") mount -t sysfs sysfs "${target}/sys" && stack_ref+=("${target}/sys") mount -t devpts devpts "${target}/dev/pts" && stack_ref+=("${target}/dev/pts") cp /etc/resolv.conf "${target}/etc/resolv.conf" } 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; } 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" }