#!/usr/bin/env bash set -Eeuo pipefail # Dieses Skript wird von einem Orchestrator-Modul (z.B. 40_backend.sh) per # `source` in dessen Shell geladen. Variablen bleiben deshalb bewusst nicht # readonly, um Namenskollisionen mit dem ladenden Modul zu vermeiden. BACKEND_KEY="mint" BACKEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PRESEED_TEMPLATE="${BACKEND_DIR}/preseed.tpl" POSTINSTALL_SCRIPT="${BACKEND_DIR}/postinstall.sh" RUNTIME_BLUEPRINT_FILE="/run/tuxflotte/runtime/runtime_blueprint.json" SERVER_RESPONSE_FILE="/run/tuxflotte/server/response.json" RUNTIME_DIR="/run/tuxflotte/backend" CONFIG_FILE="${RUNTIME_DIR}/config" backend_log() { printf '[backend:%s] %s\n' "${BACKEND_KEY}" "$*" >&2 } backend_fatal() { printf '[backend:%s] FEHLER: %s\n' "${BACKEND_KEY}" "$*" >&2 return 1 } backend_init() { for cmd in jq envsubst base64; do command -v "${cmd}" >/dev/null 2>&1 || { backend_fatal "Benötigtes Werkzeug fehlt: ${cmd}"; return 1; } done # kexec-tools ist auf dem Live-Medium selbst (anders als im Zielsystem, # siehe pkgsel/include in preseed.tpl) nicht vorinstalliert - real gegen # das Live-Abbild verifiziert (Phase-1-Spike). Bis das Paket fest in die # ISO gebacken wird (Folgeschritt), hier als Live-Nachinstallation. if ! command -v kexec >/dev/null 2>&1; then backend_log "kexec-tools fehlt auf dem Live-Medium, installiere nach." sed -i '/^deb cdrom/d' /etc/apt/sources.list 2>/dev/null || true rm -f /etc/apt/sources.list.d/*cdrom* 2>/dev/null || true apt-get update -qq || { backend_fatal "apt-get update fehlgeschlagen (kexec-tools)."; return 1; } DEBIAN_FRONTEND=noninteractive apt-get install -y kexec-tools || { backend_fatal "Installation von kexec-tools fehlgeschlagen."; return 1; } fi [[ -r "${PRESEED_TEMPLATE}" ]] || { backend_fatal "Preseed-Template nicht gefunden: ${PRESEED_TEMPLATE}"; return 1; } [[ -r "${POSTINSTALL_SCRIPT}" ]] || { backend_fatal "Postinstall-Skript nicht gefunden: ${POSTINSTALL_SCRIPT}"; return 1; } install -d \ --mode=0700 \ --owner=root \ --group=root \ "${RUNTIME_DIR}" rm -f -- "${CONFIG_FILE}" backend_log "Initialisiert." } backend_validate() { [[ -r "${RUNTIME_BLUEPRINT_FILE}" ]] || { backend_fatal "Runtime Blueprint nicht gefunden: ${RUNTIME_BLUEPRINT_FILE}"; return 1; } jq --exit-status \ --arg backend_key "${BACKEND_KEY}" \ '.runtime_blueprint.backend_id == $backend_key' \ "${RUNTIME_BLUEPRINT_FILE}" >/dev/null || { backend_fatal "Runtime Blueprint ist nicht für Backend '${BACKEND_KEY}' aufgelöst."; return 1; } jq --exit-status ' .runtime_blueprint.installation_directives | (.disk_encryption | type == "boolean") and (.partitioning | type == "string") and (.secure_boot_required | type == "boolean") ' "${RUNTIME_BLUEPRINT_FILE}" >/dev/null || { backend_fatal "Installationszeitliche Vorgaben fehlen oder sind ungültig."; return 1; } # disk_encryption wird für Mint (noch) nicht unterstützt - kein getesteter # LUKS-Preseed-Mechanismus (anders als Fedoras "autopart --encrypted"). if [[ "$(jq --raw-output '.runtime_blueprint.installation_directives.disk_encryption' "${RUNTIME_BLUEPRINT_FILE}")" == "true" ]]; then backend_fatal "disk_encryption=true wird vom Mint-Backend derzeit nicht unterstützt." return 1 fi backend_log "Runtime Blueprint ist gültig für Backend '${BACKEND_KEY}'." } backend_generate_config() { local hostname local device_id local partitioning local secure_boot_required local partman_recipe local blueprints_json local postinstall_rendered local postinstall_b64 [[ -r "${SERVER_RESPONSE_FILE}" ]] || { backend_fatal "Serverantwort nicht gefunden: ${SERVER_RESPONSE_FILE}"; return 1; } hostname="$(jq --raw-output '.device.hostname // empty' "${SERVER_RESPONSE_FILE}")" [[ -n "${hostname}" ]] || { backend_fatal "Kein Hostname in der Serverantwort gefunden."; return 1; } device_id="$(jq --raw-output '.device.id // empty' "${SERVER_RESPONSE_FILE}")" [[ -n "${device_id}" ]] || { backend_fatal "Keine Geräte-ID in der Serverantwort gefunden."; return 1; } partitioning="$(jq --raw-output '.runtime_blueprint.installation_directives.partitioning' "${RUNTIME_BLUEPRINT_FILE}")" secure_boot_required="$(jq --raw-output '.runtime_blueprint.installation_directives.secure_boot_required' "${RUNTIME_BLUEPRINT_FILE}")" case "${partitioning}" in default) partman_recipe="atomic" ;; *) backend_fatal "Nicht unterstützte Partitionierungsvorgabe: ${partitioning}" return 1 ;; esac if [[ "${secure_boot_required}" == "true" ]]; then backend_log "Hinweis: secure_boot_required=true wird derzeit nicht in der Preseed-Konfiguration durchgesetzt (Phase 1)." fi blueprints_json="$(jq --compact-output '.runtime_blueprint.blueprints' "${RUNTIME_BLUEPRINT_FILE}")" # Erste Stufe: postinstall.sh-Platzhalter auflösen. postinstall_rendered="$( TUXFLOTTE_DEVICE_ID="${device_id}" \ TUXFLOTTE_BLUEPRINTS_JSON="${blueprints_json}" \ envsubst '${TUXFLOTTE_DEVICE_ID} ${TUXFLOTTE_BLUEPRINTS_JSON}' \ <"${POSTINSTALL_SCRIPT}" )" if grep -q '\${TUXFLOTTE_' <<<"${postinstall_rendered}"; then backend_fatal "postinstall.sh enthält nach envsubst nicht aufgelöste Platzhalter." return 1 fi # base64-Kodierung: mehrzeilige/zitierte Preseed-Werte brechen unter # Debconf lautlos (real erprobt, siehe backends/mint/wlan-test.seed) - # als einzeiliger Base64-Blob besteht der success_command-Wert nur noch # aus unkritischen Zeichen. postinstall_b64="$(printf '%s' "${postinstall_rendered}" | base64 -w0)" # Zweite Stufe: preseed.tpl mit allen Werten inkl. des fertigen Base64-Blobs auflösen. TUXFLOTTE_HOSTNAME="${hostname}" \ TUXFLOTTE_PARTMAN_RECIPE="${partman_recipe}" \ TUXFLOTTE_POSTINSTALL_B64="${postinstall_b64}" \ envsubst '${TUXFLOTTE_HOSTNAME} ${TUXFLOTTE_PARTMAN_RECIPE} ${TUXFLOTTE_POSTINSTALL_B64}' \ <"${PRESEED_TEMPLATE}" >"${CONFIG_FILE}" chmod 0600 "${CONFIG_FILE}" [[ -s "${CONFIG_FILE}" ]] || { backend_fatal "Erzeugte Konfigurationsdatei ist leer: ${CONFIG_FILE}"; return 1; } if grep -q '\${TUXFLOTTE_' "${CONFIG_FILE}"; then backend_fatal "Erzeugte Konfigurationsdatei enthält nicht aufgelöste Platzhalter." return 1 fi backend_log "Konfiguration erzeugt: ${CONFIG_FILE}" } backend_launch() { local cdrom_vmlinuz="/cdrom/casper/vmlinuz" local cdrom_initrd="/cdrom/casper/initrd.lz" local extra_initrd_dir="${RUNTIME_DIR}/initrd-extra" local extra_cpio="${RUNTIME_DIR}/extra.cpio.gz" local custom_initrd="${RUNTIME_DIR}/initrd-custom.lz" [[ -r "${cdrom_vmlinuz}" && -r "${cdrom_initrd}" ]] || { backend_fatal "Casper-Kernel/-Initrd nicht gefunden unter /cdrom/casper."; return 1; } # Das personalisierte Preseed (CONFIG_FILE, erst live auf diesem Gerät # erzeugt - Hostname/Geräte-ID/Partitionierung sind erst hier bekannt, # nicht schon beim ISO-Bau) kann nicht per file=/cdrom/... übergeben # werden (read-only Medium, Inhalt seit ISO-Bau fixiert) und auch nicht # per url= von einem selbst gestarteten lokalen Server - der komplette # Prozess- und Netzwerkzustand dieser Sitzung geht beim Kexec-Sprung # verloren, ein soeben gestarteter HTTP-Server koennte die neue # Boot-Umgebung also nicht mehr bedienen. Stattdessen wird das Preseed in # eine zusaetzliche Initrd-Schicht eingebettet: der Kernel unterstuetzt # aneinandergehaengte cpio-Archive als initramfs (spaetere Archive # ergaenzen fruehere), das uebersteht den Kexec-Uebergang unveraendert. # Real gegen QEMU verifiziert (Phase-1-Spike, beide Ansaetze getestet). rm -rf "${extra_initrd_dir}" install -d --mode=0700 --owner=root --group=root "${extra_initrd_dir}" cp "${CONFIG_FILE}" "${extra_initrd_dir}/preseed.cfg" (cd "${extra_initrd_dir}" && find . | cpio -o -H newc 2>/dev/null | gzip) \ >"${extra_cpio}" || { backend_fatal "Preseed-Initrd-Schicht konnte nicht gebaut werden."; return 1; } cat "${cdrom_initrd}" "${extra_cpio}" >"${custom_initrd}" || { backend_fatal "Initrd konnte nicht zusammengesetzt werden."; return 1; } backend_log "Lade Kexec-Ziel fuer automatisierten Ubiquity-Start." kexec -l "${cdrom_vmlinuz}" \ --initrd="${custom_initrd}" \ --append="boot=casper automatic-ubiquity noprompt file=/preseed.cfg debian-installer/language=de keyboard-configuration/layoutcode=de quiet splash ---" || { backend_fatal "kexec -l fehlgeschlagen."; return 1; } backend_log "Starte unbeaufsichtigte Installation (kexec -e). Kein Ruecksprung erwartet - ab hier laeuft die eigentliche Installation im neuen Kernel weiter." kexec -e } backend_postinstall() { backend_log "Provisioning-Agent-Einrichtung erfolgt im ubiquity/success_command der Preseed-Konfiguration (Agent-Abruf, Bootstrap-Registrierung, systemd-Aktivierung)." }