#!/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 [[ -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() { backend_log "Phase 1: Start des nativen Installers ist noch nicht aktiv." backend_log "Erzeugte Konfiguration liegt bereit unter: ${CONFIG_FILE}" } backend_postinstall() { backend_log "Provisioning-Agent-Einrichtung erfolgt im ubiquity/success_command der Preseed-Konfiguration (Agent-Abruf, Bootstrap-Registrierung, systemd-Aktivierung)." }