feat: neues Backend backends/mint-image/ (Golden-Image-Deployment, Phase 2)
Implementiert die von 40_backend.sh erwartete Fuenf-Funktionen-
Lebenszyklus-Schnittstelle (backend_init/validate/generate_config/
launch/postinstall) auf Basis der in Phase 1 isoliert verifizierten
Mechanik in scripts/lib/image_deploy.sh (siehe ADR-0024).
- backend_generate_config() liest dieselben Werte wie das bestehende
Mint-Backend (Hostname, Device-ID, Device-Fingerprint, Root-Dateisystem,
Blueprints-JSON), schreibt sie aber als konsolidierte JSON-Datei statt
eines Preseed/Kickstart-Templates.
- backend_launch() laedt das Golden Image von anode, partitioniert,
formatiert, entpackt, schreibt fstab, macht chroot-Fixup, installiert
den Bootloader.
- backend_postinstall() rendert dasselbe postinstall.sh (per Symlink
geteilt mit backends/mint/, unveraendert wiederverwendet) per envsubst,
fuehrt es per chroot aus, haengt aus, startet neu.
- backend_validate() beschraenkt sich bewusst auf das einfache
Partitionierungsschema ('single') - 'custom' mit extra_partitions ist
noch nicht auf die parted-basierte Mechanik uebertragen, klarer Fehler
statt stiller Fehlinterpretation.
backends/mint/ bleibt unveraendert als Referenz erhalten - neuer,
eigenstaendiger backend_id ('mint-image'), nichts wird live umgeschaltet.
Noch offen (Phase 3): Golden Image muss noch auf anode bereitgestellt
werden (GOLDEN_IMAGE_URL zeigt bereits dorthin), End-to-End-Test ueber
den echten Self-Service-Flow steht noch aus.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
8be6ece0d3
commit
72d7a4b27d
284
backends/mint-image/backend.sh
Normal file
284
backends/mint-image/backend.sh
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# Dieses Skript wird von einem Orchestrator-Modul (40_backend.sh) per
|
||||||
|
# `source` in dessen Shell geladen. Variablen bleiben deshalb bewusst nicht
|
||||||
|
# readonly, um Namenskollisionen mit dem ladenden Modul zu vermeiden.
|
||||||
|
#
|
||||||
|
# Golden-Image-Deployment-Backend (siehe ADR-0024) - ersetzt die
|
||||||
|
# Ubiquity-Automatisierung von backends/mint/ durch das curtin/FAI-Muster:
|
||||||
|
# Zieldatentraeger direkt partitionieren, ein fertiges Root-Filesystem-
|
||||||
|
# Image entpacken, per chroot nacharbeiten. Kein GUI-Installer, kein
|
||||||
|
# Preseed/Kickstart mehr - die eigentliche Mechanik steckt in
|
||||||
|
# scripts/lib/image_deploy.sh (Phase 1, isoliert live verifiziert).
|
||||||
|
#
|
||||||
|
# backends/mint/ bleibt unveraendert als Referenz bestehen - dieses
|
||||||
|
# Backend ist ein bewusst NEUER backend_id ("mint-image"), nichts wird
|
||||||
|
# live umgeschaltet.
|
||||||
|
BACKEND_KEY="mint-image"
|
||||||
|
|
||||||
|
BACKEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_DIR="$(cd "${BACKEND_DIR}/../.." && pwd)"
|
||||||
|
POSTINSTALL_SCRIPT="${BACKEND_DIR}/postinstall.sh"
|
||||||
|
IMAGE_DEPLOY_LIB="${REPO_DIR}/scripts/lib/image_deploy.sh"
|
||||||
|
|
||||||
|
RUNTIME_BLUEPRINT_FILE="/run/tuxflotte/runtime/runtime_blueprint.json"
|
||||||
|
SERVER_RESPONSE_FILE="/run/tuxflotte/server/response.json"
|
||||||
|
HARDWARE_FILE="/run/tuxflotte/hardware/hardware.json"
|
||||||
|
|
||||||
|
RUNTIME_DIR="/run/tuxflotte/backend"
|
||||||
|
CONFIG_FILE="${RUNTIME_DIR}/config.json"
|
||||||
|
GOLDEN_IMAGE_FILE="${RUNTIME_DIR}/golden-image.tar.zst"
|
||||||
|
|
||||||
|
# Ziel-Mountpunkt fuer die Deployment-Mechanik - global, da backend_launch()
|
||||||
|
# und backend_postinstall() (separate Funktionsaufrufe, aber dieselbe
|
||||||
|
# Shell/derselbe Prozess, siehe 40_backend.sh) sich denselben Baum teilen.
|
||||||
|
TARGET_DIR="/target"
|
||||||
|
declare -a MOUNT_STACK=()
|
||||||
|
|
||||||
|
# TODO(Phase 3): muss vor dem ersten echten Self-Service-Testlauf tatsaechlich
|
||||||
|
# auf anode bereitgestellt werden (siehe ADR-0024-Plan, "Ablage/
|
||||||
|
# Bereitstellung: ueber anode per HTTPS als statische Datei") - aktuell nur
|
||||||
|
# lokal auf enterprise gebaut und getestet.
|
||||||
|
GOLDEN_IMAGE_URL="https://anode.tuxflotte.de/golden-images/debian-trixie-base.tar.zst"
|
||||||
|
|
||||||
|
backend_log() {
|
||||||
|
printf '[backend:%s] %s\n' "${BACKEND_KEY}" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
backend_fatal() {
|
||||||
|
printf '[backend:%s] FEHLER: %s\n' "${BACKEND_KEY}" "$*" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Analog zu _tuxflotte_detect_target_disk() in backends/mint/backend.sh -
|
||||||
|
# bewusst hier dupliziert statt geteilt, um dieses Backend unabhaengig vom
|
||||||
|
# Mint-Referenzbackend zu halten (siehe Modul-Kommentar oben). Ein Umzug in
|
||||||
|
# eine gemeinsame lib waere ein sinnvolles spaeteres Aufraeumen, sobald
|
||||||
|
# mehr als zwei Backends dieselbe Logik brauchen.
|
||||||
|
_mint_image_detect_target_disk() {
|
||||||
|
lsblk --nodeps --noheadings --bytes --output NAME,TYPE,SIZE --paths |
|
||||||
|
awk '$2 == "disk" && $3 > 0 && $1 !~ /(nbd|zram|loop)[0-9]*$/ { print $1; exit }'
|
||||||
|
}
|
||||||
|
|
||||||
|
backend_init() {
|
||||||
|
local live_packages_needed=()
|
||||||
|
|
||||||
|
command -v jq >/dev/null 2>&1 || live_packages_needed+=(jq)
|
||||||
|
command -v envsubst >/dev/null 2>&1 || live_packages_needed+=(gettext-base)
|
||||||
|
command -v parted >/dev/null 2>&1 || live_packages_needed+=(parted)
|
||||||
|
command -v mkfs.vfat >/dev/null 2>&1 || live_packages_needed+=(dosfstools)
|
||||||
|
command -v mkfs.ext4 >/dev/null 2>&1 || live_packages_needed+=(e2fsprogs)
|
||||||
|
command -v zstd >/dev/null 2>&1 || live_packages_needed+=(zstd)
|
||||||
|
command -v curl >/dev/null 2>&1 || live_packages_needed+=(curl)
|
||||||
|
|
||||||
|
if [[ "${#live_packages_needed[@]}" -gt 0 ]]; then
|
||||||
|
backend_log "Werkzeuge fehlen auf dem Live-Medium, installiere nach: ${live_packages_needed[*]}"
|
||||||
|
|
||||||
|
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."; return 1; }
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y "${live_packages_needed[@]}" ||
|
||||||
|
{ backend_fatal "Installation fehlender Werkzeuge fehlgeschlagen."; return 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -r "${IMAGE_DEPLOY_LIB}" ]] ||
|
||||||
|
{ backend_fatal "Deployment-Bibliothek nicht gefunden: ${IMAGE_DEPLOY_LIB}"; return 1; }
|
||||||
|
# shellcheck source=../../scripts/lib/image_deploy.sh
|
||||||
|
source "${IMAGE_DEPLOY_LIB}"
|
||||||
|
|
||||||
|
[[ -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}" "${GOLDEN_IMAGE_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 == "object")
|
||||||
|
and (.secure_boot_required | type == "boolean")
|
||||||
|
' "${RUNTIME_BLUEPRINT_FILE}" >/dev/null ||
|
||||||
|
{ backend_fatal "Installationszeitliche Vorgaben fehlen oder sind ungültig."; return 1; }
|
||||||
|
|
||||||
|
# Phase 2 deckt bewusst nur das einfache Schema ab (ESP/biosgrub +
|
||||||
|
# eine Root-Partition, siehe image_deploy_partition()) - "custom" mit
|
||||||
|
# extra_partitions (/home, /var) ist noch nicht auf die neue
|
||||||
|
# parted-basierte Mechanik uebertragen. Klarer Fehler statt stiller
|
||||||
|
# Fehlinterpretation.
|
||||||
|
local scheme
|
||||||
|
scheme="$(jq --raw-output '.runtime_blueprint.installation_directives.partitioning.scheme // "single"' "${RUNTIME_BLUEPRINT_FILE}")"
|
||||||
|
[[ "${scheme}" == "single" ]] ||
|
||||||
|
{ backend_fatal "Partitionierungsschema '${scheme}' wird von diesem Backend noch nicht unterstützt (nur 'single')."; return 1; }
|
||||||
|
|
||||||
|
if [[ "$(jq --raw-output '.runtime_blueprint.installation_directives.disk_encryption' "${RUNTIME_BLUEPRINT_FILE}")" == "true" ]]; then
|
||||||
|
backend_fatal "disk_encryption=true wird von diesem 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 device_id device_fingerprint
|
||||||
|
local root_filesystem partitioning_json blueprints_json
|
||||||
|
|
||||||
|
[[ -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; }
|
||||||
|
|
||||||
|
[[ -r "${HARDWARE_FILE}" ]] ||
|
||||||
|
{ backend_fatal "Hardware-Erfassung nicht gefunden: ${HARDWARE_FILE}"; return 1; }
|
||||||
|
device_fingerprint="$(jq --raw-output '.identity.device_fingerprint // empty' "${HARDWARE_FILE}")"
|
||||||
|
[[ -n "${device_fingerprint}" ]] ||
|
||||||
|
{ backend_fatal "Kein device_fingerprint in ${HARDWARE_FILE} gefunden."; return 1; }
|
||||||
|
|
||||||
|
partitioning_json="$(jq --compact-output '.runtime_blueprint.installation_directives.partitioning' "${RUNTIME_BLUEPRINT_FILE}")"
|
||||||
|
root_filesystem="$(jq --raw-output '.root_filesystem // "ext4"' <<<"${partitioning_json}")"
|
||||||
|
case "${root_filesystem}" in
|
||||||
|
ext4|btrfs) ;;
|
||||||
|
*) backend_fatal "Nicht unterstütztes Root-Dateisystem: ${root_filesystem}"; return 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
blueprints_json="$(jq --compact-output '.runtime_blueprint.blueprints' "${RUNTIME_BLUEPRINT_FILE}")"
|
||||||
|
|
||||||
|
jq --null-input \
|
||||||
|
--arg hostname "${hostname}" \
|
||||||
|
--arg device_id "${device_id}" \
|
||||||
|
--arg device_fingerprint "${device_fingerprint}" \
|
||||||
|
--arg root_filesystem "${root_filesystem}" \
|
||||||
|
--argjson blueprints "${blueprints_json}" \
|
||||||
|
'{
|
||||||
|
hostname: $hostname,
|
||||||
|
device_id: $device_id,
|
||||||
|
device_fingerprint: $device_fingerprint,
|
||||||
|
root_filesystem: $root_filesystem,
|
||||||
|
blueprints: $blueprints
|
||||||
|
}' > "${CONFIG_FILE}" ||
|
||||||
|
{ backend_fatal "Konfigurationsdatei konnte nicht erzeugt werden."; return 1; }
|
||||||
|
|
||||||
|
chmod 0600 "${CONFIG_FILE}"
|
||||||
|
|
||||||
|
backend_log "Konfiguration erzeugt: ${CONFIG_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
backend_launch() {
|
||||||
|
local disk is_efi root_fs boot_part root_part
|
||||||
|
|
||||||
|
disk="$(_mint_image_detect_target_disk)"
|
||||||
|
[[ -n "${disk}" ]] ||
|
||||||
|
{ backend_fatal "Zieldatenträger konnte nicht ermittelt werden."; return 1; }
|
||||||
|
|
||||||
|
[[ -d /sys/firmware/efi ]] && is_efi="true" || is_efi="false"
|
||||||
|
backend_log "Zieldatenträger: ${disk} (Firmware: $([ "${is_efi}" = true ] && echo UEFI || echo BIOS))"
|
||||||
|
|
||||||
|
root_fs="$(jq --raw-output '.root_filesystem' "${CONFIG_FILE}")"
|
||||||
|
|
||||||
|
backend_log "Lade Golden Image von ${GOLDEN_IMAGE_URL}"
|
||||||
|
curl --silent --show-error --fail --location \
|
||||||
|
--output "${GOLDEN_IMAGE_FILE}" "${GOLDEN_IMAGE_URL}" ||
|
||||||
|
{ backend_fatal "Golden Image konnte nicht geladen werden: ${GOLDEN_IMAGE_URL}"; return 1; }
|
||||||
|
|
||||||
|
backend_log "Partitioniere ${disk}"
|
||||||
|
read -r boot_part root_part <<<"$(image_deploy_partition "${disk}" "${is_efi}")" ||
|
||||||
|
return 1
|
||||||
|
|
||||||
|
backend_log "Formatiere Partitionen"
|
||||||
|
image_deploy_format "${boot_part}" "${root_part}" "${root_fs}" || return 1
|
||||||
|
|
||||||
|
backend_log "Mounte unter ${TARGET_DIR}"
|
||||||
|
image_deploy_mount "${TARGET_DIR}" "${boot_part}" "${root_part}" || return 1
|
||||||
|
|
||||||
|
backend_log "Entpacke Golden Image"
|
||||||
|
image_deploy_extract_image "${GOLDEN_IMAGE_FILE}" "${TARGET_DIR}" || return 1
|
||||||
|
rm -f "${GOLDEN_IMAGE_FILE}"
|
||||||
|
|
||||||
|
backend_log "Schreibe fstab"
|
||||||
|
image_deploy_write_fstab "${TARGET_DIR}" "${boot_part}" "${root_part}" "${root_fs}" || return 1
|
||||||
|
|
||||||
|
backend_log "Binde /dev, /proc, /sys ein"
|
||||||
|
image_deploy_bind_mounts "${TARGET_DIR}" MOUNT_STACK || return 1
|
||||||
|
|
||||||
|
backend_log "chroot-Fixup (machine-id, SSH-Hostkeys, initramfs)"
|
||||||
|
image_deploy_chroot_fixup "${TARGET_DIR}" || return 1
|
||||||
|
|
||||||
|
backend_log "Installiere Bootloader"
|
||||||
|
image_deploy_install_bootloader "${TARGET_DIR}" "${disk}" "${is_efi}" || return 1
|
||||||
|
|
||||||
|
local hostname
|
||||||
|
hostname="$(jq --raw-output '.hostname' "${CONFIG_FILE}")"
|
||||||
|
backend_log "Setze Hostname (${hostname})"
|
||||||
|
image_deploy_set_hostname "${TARGET_DIR}" "${hostname}" || return 1
|
||||||
|
|
||||||
|
backend_log "Deployment abgeschlossen."
|
||||||
|
}
|
||||||
|
|
||||||
|
backend_postinstall() {
|
||||||
|
local device_id device_fingerprint blueprints_json
|
||||||
|
local postinstall_rendered
|
||||||
|
|
||||||
|
device_id="$(jq --raw-output '.device_id' "${CONFIG_FILE}")"
|
||||||
|
device_fingerprint="$(jq --raw-output '.device_fingerprint' "${CONFIG_FILE}")"
|
||||||
|
blueprints_json="$(jq --compact-output '.blueprints' "${CONFIG_FILE}")"
|
||||||
|
|
||||||
|
# Dasselbe Template wie backends/mint/postinstall.sh (per Symlink
|
||||||
|
# geteilt, siehe Verzeichnis) - rein distributionsunabhaengiges
|
||||||
|
# Bash-Skript (curl/jq gegen anode), hier per chroot statt per
|
||||||
|
# ubiquity/success_command ausgefuehrt.
|
||||||
|
postinstall_rendered="$(
|
||||||
|
TUXFLOTTE_DEVICE_ID="${device_id}" \
|
||||||
|
TUXFLOTTE_BLUEPRINTS_JSON="${blueprints_json}" \
|
||||||
|
TUXFLOTTE_DEVICE_FINGERPRINT="${device_fingerprint}" \
|
||||||
|
envsubst '${TUXFLOTTE_DEVICE_ID} ${TUXFLOTTE_BLUEPRINTS_JSON} ${TUXFLOTTE_DEVICE_FINGERPRINT}' \
|
||||||
|
<"${POSTINSTALL_SCRIPT}"
|
||||||
|
)"
|
||||||
|
|
||||||
|
if grep -q '\${TUXFLOTTE_' <<<"${postinstall_rendered}"; then
|
||||||
|
backend_fatal "postinstall.sh enthält nach envsubst nicht aufgelöste Platzhalter."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "${postinstall_rendered}" > "${TARGET_DIR}/tmp/postinstall.sh"
|
||||||
|
chmod 0700 "${TARGET_DIR}/tmp/postinstall.sh"
|
||||||
|
|
||||||
|
backend_log "Führe Postinstall-Skript im chroot aus."
|
||||||
|
chroot "${TARGET_DIR}" /bin/bash /tmp/postinstall.sh ||
|
||||||
|
{ backend_fatal "Postinstall-Skript ist im chroot fehlgeschlagen."; return 1; }
|
||||||
|
|
||||||
|
rm -f "${TARGET_DIR}/tmp/postinstall.sh"
|
||||||
|
|
||||||
|
backend_log "Hänge Ziel-Dateisystem aus."
|
||||||
|
image_deploy_unbind_mounts MOUNT_STACK
|
||||||
|
umount --recursive "${TARGET_DIR}" ||
|
||||||
|
{ backend_fatal "${TARGET_DIR} konnte nicht ausgehängt werden."; return 1; }
|
||||||
|
|
||||||
|
backend_log "Starte neu - kein Rücksprung erwartet, ab hier läuft das frisch installierte System."
|
||||||
|
reboot
|
||||||
|
}
|
||||||
1
backends/mint-image/postinstall.sh
Symbolic link
1
backends/mint-image/postinstall.sh
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../mint/postinstall.sh
|
||||||
Loading…
x
Reference in New Issue
Block a user