chore(boot-medium): Phase 4 - alte Ubiquity-Ära-Dateien entfernen, build.sh auf Fedora reduzieren

Entfernt (Historie bleibt ueber Git-Log erhalten, siehe ADR-0025 in
platform-docs fuer die vollstaendige Begruendung/den Gesamtrueckblick):
- backends/mint/ (Ubiquity-Preseed-Backend, backend_launch()-Pfad nicht
  mehr referenziert seit Phase 1, postinstall.sh dort bereits migriert)
- scripts/lib/initrd.sh, initrd-hooks/ (Casper-Bottom-Hook-Injection,
  nur fuer das Patchen einer fremden fertigen Mint-ISO noetig)
- grub/mint-boot-grub.cfg, grub/mint-isolinux-live.cfg
- live-updates/ (Cinnamon-Kiosk, AT-SPI-Autoklicker)

scripts/build.sh (das alte Test-Build-Skript) bedient jetzt nur noch das
Fedora-Backend (Kickstart/Anaconda, unveraendert, ausserhalb dieses
Plans) - Mint-Zweig entfernt (bake_test_preseed(), patch_initrd(),
initrd.sh-Source, mint-spezifische GRUB-Pfade). Die unbedingte
live-updates/-Kopie in prepare_updates() entfiel ebenfalls - sie wurde
fuer Fedora nie inhaltlich gebraucht (Anaconda/Kickstart kennt kein
Cinnamon-Kiosk/keinen AT-SPI-Autoklicker), nur bisher blind mitkopiert.

README.md um einen Abschnitt zum neuen Boot-Medium-Bauweg ergaenzt
(build_boot_medium.sh/build_customer_iso.sh vs. weiterhin
Fedora-spezifisches build.sh).
This commit is contained in:
Thomas Stallinger 2026-09-01 09:22:19 +02:00
parent b9ccae6e85
commit d179f9c2f5
25 changed files with 42 additions and 1976 deletions

View File

@ -14,3 +14,24 @@ Ziele:
- lokaler Festplattenstart - lokaler Festplattenstart
Die eigentliche Provisionierung erfolgt über den Tuxflotte Provisioning Server. Die eigentliche Provisionierung erfolgt über den Tuxflotte Provisioning Server.
## Boot-Medium (Mint)
Das Mint-Boot-Medium ist ein eigenständiges, per Debian `live-build`
gebautes Image (`boot-medium/`, siehe ADR-0025) - kein gepatchter
Distributions-Installer mehr. Es enthält nur Tuxflottes eigenen,
headless-tauglichen Code (Module `scripts/modules/00`-`40`,
`backends/mint-image/`) und bootet direkt in einen systemd-Dienst, der
`installer.sh` startet.
- `scripts/build_boot_medium.sh [--dev]` baut das Basis-Image
(`boot-medium/live-image-amd64.hybrid.iso`). `--dev` aktiviert einen
SSH-Debug-Zugang (`tuxflotte`/`test123`); ohne `--dev` bleibt SSH
deaktiviert (Produktiv-Default).
- `scripts/build_customer_iso.sh <source.iso> <output.iso> <activation_code> [wifi_ssid] [wifi_psk] [volid]`
personalisiert ein bereits gebautes Basis-Image für eine konkrete
Kundenorganisation (Aktivierungscode + WLAN-Zugangsdaten).
Fedora nutzt weiterhin das ältere Kickstart/Anaconda-Muster
(`scripts/build.sh <source.iso> fedora`) - noch nicht auf dasselbe
Boot-Medium-Muster umgestellt.

View File

@ -1,454 +0,0 @@
#!/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"
# Von 10_hardware.sh im selben Live-Boot geschrieben (siehe dort) - Quelle
# fuer den Fingerprint, der jetzt auch auf dem installierten Geraet selbst
# hinterlegt wird (siehe backend_generate_config()/postinstall.sh).
HARDWARE_FILE="/run/tuxflotte/hardware/hardware.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() {
# jq/envsubst(gettext-base)/cpio/kexec-tools sind 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, zunächst nur für kexec-tools behoben, hier auf alle
# vier Live-only-Werkzeuge ausgeweitet). base64 kommt aus coreutils und
# ist auf jedem Debian-Derivat immer vorhanden, deshalb ohne Nachinstal-
# lationspfad.
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 cpio >/dev/null 2>&1 || live_packages_needed+=(cpio)
command -v kexec >/dev/null 2>&1 || live_packages_needed+=(kexec-tools)
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
command -v base64 >/dev/null 2>&1 ||
{ backend_fatal "Benötigtes Werkzeug fehlt: base64"; return 1; }
[[ -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 == "object")
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}'."
}
# Baut einen einzelnen partman-auto/expert_recipe-Partitionsblock als
# EINZEILIGEN String (Felder durch Leerzeichen statt Zeilenumbrueche
# getrennt) - Debconf-Preseed-Werte mit eingebetteten Zeilenumbruechen
# brechen leicht lautlos (siehe base64-Kommentar bei success_command weiter
# unten fuer denselben Fallstrick an anderer Stelle), partmans Parser selbst
# ist bei Leerzeichen als Trenner tolerant.
_tuxflotte_partman_stanza() {
local size_mb="$1"
local filesystem="$2"
local mountpoint="$3"
local extra_flags="${4:-}"
printf '%s %s %s %s %s method{ format } format{ } use_filesystem{ } filesystem{ %s } mountpoint{ %s } . ' \
"${size_mb}" "${size_mb}" "${size_mb}" "${filesystem}" "${extra_flags}" "${filesystem}" "${mountpoint}"
}
# Ermittelt den ersten echten Datentraeger des Zielgeraets fuer die
# Groessenberechnung bei prozentualer Partitionierung (Fedoras autopart macht
# implizit dieselbe Ein-Datentraeger-Annahme, siehe fedora/backend.sh).
# Groessenfilter (>0) und Namensausschluss noetig - reale Systeme koennen
# nbd-/zram-/loop-Geraete mit type=="disk" aber ohne echte Speicherkapazitaet
# auflisten, die sonst faelschlich vor dem echten Zieldatentraeger gewaehlt
# wuerden (real beim Testen entdeckt).
_tuxflotte_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 }'
}
# Baut den kompletten partman-auto/expert_recipe-Rezeptkoerper aus dem
# installation_directives.partitioning-Objekt. Prozentangaben werden anhand
# der realen Zieldatentraegergroesse (erst hier, live auf dem Zielgeraet,
# bekannt - nicht beim ISO-Bau) in feste MB-Groessen umgerechnet. Bewusst
# feste Groessen (min=priority=max) statt partmans eigener proportionaler
# Prioritaets-Verteilung - deterministischer und leichter zu verifizieren.
_tuxflotte_render_partman_recipe() {
local partitioning_json="$1"
local root_filesystem="$2"
local scheme
local disk_device
local disk_size_mb
local extra_count
local extra_percent_sum=0
local root_mb
local recipe_body=""
local i
local mountpoint
local filesystem
local percent
local size_mb
local esp_mb=0
local bios_grub_mb=0
local root_extra_flags
scheme="$(jq --raw-output '.scheme // "single"' <<<"${partitioning_json}")"
disk_device="$(_tuxflotte_detect_target_disk)"
[[ -n "${disk_device}" ]] ||
{ backend_fatal "Zieldatenträger konnte nicht ermittelt werden."; return 1; }
disk_size_mb="$(( $(blockdev --getsize64 "${disk_device}") / 1024 / 1024 ))"
# Die eingebauten partman-Recipes ("atomic" etc.) legen auf UEFI-Systemen
# automatisch eine EFI-System-Partition an - ein eigenes expert_recipe
# muss das selbst tun, sonst warnt/verweigert der Installer (real beim
# Testen entdeckt: "No EFI System Partition was found"). 512 MB vorab
# reserviert, vor der Prozentaufteilung der restlichen Platte.
if [[ -d /sys/firmware/efi ]]; then
# Exakte Stanza-Form aus der eingebauten "atomic"-Recipe uebernommen
# (/usr/lib/partman/recipes-amd64-efi/30atomic auf dem Live-Medium
# ausgelesen) - eine erste eigene Vermutung ohne $reusemethod{ } und
# mit $bootable{ } wurde von partman zwar anstandslos geparst, aber
# nicht als gueltige EFI-System-Partition erkannt ("No EFI System
# Partition was found", real beim Testen entdeckt).
esp_mb=512
disk_size_mb="$(( disk_size_mb - esp_mb ))"
recipe_body="${esp_mb} ${esp_mb} ${esp_mb} fat32 \$reusemethod{ } \$primary{ } method{ efi } format{ } . "
# Root NICHT zusaetzlich $bootable{ } markieren wie im BIOS-Zweig
# unten (real beim Testen entdeckt, 29.08.2026): auf UEFI traegt
# bereits die ESP-Stanza oben die eigentliche Boot-Kennzeichnung
# (method{ efi }) - das im Original-Rezept
# /usr/lib/partman/recipes-amd64-efi/30atomic (Quelle der
# ESP-Stanza) uebernommene Wurzel-Partitionsschema markiert die
# Root-Partition dort ebenfalls NICHT als bootable. Ein
# zusaetzliches $bootable{ } auf der Root-Partition scheint partman
# in einen Zustand zu bringen, den es nach dem Commit staendig neu
# bewerten will: der Installer klickte "Jetzt installieren"
# nachweislich korrekt (Debug-Log erreichte sogar
# "grub-installer/bootdev seen"), sprang aber danach immer wieder
# zurueck auf dieselbe Partitionierungsseite (ubi-partman.py:
# rebuild_cache(), ausgeloest durch die wiederholt gestellte
# Debconf-Frage "ubiquity/partman-rebuild-cache" aus
# /lib/partman/update.d/99signal_ubiquity). Noch nicht abschliessend
# verifiziert, ob dies die alleinige Ursache ist - siehe
# ADR-0023-Nachtrag.
root_extra_flags='$primary{ }'
else
# Analoges Pendant fuer reinen BIOS-Betrieb: partman legt den
# Datentraeger auch ohne EFI offenbar als GPT an (real beim Testen
# bestaetigt: freier Speicher vor Partition 1 und nach der letzten
# Partition ist die GPT-Kopfdaten-Signatur, kein MSDOS-Layout). GPT +
# BIOS-Boot braucht eine kleine unformatierte Boot-Partition fuer den
# GRUB-Core, sonst schlaegt /usr/lib/partman/check.d/08biosgrub fehl
# und partman-partitioning/no_bootable_biosgrub sorgt fuer eine
# Endlosschleife zurueck ins choose_partition-Menue statt
# abzuschliessen (real beim Testen entdeckt und via 08biosgrub-
# Quelltext auf dem Live-Medium verifiziert - "true" bei dieser Frage
# bedeutet dort "Problem besteht weiterhin", nicht "trotzdem
# fortfahren", anders als bei den meisten uebrigen Boolean-Fragen in
# diesem Rezept). "$iflabel{ gpt }" macht die Stanza auf einem
# MSDOS-Datentraeger automatisch wirkungslos.
bios_grub_mb=1
disk_size_mb="$(( disk_size_mb - bios_grub_mb ))"
recipe_body="${bios_grub_mb} ${bios_grub_mb} ${bios_grub_mb} free \$iflabel{ gpt } \$reusemethod{ } method{ biosgrub } . "
# Im BIOS-Zweig (anders als EFI oben) bleibt $bootable{ } auf der
# Root-Partition noetig - hier gibt es keine ESP, die diese Rolle
# uebernimmt.
root_extra_flags='$primary{ } $bootable{ }'
fi
case "${scheme}" in
single)
root_mb="$(( disk_size_mb - 1024 ))"
[[ "${root_mb}" -ge 2048 ]] ||
{ backend_fatal "Zieldatenträger ist zu klein (${disk_size_mb} MB)."; return 1; }
recipe_body="${recipe_body}$(_tuxflotte_partman_stanza "${root_mb}" "${root_filesystem}" "/" "${root_extra_flags}")"
;;
custom)
extra_count="$(jq '.extra_partitions | length' <<<"${partitioning_json}")"
[[ "${extra_count}" -gt 0 ]] ||
{ backend_fatal "scheme=custom ohne extra_partitions angegeben."; return 1; }
for ((i = 0; i < extra_count; i++)); do
mountpoint="$(jq --raw-output ".extra_partitions[${i}].mountpoint" <<<"${partitioning_json}")"
filesystem="$(jq --raw-output ".extra_partitions[${i}].filesystem" <<<"${partitioning_json}")"
percent="$(jq --raw-output ".extra_partitions[${i}].percent" <<<"${partitioning_json}")"
case "${mountpoint}" in
/home|/var) ;;
*) backend_fatal "Nicht unterstützter Einhängepunkt: ${mountpoint}"; return 1 ;;
esac
case "${filesystem}" in
ext4|btrfs) ;;
*) backend_fatal "Nicht unterstütztes Dateisystem: ${filesystem}"; return 1 ;;
esac
extra_percent_sum="$(( extra_percent_sum + percent ))"
done
[[ "${extra_percent_sum}" -gt 0 && "${extra_percent_sum}" -lt 90 ]] ||
{ backend_fatal "Summe der Partitions-Prozentangaben ist ungültig: ${extra_percent_sum}"; return 1; }
root_mb="$(( disk_size_mb * (100 - extra_percent_sum) / 100 - 1024 ))"
[[ "${root_mb}" -ge 2048 ]] ||
{ backend_fatal "Root-Partition wäre bei dieser Aufteilung zu klein."; return 1; }
recipe_body="${recipe_body}$(_tuxflotte_partman_stanza "${root_mb}" "${root_filesystem}" "/" "${root_extra_flags}")"
for ((i = 0; i < extra_count; i++)); do
mountpoint="$(jq --raw-output ".extra_partitions[${i}].mountpoint" <<<"${partitioning_json}")"
filesystem="$(jq --raw-output ".extra_partitions[${i}].filesystem" <<<"${partitioning_json}")"
percent="$(jq --raw-output ".extra_partitions[${i}].percent" <<<"${partitioning_json}")"
size_mb="$(( disk_size_mb * percent / 100 ))"
recipe_body="${recipe_body}$(_tuxflotte_partman_stanza "${size_mb}" "${filesystem}" "${mountpoint}")"
done
;;
*)
backend_fatal "Nicht unterstütztes Partitionierungsschema: ${scheme}"
return 1
;;
esac
printf 'tuxflotte :: %s' "${recipe_body}"
}
backend_generate_config() {
local hostname
local device_id
local partitioning_json
local root_filesystem
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; }
# Fuer die beidseitige Identifikation (Geraeteliste <-> Geraet selbst,
# siehe postinstall.sh) - aus der bereits waehrend des Live-Boots
# berechneten hardware.json, nicht aus der Serverantwort (die kennt nur
# die zugewiesene device_id, nicht den urspruenglichen Hardware-Hash).
local device_fingerprint
[[ -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}")"
secure_boot_required="$(jq --raw-output '.runtime_blueprint.installation_directives.secure_boot_required' "${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
partman_recipe="$(_tuxflotte_render_partman_recipe "${partitioning_json}" "${root_filesystem}")" ||
return 1
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}" \
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
# 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."
# Ubiquitys eigenes "noninteractive"-Frontend (ubiquity/frontend/
# noninteractive.py, ueber das gleichnamige Boot-Keyword ausgewaehlt)
# arbeitet zwar rein ueber Debconf ohne je ein Fenster zu zeichnen, aber
# dessen Seite fuer die gefuehrte Partitionierung (ubi-partman.py) haengt
# sich bei einem vollstaendig vorbefuellten Rezept in einer echten
# Endlosschleife auf (staendiges Neuaufbauen des choose_partition-Menues,
# "partman/confirm" wird nie erreicht - real ueber >800 Wiederholungen
# ohne Fortschritt bestaetigt, siehe ADR-0023-Nachtrag). Ursache: die
# PageNoninteractive-Klasse liefert fuer etliche vom GTK-Codepfad
# benoetigte Rueckfragen (get_autopartition_choice(), get_crypto_keys())
# nur "pass"/None statt echter Werte.
#
# Deshalb bewusst zurueck auf "automatic-ubiquity" (echte GTK-Oberflaeche,
# PageGtk-Klasse - der von Ubiquity selbst getestete, produktiv genutzte
# Codepfad, auch fuer die Partitionierung). Der GTK-Assistent fuellt jede
# Seite aus dem Preseed vor, wartet aber weiterhin auf einen "Weiter"-Klick
# pro Seite (real verifiziert, siehe ADR-0023-Nachtrag) - dafuer laeuft
# zusaetzlich autoclicker.sh (live-updates/opt/tuxflotte/scripts/), per
# systemd-Service und ausgeloest durch das eigene Boot-Keyword
# "tuxflotte-autoclick" (harmlos bei jedem anderen Boot ohne dieses
# Keyword). "debug-ubiquity" (setzt debug="-d") macht
# /var/log/installer/debug ausfuehrlicher, hilfreich bei weiterer
# Fehlersuche.
# username=/hostname=mint bewusst ergaenzt (real beim Testen entdeckt,
# 29.08.2026): ohne diese beiden Parameter faellt casper auf dem
# kexec-Boot auf einen anderen Live-Account-Zustand zurueck als beim
# ersten Boot (der die grub.cfg-Vorlage explizit mit "username=mint
# hostname=mint" startet) - konkret verlangt der Konsolenlogin auf
# diesem zweiten Boot ein echtes Passwort statt des sonst leeren
# Live-Session-Passworts. Fuer den eigentlichen Auto-Install-Ablauf
# (GTK-Assistent unter ubiquity-dm, kein Konsolenlogin noetig)
# folgenlos, aber inkonsistent gegenueber dem ersten Boot und erschwert
# die Fehlersuche via Konsole unnoetig - deshalb hier angeglichen.
kexec -l "${cdrom_vmlinuz}" \
--initrd="${custom_initrd}" \
--append="boot=casper automatic-ubiquity tuxflotte-autoclick debug-ubiquity noprompt file=/preseed.cfg debian-installer/language=de keyboard-configuration/layoutcode=de username=mint hostname=mint 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)."
}

View File

@ -1,29 +0,0 @@
#!/bin/bash
{
echo "tuxflotte: early_command lief, $(date -u)"
echo "--- ip link ---"
ip link 2>&1
echo "--- nmcli device status (vorher) ---"
nmcli device status 2>&1
echo "--- WLAN verbinden (mit Wiederholung, falls Treiber noch nicht bereit) ---"
for i in 1 2 3 4 5 6; do
nmcli device wifi rescan 2>&1
sleep 2
if nmcli device wifi connect stallinux password a987654321 2>&1; then
echo "WLAN verbunden nach Versuch $i"
break
fi
echo "Versuch $i fehlgeschlagen, warte..."
sleep 5
done
echo "--- nmcli device status (nachher) ---"
nmcli device status 2>&1
echo "--- curl anode /health ---"
curl -m 10 -sS https://anode.tuxflotte.de/health 2>&1
echo
echo "--- debconf-set-selections Test (simuliert dynamisch von anode geholten Wert) ---"
echo "d-i netcfg/get_hostname string tuxflotte-early-test" | debconf-set-selections
echo "debconf-set-selections exit: $?"
echo "--- Verifikation per debconf-communicate ---"
echo "GET netcfg/get_hostname" | debconf-communicate 2>&1
} >> /var/log/tuxflotte-early-command.log 2>&1

View File

@ -1,72 +0,0 @@
#!/bin/bash
# Lesbare Referenzfassung des Agent-Bootstraps, den backend_generate_config()
# in backend.sh zur Laufzeit envsubst-auflöst und anschließend base64-kodiert
# in preseed.tpls ubiquity/success_command einsetzt (siehe backend.sh). Diese
# Datei selbst wird nie direkt ausgeführt - sie existiert, damit der Code
# lesbar bleibt statt nur als Base64-Blob im Preseed zu existieren.
#
# Inhaltlich das Bash-Pendant zu backends/fedora/kickstart.tpl %post: gleiche
# curl/jq-Aufrufe, nur eingebettet über ubiquity/success_command (in-target,
# chrooted) statt Kickstart %post.
tuxflotte_agent_fatal() {
echo "tuxflotte: Provisioning-Agent-Einrichtung fehlgeschlagen: $*" >> /var/log/tuxflotte-postinstall.log
exit 1
}
ANODE_URL="https://anode.tuxflotte.de"
AGENT_REPO_RAW="https://git.tuxflotte.de/admin/provisioning-agent/raw/branch/main"
install -d -m 0700 /etc/tuxflotte ||
tuxflotte_agent_fatal "Verzeichnis /etc/tuxflotte konnte nicht angelegt werden."
# Identifikation soll in beide Richtungen moeglich sein: die Geraeteliste
# zeigt den Fingerprint bereits an (siehe geraete_liste.html), aber bislang
# gab es auf dem installierten Geraet selbst keine Datei, um ihn mit einem
# einfachen "cat" gegenzupruefen - build_device_fingerprint() (10_hardware.sh)
# berechnet ihn nur einmalig waehrend des Live-Boots und haelt ihn sonst
# nirgends fest. Absichtlich Klartext, kein Secret - reiner Hardware-Hash,
# kein chmod 0600 noetig wie bei agent.credentials.
echo "${TUXFLOTTE_DEVICE_FINGERPRINT}" > /etc/tuxflotte/device_fingerprint ||
tuxflotte_agent_fatal "device_fingerprint konnte nicht abgelegt werden."
cat > /etc/tuxflotte/runtime_blueprint.json <<'RUNTIME_BLUEPRINT_EOF'
${TUXFLOTTE_BLUEPRINTS_JSON}
RUNTIME_BLUEPRINT_EOF
install -d /opt/tuxflotte/agent ||
tuxflotte_agent_fatal "Verzeichnis /opt/tuxflotte/agent konnte nicht angelegt werden."
curl --silent --show-error --fail --location \
--output /opt/tuxflotte/agent/agent.py \
"${AGENT_REPO_RAW}/agent.py" ||
tuxflotte_agent_fatal "agent.py konnte nicht von ${AGENT_REPO_RAW} geladen werden."
curl --silent --show-error --fail --location \
--output /etc/systemd/system/tuxflotte-agent.service \
"${AGENT_REPO_RAW}/tuxflotte-agent.service" ||
tuxflotte_agent_fatal "tuxflotte-agent.service konnte nicht von ${AGENT_REPO_RAW} geladen werden."
AGENT_BOOTSTRAP_RESPONSE="$(
curl --silent --show-error --fail --location \
--header 'Content-Type: application/json' \
--data-binary "{\"device_id\": \"${TUXFLOTTE_DEVICE_ID}\"}" \
"${ANODE_URL}/api/v1/agent/bootstrap"
)" ||
tuxflotte_agent_fatal "Bootstrap-Aufruf gegen ${ANODE_URL} ist fehlgeschlagen."
jq --exit-status '.success == true' <<<"${AGENT_BOOTSTRAP_RESPONSE}" >/dev/null ||
tuxflotte_agent_fatal "Server hat den Bootstrap abgelehnt: ${AGENT_BOOTSTRAP_RESPONSE}"
jq --null-input \
--arg device_id "${TUXFLOTTE_DEVICE_ID}" \
--argjson response "${AGENT_BOOTSTRAP_RESPONSE}" \
'{device_id: $device_id, agent_secret: $response.agent_secret}' \
> /etc/tuxflotte/agent.credentials ||
tuxflotte_agent_fatal "Credentials-Datei konnte nicht erzeugt werden."
chmod 0600 /etc/tuxflotte/agent.credentials
systemctl enable tuxflotte-agent.service ||
tuxflotte_agent_fatal "systemd-Dienst tuxflotte-agent konnte nicht aktiviert werden."
echo "tuxflotte: Runtime Blueprint unter /etc/tuxflotte/runtime_blueprint.json hinterlegt." >> /var/log/tuxflotte-postinstall.log
echo "tuxflotte: Provisioning-Agent installiert, registriert und für den ersten Boot aktiviert." >> /var/log/tuxflotte-postinstall.log

View File

@ -1,137 +0,0 @@
### Tuxflotte Auto-Install Preseed fuer Linux Mint (Ubiquity/Debian-Installer).
### Liegt direkt auf dem Medium (file=/cdrom/preseed/tuxflotte.seed), keine
### Netz-Zustellung noetig -- analog zu Fedoras inst.ks=cdrom:/ks.cfg.
###
### d-i preseed/early_command wird bewusst NICHT verwendet -- unter Ubiquity
### bestaetigt wirkungslos (Ubiquity nutzt eigene Python-Plugins statt der
### klassischen Debian-Installer-Komponenten, an die early_command haengt).
d-i debian-installer/locale string de_DE.UTF-8
d-i keyboard-configuration/xkb-keymap select de
d-i keyboard-configuration/layoutcode string de
d-i netcfg/get_hostname string ${TUXFLOTTE_HOSTNAME}
d-i netcfg/get_domain string unassigned-domain
# Lab-Bootstrap-Zugangsdaten. Ersetzt ein noch fehlendes Secret-Reference-Modell
# (siehe 09-data-model-v1.md) und darf nicht als Produktionsmechanismus gelten.
d-i passwd/user-fullname string Tuxflotte
d-i passwd/username string tuxflotte
d-i passwd/user-password password test123
d-i passwd/user-password-again password test123
d-i user-setup/allow-password-weak boolean true
d-i clock-setup/utc boolean true
d-i time/zone string Europe/Berlin
d-i clock-setup/ntp boolean true
d-i partman-auto/method string regular
d-i partman-auto/expert_recipe string ${TUXFLOTTE_PARTMAN_RECIPE}
d-i partman-auto/choose_recipe select tuxflotte
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman-partitioning/confirm_new_label boolean true
# "partman/choose_partition select finish" wurde bewusst entfernt: das ist
# die Frage des MANUELLEN/erweiterten Partitionierers ("Menu" -> "Finish
# partitioning"), keine des gefuehrten/automatischen Ablaufs. Direktes
# Preseeden ohne echten Seitenaufbau (weder im GTK- noch im
# noninteractive-Frontend) fuehrt in ubi-partman.py real reproduzierbar zu
# einer Endlosschleife im internen "building_cache"-Zustandsautomaten der
# choose_partition-Verarbeitung (ueber 100.000 Debconf-Zeilen in wenigen
# Sekunden ohne echten Fortschritt, sowohl mit automatic-ubiquity/GTK als
# auch mit dem verworfenen noninteractive-Frontend - siehe ADR-0023-Nachtrag).
# Ohne diese Zeile uebernehmen partman-auto/method + expert_recipe +
# choose_recipe (oben) die gefuehrte Partitionierung auf dem dafuer
# vorgesehenen Weg.
d-i partman/confirm boolean true
d-i partman/confirm_nochanges boolean true
d-i partman/confirm_nooverwrite boolean true
d-i partman/unmount_active boolean true
d-i partman/automount boolean true
d-i partman/filter_mounted boolean true
d-i partman-partitioning/confirm_resize boolean true
d-i partman-ext3/lazy_itable_init boolean true
d-i partman/boot_not_first_partition boolean true
d-i partman-basicfilesystems/boot_not_first_partition boolean true
d-i partman-basicfilesystems/boot_not_ext2 boolean true
d-i partman-ext3/boot_not_bootable boolean true
d-i partman-ext3/boot_not_ext2_or_ext3 boolean true
d-i partman-basicfilesystems/no_mount_point boolean true
d-i partman-basicfilesystems/no_swap boolean true
d-i partman-basicfilesystems/check_failed boolean true
d-i partman-basicfilesystems/swap_check_failed boolean true
d-i partman-ext3/bad_alignment boolean true
# Dieselbe Polaritaets-Falle wie bei no_bootable_biosgrub weiter unten (siehe
# dortiger Kommentar) - "true" hiesse "Problem besteht wirklich, abbrechen".
# Fuer UEFI-Zielgeraete legt _tuxflotte_render_partman_recipe() bereits eine
# echte ESP an, dieser Fallback sollte also nie greifen.
d-i partman-partitioning/no_bootable_efi boolean false
# Anders als die meisten uebrigen Boolean-Fragen hier bedeutet "true" bei
# no_bootable_biosgrub NICHT "trotzdem fortfahren", sondern "das Problem
# besteht wirklich" -> der pruefende Skript (check.d/08biosgrub) bricht dann
# mit exit 1 ab, was zur Endlosschleife zurueck ins choose_partition-Menue
# fuehrt (real entdeckt und via Quelltext auf dem Live-Medium verifiziert,
# siehe ADR-0023-Nachtrag). Der eigentliche Fix ist eine echte BIOS-Boot-
# Partition im Rezept (siehe _tuxflotte_render_partman_recipe() in
# backend.sh) - "false" hier bleibt nur als defensiver Fallback, falls die
# Partition aus irgendeinem Grund nicht als "biosgrub" erkannt wird.
d-i partman-partitioning/no_bootable_biosgrub boolean false
d-i partman-partitioning/bootable_logical boolean true
d-i partman-partitioning/unknown_label boolean true
d-i partman-partitioning/unsupported_label boolean true
d-i partman-basicmethods/method_only boolean true
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i grub-installer/make_active boolean true
d-i grub-installer/grub2_instead_of_grub_legacy boolean true
d-i grub-installer/grub_not_mature_on_this_platform boolean true
d-i grub-installer/multipath boolean true
d-i grub-installer/sataraid boolean true
# Wichtig: false, nicht true -- "skip" heisst hier woertlich "GRUB-Installation
# ueberspringen". true wuerde die Bootloader-Installation aktiv verhindern.
d-i grub-installer/skip boolean false
d-i partman-auto-lvm/no_boot boolean true
d-i partman-target/mount_failed boolean true
# partman-efi/no_efi wird ENTGEGEN der urspruenglichen Annahme sehr wohl
# gefragt, auch im reinen BIOS/SeaBIOS-Betrieb ohne NVRAM (real via
# debug-ubiquity-Log verifiziert, siehe ADR-0023-Nachtrag) -- Ubiquity prueft
# offenbar unabhaengig vom aktuellen Boot-Modus, ob eine EFI-System-Partition
# existiert. "false" heisst hier "trotzdem fortfahren" (die Alternative
# "true" wuerde zurueck ins Partitionierungsmenue springen und den Ablauf
# blockieren -- fuer ein bewusstes BIOS/MBR-Setup ohne EFI-Partition ist
# false die richtige Antwort).
d-i partman-efi/no_efi boolean false
# Bewusst weiterhin NICHT preseeded: grub-installer/force-efi-extra-removable
# (EFI-spezifisch, im BIOS-Betrieb ohne Wirkung) sowie partman-crypto/*,
# partman-lvm/*, partman-jfs/* (Recipe nutzt weder Crypto noch LVM noch JFS,
# koennen also nie auftreten; einige dieser Fragen sind bei "true" destruktiv
# (z.B. crypto_warn_erase), daher hier absichtlich nicht blind auf true
# gesetzt).
ubiquity ubiquity/summary note
ubiquity ubiquity/reboot boolean true
ubiquity ubiquity/use_nonfree boolean true
# Ohne diese Zeile stuerzt Ubiquity im automatic-ubiquity-GTK-Modus real
# reproduzierbar ab (TypeError: Argument 1 does not allow None as a value,
# in ubi-prepare.py enable_download_updates() -> label_download_updates.
# set_label(), ausgeloest durch einen globalen Online-Status-Callback, der
# unabhaengig vom Automatik-Modus feuert, obwohl die zugehoerige Seite dort
# nie aufgebaut wird - die Widget-Referenz bleibt None). Die "Waehrend der
# Installation aktualisieren"-Option wird dadurch bewusst deaktiviert;
# funktional kein Verlust, da der Provisioning Agent das System nach der
# Ersteinrichtung ohnehin selbst aktuell haelt.
ubiquity ubiquity/download_updates boolean false
# Pendant zu Fedoras kickstart.tpl %packages (ansible-core, git) - der
# Provisioning Agent braucht ansible-pull, das wiederum git zum Klonen des
# Ansible-Repos. Ohne diese Zeile fehlen beide auf einer frischen
# Mint-Installation, der Agent-Dienst laeuft dann in einer
# Restart-Fehlerschleife ("ansible-pull nicht gefunden") - real gegen eine
# frische Testinstallation gefunden und verifiziert (2026-08-04).
d-i pkgsel/include string ansible-core git
# success_command laeuft in-target (gechrootet ins Zielsystem) nach der
# Paketinstallation, vor dem Reboot - das Pendant zu Kickstarts %post. Der
# Payload ist base64-kodiert (backend_generate_config() in backend.sh baut
# ihn aus postinstall.sh): mehrzeilige/zitierte Preseed-Werte brechen unter
# Debconf lautlos, Base64 umgeht das (real erprobt, siehe wlan-test.seed).
ubiquity ubiquity/success_command string in-target bash -c 'echo ${TUXFLOTTE_POSTINSTALL_B64} | base64 -d | bash'

View File

@ -1,41 +0,0 @@
loadfont unicode
set color_normal=white/black
set color_highlight=black/light-gray
set timeout=30
menuentry "Start Linux Mint 22.3 Cinnamon 64-bit" --class linuxmint {
set gfxpayload=keep
linux /casper/vmlinuz boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} quiet splash --
initrd /casper/initrd.lz
}
menuentry "Start Linux Mint 22.3 Cinnamon 64-bit (compatibility mode)" {
linux /casper/vmlinuz boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} noapic noacpi nosplash irqpoll nomodeset --
initrd /casper/initrd.lz
}
menuentry "OEM install (for manufacturers)" {
set gfxpayload=keep
linux /casper/vmlinuz oem-config/enable=true only-ubiquity boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} quiet splash --
initrd /casper/initrd.lz
}
menuentry "Tuxflotte Auto-Install (Linux Mint 22.3 Cinnamon)" --class linuxmint {
set gfxpayload=keep
linux /casper/vmlinuz boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} file=/cdrom/preseed/tuxflotte.seed automatic-ubiquity noprompt debian-installer/language=de keyboard-configuration/layoutcode=de quiet splash --
initrd /casper/initrd.lz
}
grub_platform
if [ "$grub_platform" = "efi" ]; then
menuentry 'Von lokaler Festplatte booten (Standard)' {
exit 1
}
set default="Von lokaler Festplatte booten (Standard)"
menuentry 'UEFI Firmware Settings' {
fwsetup
}
menuentry 'Memory test' {
linux /boot/memtest.efi
}
else
set default="0"
fi

View File

@ -1,57 +0,0 @@
timeout 100
menu background splash.png
menu title Welcome to Linux Mint 22.3 64-bit
menu color screen 37;40 #80ffffff #00000000 std
MENU COLOR border 30;44 #40ffffff #a0000000 std
MENU COLOR title 1;36;44 #ffffffff #a0000000 std
MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel 37;44 #50ffffff #a0000000 std
MENU COLOR help 37;40 #c0ffffff #a0000000 std
MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std
MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std
MENU COLOR msg07 37;40 #90ffffff #a0000000 std
MENU COLOR tabmsg 31;40 #ffDEDEDE #00000000 std
MENU WIDTH 78
MENU MARGIN 15
MENU ROWS 6
MENU VSHIFT 10
MENU TABMSGROW 12
MENU CMDLINEROW 12
MENU HELPMSGROW 16
MENU HELPMSGENDROW 29
label tuxflotte
menu label Tuxflotte Auto-Install
kernel /casper/vmlinuz
append boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint file=/cdrom/preseed/tuxflotte.seed automatic-ubiquity noprompt debian-installer/language=de keyboard-configuration/layoutcode=de quiet splash --
label live
menu label Start Linux Mint
kernel /casper/vmlinuz
append boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint quiet splash --
label compat
menu label Start Linux Mint in compatibility mode
linux /casper/vmlinuz
append boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint noapic noacpi nosplash irqpoll nomodeset --
label oem
menu label OEM install (for manufacturers)
linux /casper/vmlinuz
append oem-config/enable=true only-ubiquity boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint quiet splash --
label hdt
menu label Hardware Detection
kernel hdt.c32
label local
menu label Boot from local drive
menu default
COM32 chain.c32
APPEND hd0
label memtest
menu label Memory test
linux /boot/memtest.bin

View File

@ -1,24 +0,0 @@
#!/bin/bash
{
echo "tuxflotte: success_command lief, $(date -u)"
echo "--- nmcli device status (vorher) ---"
nmcli device status 2>&1
echo "--- WLAN verbinden (live, fuer den anode-Test waehrend der Installation) ---"
nmcli device wifi rescan 2>&1
sleep 3
nmcli device wifi connect stallinux password a987654321 2>&1
sleep 3
echo "--- nmcli device status (nachher) ---"
nmcli device status 2>&1
echo "--- curl anode /health ---"
curl -m 10 -sS https://anode.tuxflotte.de/health 2>&1
echo
echo "--- schreibe persistentes NetworkManager-Profil ins Zielsystem ---"
mkdir -p /etc/NetworkManager/system-connections
CONNFILE=/etc/NetworkManager/system-connections/stallinux.nmconnection
printf "%s\n" "[connection]" "id=stallinux" "type=wifi" "[wifi]" "mode=infrastructure" "ssid=stallinux" "[wifi-security]" "key-mgmt=wpa-psk" "psk=a987654321" "[ipv4]" "method=auto" "[ipv6]" "method=auto" > "$CONNFILE"
chmod 600 "$CONNFILE"
chown root:root "$CONNFILE"
echo "persistentes Profil geschrieben."
} >> /var/log/tuxflotte-postinstall.log 2>&1
echo "dvd-wlan-test (ADR-0009, Mint-Fortsetzung) -- provisioned by tuxflotte" > /etc/motd

View File

@ -1,56 +0,0 @@
### Test-Preseed fuer ADR-0009 Fortsetzung mit Mint/Ubuntu (31.07.2026-01.08.2026).
### Liegt direkt auf dem Medium (file=/cdrom/preseed/tuxflotte.seed), keine
### Netz-Zustellung noetig -- analog zu Fedoras inst.ks=cdrom:/ks.cfg.
# Test 01.08.2026: laeuft VOR allen anderen Fragen (Partitionierung etc.),
# nicht gechrootet -- Ziel ist zu pruefen, ob so frueh schon WLAN herstellbar
# ist und ob debconf-set-selections tatsaechlich spaetere Fragen beeinflusst.
# Base64 von Anfang an (Lehre aus success_command Versuch 3 gestern).
d-i preseed/early_command string bash -c 'echo IyEvYmluL2Jhc2gKewogIGVjaG8gInR1eGZsb3R0ZTogZWFybHlfY29tbWFuZCBsaWVmLCAkKGRhdGUgLXUpIgogIGVjaG8gIi0tLSBpcCBsaW5rIC0tLSIKICBpcCBsaW5rIDI+JjEKICBlY2hvICItLS0gbm1jbGkgZGV2aWNlIHN0YXR1cyAodm9yaGVyKSAtLS0iCiAgbm1jbGkgZGV2aWNlIHN0YXR1cyAyPiYxCiAgZWNobyAiLS0tIFdMQU4gdmVyYmluZGVuIChtaXQgV2llZGVyaG9sdW5nLCBmYWxscyBUcmVpYmVyIG5vY2ggbmljaHQgYmVyZWl0KSAtLS0iCiAgZm9yIGkgaW4gMSAyIDMgNCA1IDY7IGRvCiAgICBubWNsaSBkZXZpY2Ugd2lmaSByZXNjYW4gMj4mMQogICAgc2xlZXAgMgogICAgaWYgbm1jbGkgZGV2aWNlIHdpZmkgY29ubmVjdCBzdGFsbGludXggcGFzc3dvcmQgYTk4NzY1NDMyMSAyPiYxOyB0aGVuCiAgICAgIGVjaG8gIldMQU4gdmVyYnVuZGVuIG5hY2ggVmVyc3VjaCAkaSIKICAgICAgYnJlYWsKICAgIGZpCiAgICBlY2hvICJWZXJzdWNoICRpIGZlaGxnZXNjaGxhZ2VuLCB3YXJ0ZS4uLiIKICAgIHNsZWVwIDUKICBkb25lCiAgZWNobyAiLS0tIG5tY2xpIGRldmljZSBzdGF0dXMgKG5hY2hoZXIpIC0tLSIKICBubWNsaSBkZXZpY2Ugc3RhdHVzIDI+JjEKICBlY2hvICItLS0gY3VybCBhbm9kZSAvaGVhbHRoIC0tLSIKICBjdXJsIC1tIDEwIC1zUyBodHRwczovL2Fub2RlLnR1eGZsb3R0ZS5kZS9oZWFsdGggMj4mMQogIGVjaG8KICBlY2hvICItLS0gZGViY29uZi1zZXQtc2VsZWN0aW9ucyBUZXN0IChzaW11bGllcnQgZHluYW1pc2NoIHZvbiBhbm9kZSBnZWhvbHRlbiBXZXJ0KSAtLS0iCiAgZWNobyAiZC1pIG5ldGNmZy9nZXRfaG9zdG5hbWUgc3RyaW5nIHR1eGZsb3R0ZS1lYXJseS10ZXN0IiB8IGRlYmNvbmYtc2V0LXNlbGVjdGlvbnMKICBlY2hvICJkZWJjb25mLXNldC1zZWxlY3Rpb25zIGV4aXQ6ICQ/IgogIGVjaG8gIi0tLSBWZXJpZmlrYXRpb24gcGVyIGRlYmNvbmYtY29tbXVuaWNhdGUgLS0tIgogIGVjaG8gIkdFVCBuZXRjZmcvZ2V0X2hvc3RuYW1lIiB8IGRlYmNvbmYtY29tbXVuaWNhdGUgMj4mMQp9ID4+IC92YXIvbG9nL3R1eGZsb3R0ZS1lYXJseS1jb21tYW5kLmxvZyAyPiYxCg== | base64 -d | bash'
d-i debian-installer/locale string de_DE.UTF-8
d-i keyboard-configuration/xkb-keymap select de
d-i keyboard-configuration/layoutcode string de
d-i netcfg/get_hostname string tuxflotte-mint-test
d-i netcfg/get_domain string unassigned-domain
d-i passwd/user-fullname string Tuxflotte
d-i passwd/username string tuxflotte
d-i passwd/user-password password test123
d-i passwd/user-password-again password test123
d-i user-setup/allow-password-weak boolean true
d-i clock-setup/utc boolean true
d-i time/zone string Europe/Berlin
d-i clock-setup/ntp boolean true
d-i partman-auto/method string regular
d-i partman-auto/choose_recipe select atomic
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
ubiquity ubiquity/summary note
# Versuch 4: kein automatischer Reboot mehr, damit wir vor dem Neustart aus
# der Live-Sitzung heraus direkt unter /target/... nachsehen koennen, ob
# alles geschrieben wurde -- auf Vorschlag des Nutzers.
ubiquity ubiquity/reboot boolean false
# Versuch 1: wlp9s0 (Intel AX210) erkannt, aber nicht verbunden (keine
# Zugangsdaten). Versuch 2: nmcli verbindet + anode per curl erreichbar
# WAEHREND der Installation -- funktioniert. Verbindung ueberlebte den
# Reboot nicht (nmcli sprach vermutlich mit der Live-NetworkManager-Instanz,
# nicht mit einer, die ins Zielsystem schreibt). Versuch 3: direktes
# Schreiben der .nmconnection-Datei als eigener Preseed-Wert -- Datei kam
# nie an, kein Log vorhanden, vermutlich Debconf-Problem mit einem so
# langen, mehrzeiligen Preseed-Wert (lokaler Bash-Syntaxtest war fehlerfrei,
# also kein Bash-Problem). Versuch 4 (dieser): dasselbe Skript Base64-
# kodiert -- der Preseed-Wert besteht dadurch nur noch aus unkritischen
# Zeichen, keine Anfuehrungszeichen/Klammern mehr, die Debconf Probleme
# machen koennten.
ubiquity ubiquity/use_nonfree boolean true
ubiquity ubiquity/success_command string in-target bash -c 'echo IyEvYmluL2Jhc2gKewogIGVjaG8gInR1eGZsb3R0ZTogc3VjY2Vzc19jb21tYW5kIGxpZWYsICQoZGF0ZSAtdSkiCiAgZWNobyAiLS0tIG5tY2xpIGRldmljZSBzdGF0dXMgKHZvcmhlcikgLS0tIgogIG5tY2xpIGRldmljZSBzdGF0dXMgMj4mMQogIGVjaG8gIi0tLSBXTEFOIHZlcmJpbmRlbiAobGl2ZSwgZnVlciBkZW4gYW5vZGUtVGVzdCB3YWVocmVuZCBkZXIgSW5zdGFsbGF0aW9uKSAtLS0iCiAgbm1jbGkgZGV2aWNlIHdpZmkgcmVzY2FuIDI+JjEKICBzbGVlcCAzCiAgbm1jbGkgZGV2aWNlIHdpZmkgY29ubmVjdCBzdGFsbGludXggcGFzc3dvcmQgYTk4NzY1NDMyMSAyPiYxCiAgc2xlZXAgMwogIGVjaG8gIi0tLSBubWNsaSBkZXZpY2Ugc3RhdHVzIChuYWNoaGVyKSAtLS0iCiAgbm1jbGkgZGV2aWNlIHN0YXR1cyAyPiYxCiAgZWNobyAiLS0tIGN1cmwgYW5vZGUgL2hlYWx0aCAtLS0iCiAgY3VybCAtbSAxMCAtc1MgaHR0cHM6Ly9hbm9kZS50dXhmbG90dGUuZGUvaGVhbHRoIDI+JjEKICBlY2hvCiAgZWNobyAiLS0tIHNjaHJlaWJlIHBlcnNpc3RlbnRlcyBOZXR3b3JrTWFuYWdlci1Qcm9maWwgaW5zIFppZWxzeXN0ZW0gLS0tIgogIG1rZGlyIC1wIC9ldGMvTmV0d29ya01hbmFnZXIvc3lzdGVtLWNvbm5lY3Rpb25zCiAgQ09OTkZJTEU9L2V0Yy9OZXR3b3JrTWFuYWdlci9zeXN0ZW0tY29ubmVjdGlvbnMvc3RhbGxpbnV4Lm5tY29ubmVjdGlvbgogIHByaW50ZiAiJXNcbiIgIltjb25uZWN0aW9uXSIgImlkPXN0YWxsaW51eCIgInR5cGU9d2lmaSIgIlt3aWZpXSIgIm1vZGU9aW5mcmFzdHJ1Y3R1cmUiICJzc2lkPXN0YWxsaW51eCIgIlt3aWZpLXNlY3VyaXR5XSIgImtleS1tZ210PXdwYS1wc2siICJwc2s9YTk4NzY1NDMyMSIgIltpcHY0XSIgIm1ldGhvZD1hdXRvIiAiW2lwdjZdIiAibWV0aG9kPWF1dG8iID4gIiRDT05ORklMRSIKICBjaG1vZCA2MDAgIiRDT05ORklMRSIKICBjaG93biByb290OnJvb3QgIiRDT05ORklMRSIKICBlY2hvICJwZXJzaXN0ZW50ZXMgUHJvZmlsIGdlc2NocmllYmVuLiIKfSA+PiAvdmFyL2xvZy90dXhmbG90dGUtcG9zdGluc3RhbGwubG9nIDI+JjEKZWNobyAiZHZkLXdsYW4tdGVzdCAoQURSLTAwMDksIE1pbnQtRm9ydHNldHp1bmcpIC0tIHByb3Zpc2lvbmVkIGJ5IHR1eGZsb3R0ZSIgPiAvZXRjL21vdGQK | base64 -d | bash'

View File

@ -1,44 +0,0 @@
loadfont unicode
set color_normal=white/black
set color_highlight=black/light-gray
set timeout=30
menuentry "Start Linux Mint 22.3 Cinnamon 64-bit" --class linuxmint {
set gfxpayload=keep
linux /casper/vmlinuz boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} quiet splash --
initrd /casper/initrd.lz
}
menuentry "Start Linux Mint 22.3 Cinnamon 64-bit (compatibility mode)" {
linux /casper/vmlinuz boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} noapic noacpi nosplash irqpoll nomodeset --
initrd /casper/initrd.lz
}
menuentry "OEM install (for manufacturers)" {
set gfxpayload=keep
linux /casper/vmlinuz oem-config/enable=true only-ubiquity boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} quiet splash --
initrd /casper/initrd.lz
}
menuentry "Tuxflotte Auto-Install (Linux Mint 22.3 Cinnamon)" --class linuxmint {
set gfxpayload=keep
linux /casper/vmlinuz boot=casper uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint iso-scan/filename=${iso_path} file=/cdrom/preseed/tuxflotte.seed automatic-ubiquity noprompt priority=critical debian-installer/language=de keyboard-configuration/layoutcode=de quiet splash --
initrd /casper/initrd.lz
}
grub_platform
if [ "$grub_platform" = "efi" ]; then
menuentry 'Von lokaler Festplatte booten (Standard)' {
exit 1
}
set default="Von lokaler Festplatte booten (Standard)"
menuentry 'UEFI Firmware Settings' {
fwsetup
}
menuentry 'Memory test' {
linux /boot/memtest.efi
}
else
menuentry 'Von lokaler Festplatte booten (Standard)' {
exit 1
}
set default="Von lokaler Festplatte booten (Standard)"
fi

View File

@ -1,57 +0,0 @@
timeout 100
menu background splash.png
menu title Welcome to Linux Mint 22.3 64-bit
menu color screen 37;40 #80ffffff #00000000 std
MENU COLOR border 30;44 #40ffffff #a0000000 std
MENU COLOR title 1;36;44 #ffffffff #a0000000 std
MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel 37;44 #50ffffff #a0000000 std
MENU COLOR help 37;40 #c0ffffff #a0000000 std
MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std
MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std
MENU COLOR msg07 37;40 #90ffffff #a0000000 std
MENU COLOR tabmsg 31;40 #ffDEDEDE #00000000 std
MENU WIDTH 78
MENU MARGIN 15
MENU ROWS 6
MENU VSHIFT 10
MENU TABMSGROW 12
MENU CMDLINEROW 12
MENU HELPMSGROW 16
MENU HELPMSGENDROW 29
label tuxflotte
menu label Tuxflotte Auto-Install
kernel /casper/vmlinuz
append boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint file=/cdrom/preseed/tuxflotte.seed automatic-ubiquity noprompt priority=critical debian-installer/language=de keyboard-configuration/layoutcode=de quiet splash --
label live
menu label Start Linux Mint
kernel /casper/vmlinuz
append boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint quiet splash --
label compat
menu label Start Linux Mint in compatibility mode
linux /casper/vmlinuz
append boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint noapic noacpi nosplash irqpoll nomodeset --
label oem
menu label OEM install (for manufacturers)
linux /casper/vmlinuz
append oem-config/enable=true only-ubiquity boot=casper initrd=/casper/initrd.lz uuid=6e72f523-dc09-4880-8910-93ffa64401c5 username=mint hostname=mint quiet splash --
label hdt
menu label Hardware Detection
kernel hdt.c32
label local
menu label Boot from local drive
menu default
COM32 chain.c32
APPEND hd0
label memtest
menu label Memory test
linux /boot/memtest.bin

View File

@ -1,71 +0,0 @@
#! /bin/sh
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
# get pre-requisites
prereqs)
prereqs
exit 0
;;
esac
# Ueberschreibt das originale 99casperboot (macht nur `touch
# /run/.casper-boot`) statt einen zusaetzlichen, neuen Hook-Skriptnamen
# einzufuehren: initramfs-tools generiert beim ISO-Bau eine ORDER-Datei
# (scripts/casper-bottom/ORDER) mit der fest verdrahteten Aufruf-Reihenfolge
# aller casper-bottom-Skripte - real durch Extraktion des echten initrd.lz
# mit unmkinitramfs bestaetigt. Ein per Initrd-Cpio-Layer NEU hinzugefuegtes
# Skript taucht in dieser ORDER-Datei nicht auf und wird nie aufgerufen,
# selbst wenn die Datei im initramfs physisch vorhanden ist (erste Version
# dieses Fixes ist daran gescheitert). Das Ueberschreiben eines bereits in
# ORDER referenzierten Pfads funktioniert dagegen zuverlaessig (spaetere
# cpio-Schichten ueberschreiben Dateiinhalte am selben Pfad, siehe
# scripts/lib/initrd.sh - derselbe Mechanismus, der schon fuer das
# Kexec-Preseed in Phase 1 verifiziert wurde). 99casperboot ist laut ORDER
# das letzte casper-bottom-Skript ueberhaupt - garantiert nach 05mountpoints,
# das /cdrom nach /root/cdrom umhaengt und damit die Voraussetzung fuer den
# folgenden Kopiervorgang schafft.
if [ -d /root/cdrom/updates ]; then
cp -a /root/cdrom/updates/. /root/
fi
# Ubiquity stuerzt im automatic-ubiquity-GTK-Modus real reproduzierbar ab
# (TypeError: Argument 1 does not allow None as a value), sobald
# enable_download_updates(False) den Netzwerkstatus als "nicht verbunden"
# meldet: ubi-prepare.py uebergibt das Ergebnis von
# self.controller.get_string('ubiquity/text/label_download_updates_na')
# ungeprueft an GtkLabel.set_label() - fehlt fuer diese Vorlage/Sprache ein
# Uebersetzungsstring, liefert get_string() None statt eines leeren Strings,
# und GTK akzeptiert kein None als Label-Text (real ueber QEMU-Testlauf
# gefunden, siehe ADR-0023-Nachtrag). Gezielter Sed-Patch statt vollstaendigem
# Datei-Ersatz, um nicht die komplette Drittanbieter-Datei mitpflegen zu
# muessen - betrifft nur die eine Zeile, "or ''" faengt jeden None-Rueckgabewert
# ab, unabhaengig von der genauen Ursache der fehlenden Uebersetzung.
ubi_prepare="/root/usr/lib/ubiquity/plugins/ubi-prepare.py"
if [ -f "$ubi_prepare" ]; then
sed -i \
"s/self\.controller\.get_string(template))/self.controller.get_string(template) or '')/" \
"$ubi_prepare"
fi
# AT-SPI-Bruecke fuer den Autoklicker (atspi_click.py) muss VOR dem
# GTK-Start von Ubiquity aktiv sein - siehe live-updates/usr/share/ubiquity/
# tuxflotte-atspi-env.sh (dorthin per casper-bottom-Kopie oben bereits nach
# /root/usr/share/ubiquity/ ausgeliefert). Einzeilige Einbindung direkt nach
# dem fruehen Existenz-Check von ubiquity-dm - alles Weitere, inkl.
# Paketinstallation, uebernimmt die eingebundene Datei selbst.
start_ubiquity_dm="/root/usr/share/ubiquity/start-ubiquity-dm"
if [ -f "$start_ubiquity_dm" ]; then
sed -i \
'/^test -x \/usr\/bin\/ubiquity-dm || exit 0$/a\
. /usr/share/ubiquity/tuxflotte-atspi-env.sh' \
"$start_ubiquity_dm"
fi
touch /run/.casper-boot

View File

@ -1 +0,0 @@
mint ALL=(ALL) NOPASSWD: ALL

View File

@ -1 +0,0 @@
../tuxflotte-autoclicker.service

View File

@ -1,17 +0,0 @@
[Unit]
Description=Tuxflotte Autoklicker fuer Ubiquitys GTK-Installationsassistenten
# Nur bei automatisierter Installation relevant (siehe autoclicker.sh -
# prueft selbst auf das Boot-Keyword "tuxflotte-autoclick" und beendet sich
# sofort, wenn es fehlt). Bewusst kein "After=graphical.target" o.ae., da
# ubiquity-dm keine normale Display-Manager-Sitzung startet, deren
# Bereitschaft systemd auf ueblichem Weg erkennen wuerde - das Skript selbst
# wartet auf den X-Socket.
After=multi-user.target
[Service]
Type=simple
ExecStart=/opt/tuxflotte/scripts/autoclicker.sh
Restart=no
[Install]
WantedBy=multi-user.target

View File

@ -1,4 +0,0 @@
[Desktop Entry]
Type=Application
Name=SELinux Troubleshooter Notifier
Hidden=true

View File

@ -1,6 +0,0 @@
[Desktop Entry]
Type=Application
Name=Tuxflotte Provisioning
Exec=/opt/tuxflotte/kiosk/start-kiosk.sh
X-GNOME-Autostart-enabled=true
NoDisplay=true

View File

@ -1,33 +0,0 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Tuxflotte Provisioning</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #ffffff;
color: #3465a4;
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
img {
max-width: 60%;
margin-bottom: 2rem;
}
p {
font-size: 1.2rem;
}
</style>
</head>
<body>
<img src="logo.svg" alt="Tuxflotte">
<p>Kiosk-Oberfläche in Entwicklung.</p>
</body>
</html>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 77 KiB

View File

@ -1,42 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# German keyboard layout for the graphical session (vconsole.keymap=de on the
# kernel cmdline only covers the text console, not X11/Wayland input). Cinnamon
# ships its own parallel org.cinnamon.desktop.input-sources schema, separate
# from GNOME's org.gnome.desktop.input-sources — writing org.gnome alone had
# no effect (confirmed on real hardware), so both are set here.
dconf write /org/cinnamon/desktop/input-sources/sources "[('xkb', 'de')]"
dconf write /org/gnome/desktop/input-sources/sources "[('xkb', 'de')]"
# Suppresses Epiphany's "set as default browser?" first-run prompt (schema
# default is true, see /usr/share/glib-2.0/schemas/org.gnome.epiphany.gschema.xml).
dconf write /org/gnome/epiphany/ask-for-default false
# Kiosk cleanup: no desktop icons (Nemo). The panel itself is kept (removing
# the last panel via panels-enabled triggers Cinnamon's own "you don't have
# any panels, open panel settings?" confirmation dialog — hardcoded in
# js/ui/panel.js, no way to suppress it, so panels-enabled is deliberately
# left untouched). Instead, only the two useful applets are kept: a window
# list (to restore Epiphany if a user minimizes it) and power (shutdown/
# reboot) — everything else (menu, systray, clock, workspace switcher etc.)
# is removed. Deliberately the classic window-list@cinnamon.org, not
# grouped-window-list@cinnamon.org: the grouped variant ships default
# "pinned apps" launchers (firefox.desktop, org.gnome.Terminal.desktop,
# nemo.desktop — confirmed via its settings-schema.json) that showed up in
# the panel even with unrelated applets removed. Plain window-list has no
# pinned-apps concept at all, only ever shows actually open windows.
dconf write /org/nemo/desktop/show-desktop-icons false
dconf write /org/cinnamon/enabled-applets "['panel1:left:0:window-list@cinnamon.org:1', 'panel1:right:0:power@cinnamon.org:2']"
mkdir -p "${HOME}/.local/share/tuxflotte-kiosk"
# Auto-Modus (personalisierte Kunden-ISO, siehe scripts/build_customer_iso.sh)
# startet den Installer sofort unbeaufsichtigt statt der Kiosk-Startseite -
# config/installer.conf wird nur bei personalisierten Abbildern mitgeliefert,
# das Standard-/Testabbild bleibt unverändert interaktiv.
if grep -qE '^\s*(export\s+)?TUXFLOTTE_AUTO_MODE=true\s*$' /opt/tuxflotte/config/installer.conf 2>/dev/null; then
exec sudo /opt/tuxflotte/scripts/installer.sh
fi
exec epiphany --profile="${HOME}/.local/share/tuxflotte-kiosk" file:///opt/tuxflotte/kiosk/index.html

View File

@ -1,308 +0,0 @@
#!/usr/bin/env python3
"""Klickt den primaeren "Weiter"-Button der aktuell sichtbaren Ubiquity-Seite
per AT-SPI (Linux-Barrierefreiheits-API) - wird von autoclicker.sh wiederholt
aufgerufen (ein Prozessstart pro Klickversuch, absichtlich einfach statt
dauerhaft im Hintergrund am Accessibility-Bus zu lauschen).
Warum AT-SPI statt Bildschirmkoordinaten: der urspruengliche xdotool-Ansatz
scheiterte daran, dass Metacity mehrere ueberlappende X11-Fenster aehnlicher
Groesse erzeugt (WM-Dekorationsrahmen, Hilfsfenster) - eine "groesstes
sichtbares Fenster"-Heuristik traf wiederholt das falsche Fenster (siehe
ADR-0023-Nachtrag). AT-SPI identifiziert den Button stattdessen ueber seine
Rolle (PUSH_BUTTON) und seinen Namen im Accessibility-Baum der Anwendung
selbst - unabhaengig von Fenstergeometrie/-dekoration.
WICHTIG (real beim Testen entdeckt, 29.08.2026): trotz fest gesetztem
"debian-installer/language=de" sind NICHT alle Assistentenseiten auf
Deutsch beschriftet - die Mint-eigene "Multimedia-Codecs"-Seite zeigt reale
englische Buttons ("Quit"/"Back"/"Continue"), vermutlich dieselbe Ursache
wie der bereits gepatchte ubi-prepare.py-None-Bug (fehlender/unvollstaendiger
.mo-Uebersetzungskatalog fuer dieses Plugin auf dem Live-Medium). Eine reine
Werteliste bekannter deutscher Beschriftungen (fruehere Version dieses
Skripts) haette hier fuer immer haengen bleiben. Die Logik wurde deshalb auf
eine sprachunabhaengige Ausschluss-Strategie umgestellt: bekannte
rueckwaerts-/abbrechende Beschriftungen (zweisprachig) werden aus der
Kandidatenliste entfernt, der bzw. die verbleibenden Kandidaten werden
geklickt - siehe select_target() fuer die genaue Regel samt
Sicherheitsverhalten bei Mehrdeutigkeit.
Sprache ist durch "debian-installer/language=de" im Kexec-Append-Preseed
grundsaetzlich auf de_DE gesetzt (siehe backend.sh), s.o. aber nicht
verlaesslich fuer jede einzelne Seite.
NACHTRAG (real beim Testen entdeckt, 29.08.2026): auf der manuellen
Partitionierungsseite ("Etwas Anderes") lief der Klick auf "Jetzt
installieren" nachweislich zuverlaessig (AT-SPI meldete jedes Mal Erfolg),
aber die Seite kam nie voran - stattdessen loeste jeder Klick erneut die
Debconf-Frage "ubiquity/partman-rebuild-cache" aus
(/lib/partman/update.d/99signal_ubiquity), was ubi-partman.py zurueck auf
dieselbe Seite wirft (siehe ADR-0023-Nachtrag). Ein gezielter Fix am
Partitionsrezept (kein zusaetzliches $bootable{ } auf der Root-Partition
unter UEFI) aenderte NICHTS - das deutet auf einen zeitlichen statt
strukturellen Fehler hin: die Seite zeigt waehrend dieser Phase ein
Status-Panel "Konfiguration der Installation wird ueberprueft ..." - klickt
der Autoklicker WAEHREND dieser Hintergrundpruefung noch laeuft, koennte
er sie unterbrechen/neu anstossen, statt ihr Zeit zum Abschliessen zu
geben. Dieses Skript wartet deshalb jetzt aktiv, solange dieser Text
sichtbar ist (siehe VALIDATION_TEXT_MARKERS/find_showing_text()), und
klickt erst, wenn er verschwunden ist.
Exit-Codes: 0 = ein Button wurde gefunden und geklickt,
1 = kein eindeutiger Button gefunden (z.B. Seite im Ladezustand, oder echte
Mehrdeutigkeit - dann bewusst NICHT geklickt, siehe select_target()),
2 = pyatspi/AT-SPI-Bus nicht erreichbar (Bruecke evtl. nicht aktiv),
3 = Ubiquitys eigene Hintergrundpruefung laeuft noch sichtbar - bewusst
nicht geklickt, siehe Nachtrag oben.
"""
import sys
try:
import pyatspi
except ImportError:
print("pyatspi nicht verfuegbar", file=sys.stderr)
sys.exit(2)
# Zweisprachig, da nicht jede Seite verlaesslich deutsch beschriftet ist
# (siehe Modul-Docstring). Diese Namen werden aus der Kandidatenliste
# AUSGESCHLOSSEN, nie geklickt - unabhaengig davon, ob sie der einzige
# sichtbare Button waeren.
EXCLUDE_LABELS = {
"zurück",
"zurueck",
"back",
"abbrechen",
"abbruch",
"cancel",
"beenden",
"quit",
"exit",
"verwerfen",
}
# Bevorzugt geklickt, falls mehrere Kandidaten nach dem Ausschluss uebrig
# bleiben (siehe select_target()). Kein Ausschlusskriterium fuer
# Kandidaten, die HIER nicht drin stehen - die duerfen trotzdem als
# alleiniger Kandidat geklickt werden (Sole-Survivor-Regel), damit auch
# unbekannte/nicht uebersetzte Beschriftungen wie das reale "Continue"
# oben nicht zum Stillstand fuehren.
PREFER_LABELS = {
"weiter",
"continue",
"next",
"jetzt installieren",
"install now",
"fortfahren",
"proceed",
"installieren",
"install",
"neu starten",
"restart now",
"schließen",
"schliessen",
"close",
"finish",
"fertigstellen",
"done",
}
MAX_DEPTH = 40
# Zweisprachig vorsichtshalber (siehe Multimedia-Codecs-Erfahrung oben),
# auch wenn dieser konkrete Text bisher immer deutsch beobachtet wurde.
VALIDATION_TEXT_MARKERS = (
"wird überprüft",
"wird ueberprueft",
"being checked",
"checking the install",
)
def _node_text(node):
"""Sammelt allen aus einem Accessible auslesbaren Text: .name,
.description UND (falls unterstuetzt) das separate Text-Interface.
WICHTIG (real beim Testen entdeckt, 29.08.2026): ein einfaches GtkLabel
exponiert seinen sichtbaren Text ueblicherweise NICHT ueber .name/
.description (die bleiben dort oft leer) sondern ausschliesslich ueber
das AT-SPI-Text-Interface (queryText().getText(0, -1)) - ohne diesen
dritten Zugriffsweg fand find_showing_text() den sichtbar angezeigten
Status-Text nie, obwohl er laut Screenshot die ganze Zeit auf dem Schirm
stand."""
parts = []
try:
if node.name:
parts.append(node.name)
except Exception:
pass
try:
if node.description:
parts.append(node.description)
except Exception:
pass
try:
text_iface = node.queryText()
content = text_iface.getText(0, -1)
if content:
parts.append(content)
except NotImplementedError:
pass
except Exception:
pass
return " ".join(parts).strip().lower()
def find_showing_text(node, needle, depth=0):
"""Sucht ein sichtbares Accessible, dessen Text (siehe _node_text()) den
Suchtext enthaelt - unabhaengig von seiner Rolle (Label/Expander/...),
da Ubiquitys Status-Panel-Text nicht zuverlaessig unter derselben Rolle
exponiert sein muss. Bei Unsicherheit ueber den Sichtbarkeitsstatus
bewusst konservativ (lieber einen Zyklus zu viel warten als einen
Klick mitten in die Pruefung setzen)."""
if node is None or depth > MAX_DEPTH:
return False
if needle in _node_text(node):
try:
state = node.getState()
return state.contains(pyatspi.STATE_SHOWING)
except Exception:
return True
try:
count = node.childCount
except Exception:
return False
for i in range(count):
try:
child = node.getChildAtIndex(i)
except Exception:
continue
if find_showing_text(child, needle, depth + 1):
return True
return False
def collect_buttons(node, depth=0, acc=None):
"""Alle sichtbaren+aktivierten Push-Buttons im Baum einsammeln (Rolle,
Zustand). Absichtlich ALLE Kandidaten sammeln statt beim ersten Treffer
abzubrechen - select_target() braucht die vollstaendige Liste, um
Mehrdeutigkeit erkennen zu koennen."""
if acc is None:
acc = []
if node is None or depth > MAX_DEPTH:
return acc
try:
role = node.getRole()
except Exception:
return acc
if role == pyatspi.ROLE_PUSH_BUTTON:
try:
state = node.getState()
if state.contains(pyatspi.STATE_SENSITIVE) and state.contains(
pyatspi.STATE_SHOWING
):
acc.append(node)
except Exception:
pass
return acc
try:
count = node.childCount
except Exception:
return acc
for i in range(count):
try:
child = node.getChildAtIndex(i)
except Exception:
continue
collect_buttons(child, depth + 1, acc)
return acc
def select_target(buttons):
"""Waehlt aus allen sichtbaren+aktivierten Buttons genau einen aus, der
geklickt werden darf - oder None, wenn das nicht sicher moeglich ist.
Regel (bewusst sicherheitsorientiert, siehe Modul-Docstring):
1. Bekannte rueckwaerts-/abbrechende Beschriftungen (EXCLUDE_LABELS)
werden nie geklickt, unabhaengig vom Rest.
2. Von den verbleibenden Kandidaten wird bevorzugt, wer eine bekannte
vorwaerts-/abschliessende Beschriftung traegt (PREFER_LABELS).
3. Bleibt nach 1./2. KEIN bekannter Treffer, aber genau EIN Kandidat
uebrig, wird dieser geklickt (Sole-Survivor-Regel - deckt unbekannte/
nicht uebersetzte Beschriftungen wie das real beobachtete "Continue"
ab, siehe Modul-Docstring).
4. Bleiben mehrere unbekannte Kandidaten gleichzeitig uebrig, wird NICHT
geklickt (echte Mehrdeutigkeit - ein Fehlklick waere hier riskanter
als ein uebersprungener Zyklus, die Schleife in autoclicker.sh
versucht es 2 Sekunden spaeter erneut)."""
candidates = []
for btn in buttons:
name = (btn.name or "").strip().lower()
if name in EXCLUDE_LABELS:
continue
candidates.append(btn)
if not candidates:
return None
preferred = [b for b in candidates if (b.name or "").strip().lower() in PREFER_LABELS]
if preferred:
return preferred[0]
if len(candidates) == 1:
return candidates[0]
return None
def main():
try:
desktop = pyatspi.Registry.getDesktop(0)
except Exception as exc:
print(f"AT-SPI-Desktop nicht erreichbar: {exc}", file=sys.stderr)
return 2
apps = []
for i in range(desktop.childCount):
try:
app = desktop.getChildAtIndex(i)
except Exception:
continue
if app is not None:
apps.append(app)
for marker in VALIDATION_TEXT_MARKERS:
for app in apps:
if find_showing_text(app, marker):
print(
f"Hintergrundpruefung noch sichtbar ('{marker}'), warte statt zu klicken.",
file=sys.stderr,
)
return 3
all_buttons = []
for app in apps:
collect_buttons(app, 0, all_buttons)
target = select_target(all_buttons)
if target is None:
names = [repr((b.name or "").strip()) for b in all_buttons]
print(
"kein eindeutiger Button gefunden, sichtbare Buttons: "
+ (", ".join(names) if names else "(keine)"),
file=sys.stderr,
)
return 1
try:
action = target.queryAction()
action.doAction(0)
except Exception as exc:
print(f"Klick auf '{target.name}' fehlgeschlagen: {exc}", file=sys.stderr)
return 1
print(f"geklickt: {target.name}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,113 +0,0 @@
#!/bin/bash
### Klickt automatisiert durch Ubiquitys GTK-Installationsassistenten
### (automatic-ubiquity) - noetig, weil Ubiquitys eigenes "noninteractive"-
### Frontend die gefuehrte Partitionierung nicht zuverlaessig automatisieren
### kann (endlose choose_partition-Schleife in ubi-partman.py, real
### bestaetigt, siehe ADR-0023-Nachtrag). Der GTK-Assistent fuellt jede Seite
### bereits vollstaendig aus dem Preseed vor (siehe preseed.tpl) - es fehlt
### nur der "Weiter"/"Installieren"-Klick pro Seite.
###
### Klickt ueber AT-SPI (Barrierefreiheits-API), nicht mehr ueber
### Bildschirmkoordinaten/Enter-Taste (siehe atspi_click.py fuer die
### eigentliche Klicklogik). Der fruehere xdotool-Ansatz scheiterte real
### daran, dass Metacity mehrere ueberlappende X11-Fenster aehnlicher
### Groesse erzeugt (WM-Dekorationsrahmen, Hilfsfenster) - weder eine
### "groesstes sichtbares Fenster"-Heuristik noch die Enter-Taste (aktiviert
### auf der Partitionierungs-Uebersichtsseite einen Ausklapp-Pfeil statt des
### "Jetzt installieren"-Buttons) waren zuverlaessig genug (siehe
### ADR-0023-Nachtrag). AT-SPI identifiziert den Button stattdessen ueber
### seine Rolle+seinen Namen im Accessibility-Baum der Anwendung selbst -
### unabhaengig von Fenstergeometrie/-dekoration.
###
### Die AT-SPI-Bruecke selbst (GTK_MODULES=gail:atk-bridge, Session-/A11y-
### Bus) wird NICHT hier, sondern bereits VOR dem GTK-Start von Ubiquity in
### tuxflotte-atspi-env.sh aktiviert (eingebunden von start-ubiquity-dm,
### siehe Sed-Patch in initrd-hooks/casper-bottom/99casperboot) - eine erst
### nachtraeglich aktivierte Bruecke wird von einer bereits laufenden
### GTK-Anwendung nicht mehr nachgeladen.
###
### Wird per systemd-Service (siehe ...service im selben Ausliefer-Layer) bei
### JEDEM Boot gestartet, bricht aber sofort ab, wenn das eigene Boot-Keyword
### "tuxflotte-autoclick" nicht in /proc/cmdline steht - siehe backend_launch()
### in backends/mint/backend.sh, wo dieses Keyword gesetzt wird. Auf einem
### normalen (nicht automatisierten) Boot also wirkungslos.
set -u
LOG=/var/log/tuxflotte-autoclicker.log
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >>"${LOG}"
}
grep -qw tuxflotte-autoclick /proc/cmdline || exit 0
log "tuxflotte-autoclick erkannt, starte Autoklicker."
export DISPLAY=:0
is_ubiquity_running() {
pgrep -f '/usr/bin/ubiquity' >/dev/null 2>&1
}
# ubiquity-dm startet Ubiquity frueh im Boot zu einem nicht exakt
# vorhersagbaren Zeitpunkt - auf den Prozess warten statt auf eine feste
# Wartezeit zu vertrauen. AT-SPI braucht anders als der fruehere
# xdotool-Ansatz keinen eigenen X-Socket-Check - der Zugriff laeuft ueber
# den Accessibility-Bus, nicht direkt ueber X11.
ubiquity_ready=0
for _ in $(seq 1 120); do
if is_ubiquity_running; then
ubiquity_ready=1
break
fi
sleep 1
done
if [ "${ubiquity_ready}" -ne 1 ]; then
log "ubiquity-Prozess nach 120s nicht erschienen, breche ab."
exit 1
fi
log "ubiquity-Prozess erkannt."
if ! python3 -c 'import pyatspi' >/dev/null 2>&1; then
log "pyatspi fehlt auf dem Live-Medium, installiere nach."
if ! apt-get install -y python3-pyatspi >>"${LOG}" 2>&1; then
log "pyatspi-Installation fehlgeschlagen, breche ab."
exit 1
fi
fi
ATSPI_CLICK=/opt/tuxflotte/scripts/atspi_click.py
log "Starte Klick-Schleife."
# Harte Obergrenze als Sicherheitsnetz (60 Minuten) - falls die Installation
# haengen bleibt, soll dieser Dienst nicht unbegrenzt weiterlaufen.
end=$((SECONDS + 3600))
clicks=0
misses=0
while [ "${SECONDS}" -lt "${end}" ]; do
if ! is_ubiquity_running; then
log "ubiquity-Prozess nicht mehr aktiv (vermutlich fertig oder abgestuerzt), beende Schleife nach ${clicks} Klick(s)."
break
fi
out="$(python3 "${ATSPI_CLICK}" 2>&1)"
rc=$?
if [ "${rc}" -eq 0 ]; then
clicks=$((clicks + 1))
misses=0
log "Klick ${clicks}: ${out}"
elif [ "${rc}" -eq 2 ]; then
log "AT-SPI-Bus nicht erreichbar, breche ab: ${out}"
break
elif [ "${rc}" -eq 3 ]; then
# Ubiquitys eigene Hintergrundpruefung laeuft noch sichtbar (siehe
# atspi_click.py-Nachtrag) - bewusst NICHT als Fehlversuch zaehlen,
# nur abwarten. Real beim Testen entdeckt: ein Klick auf "Jetzt
# installieren" WAEHREND dieser Pruefung fuehrte wiederholt in die
# partman-rebuild-cache-Schleife statt zum eigentlichen Commit.
log "wartet auf Hintergrundpruefung: ${out}"
else
misses=$((misses + 1))
log "kein Treffer (${misses}): ${out}"
fi
sleep 2
done
log "Autoklicker beendet (${clicks} Klick(s) insgesamt)."

View File

@ -1,54 +0,0 @@
#!/bin/sh
### Wird per Ein-Zeilen-Sed-Patch (siehe initrd-hooks/casper-bottom/99casperboot)
### GANZ AM ANFANG von start-ubiquity-dm eingebunden ("."), also BEVOR
### ubiquity-dm den X-Server startet und Ubiquity selbst exec't.
###
### Zweck: die AT-SPI-Barrierefreiheits-Bruecke (atk-bridge) fuer Ubiquitys
### GTK-Prozess aktivieren, damit atspi_click.py (siehe
### /opt/tuxflotte/scripts/) Buttons ueber ihre Rolle/ihren Namen im
### Accessibility-Baum anklicken kann statt ueber Bildschirmkoordinaten.
### Der xdotool-Koordinaten-Ansatz ist an ueberlappenden X11-Fenstern durch
### die Metacity-Fensterdekoration gescheitert (mehrere Fenster aehnlicher
### Groesse, "groesstes Fenster"-Heuristik traf wiederholt das falsche -
### siehe ADR-0023-Nachtrag).
###
### WICHTIG: das muss VOR dem GTK-Init von Ubiquity passieren - eine erst
### nachtraeglich verfuegbare Bruecke wird von einer bereits laufenden
### GTK-Anwendung nicht mehr nachgeladen (GTK_MODULES wird nur beim Start
### ausgewertet). Deshalb blockierend (kein Hintergrund-"&" fuer die
### Paketinstallation) und deshalb im Skript, das ubiquity-dm selbst
### aufruft, statt im spaeter laufenden Autoklicker-Service.
###
### Absichtlich EIN gemeinsamer apt-get-Aufruf fuer alle drei Pakete (statt
### getrennter Aufrufe je Skript) - der Autoklicker installiert xdotool
### zeitlich nah, ein zweiter paralleler apt-get/dpkg-Aufruf haette real
### beobachtete Lock-Konflikte riskiert (debconf: "config.dat is locked by
### another process").
for pkg in at-spi2-core dbus-x11 python3-pyatspi; do
dpkg -s "$pkg" >/dev/null 2>&1 && continue
if [ "${_tuxflotte_atspi_apt_updated:-}" != "1" ]; then
apt-get update -qq
_tuxflotte_atspi_apt_updated=1
fi
DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg"
done
export GTK_MODULES="gail:atk-bridge"
export NO_AT_BRIDGE=0
# Eigene Session-Bus-Instanz, falls keine geerbt wurde - die minimale
# ubiquity-dm-X-Sitzung startet anders als eine normale Desktop-Sitzung
# vermutlich keinen eigenen Session-Bus.
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] && command -v dbus-launch >/dev/null 2>&1; then
eval "$(dbus-launch --sh-syntax)"
export DBUS_SESSION_BUS_ADDRESS DBUS_SESSION_BUS_PID
fi
if command -v at-spi-bus-launcher >/dev/null 2>&1; then
at-spi-bus-launcher --launch-immediately >/var/log/tuxflotte-atspi-bus.log 2>&1 &
# Kurz abwarten, bis der a11y-Bus unter org.a11y.Bus registriert ist,
# bevor ubiquity-dm/Ubiquity (naechste Zeile in start-ubiquity-dm)
# GTK initialisiert.
sleep 1
fi

View File

@ -1,6 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# Nur noch fuer das Fedora-Backend (Kickstart/Anaconda) - das Mint-Backend
# ist auf das eigenstaendige, per live-build gebaute Boot-Medium umgezogen
# (siehe scripts/build_boot_medium.sh, boot-medium/). Der bisherige
# Mint-Zweig hier (Casper-Patchen einer echten Mint-Live-ISO + Ubiquity-
# Preseed) ist damit Geschichte - siehe ADR-0025 fuer die Begruendung.
SOURCE_ISO="${1:-}" SOURCE_ISO="${1:-}"
BACKEND="${2:-}" BACKEND="${2:-}"
@ -10,18 +16,8 @@ BUILD_DIR="$BASE_DIR/build"
WORK_DIR="$BUILD_DIR/work" WORK_DIR="$BUILD_DIR/work"
OUTPUT_DIR="$BUILD_DIR/output" OUTPUT_DIR="$BUILD_DIR/output"
# shellcheck source=lib/initrd.sh
source "$REPO_DIR/scripts/lib/initrd.sh"
# Test-Device fuer den in bake_test_preseed() eingebrannten, nicht
# personalisierten Test-Preseed (Mint) - dasselbe "Default Lab"-Testgeraet,
# das schon fuer die Fedora-Verifikation genutzt wurde. Echte Personalisierung
# pro Kunde ist nicht Teil dieses Build-Skripts (siehe ADR-0011-Kontext).
TEST_DEVICE_ID="a0238a0b-d2b5-4516-a6ce-da7170041d11"
TEST_HOSTNAME="tuxflotte-mint-test"
usage() { usage() {
echo "Usage: $0 /path/to/source.iso <fedora|mint>" echo "Usage: $0 /path/to/source.iso fedora"
} }
check_input() { check_input() {
@ -35,13 +31,10 @@ check_input() {
exit 1 exit 1
fi fi
case "$BACKEND" in if [[ "$BACKEND" != "fedora" ]]; then
fedora|mint) ;; echo "Error: unbekanntes Backend '$BACKEND' (erwartet: fedora)"
*)
echo "Error: unbekanntes Backend '$BACKEND' (erwartet: fedora oder mint)"
exit 1 exit 1
;; fi
esac
} }
check_dependencies() { check_dependencies() {
@ -51,15 +44,6 @@ check_dependencies() {
exit 1 exit 1
fi fi
done done
if [[ "$BACKEND" == "mint" ]]; then
for cmd in jq envsubst base64 cpio; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Error: missing dependency: $cmd"
exit 1
fi
done
fi
} }
prepare_dirs() { prepare_dirs() {
@ -87,135 +71,39 @@ patch_grub() {
# -f: xorriso extrahiert ISO-Dateien mit deren Original-Rechten, die auf # -f: xorriso extrahiert ISO-Dateien mit deren Original-Rechten, die auf
# Live-Medien haeufig read-only (444) sind - ohne -f schlaegt cp beim # Live-Medien haeufig read-only (444) sind - ohne -f schlaegt cp beim
# Ueberschreiben mit "Keine Berechtigung" fehl. # Ueberschreiben mit "Keine Berechtigung" fehl.
if [[ "$BACKEND" == "fedora" ]]; then
cp -f "$REPO_DIR/grub/EFI-BOOT-grub.cfg" \ cp -f "$REPO_DIR/grub/EFI-BOOT-grub.cfg" \
"$WORK_DIR/EFI/BOOT/grub.cfg" "$WORK_DIR/EFI/BOOT/grub.cfg"
cp -f "$REPO_DIR/grub/boot-grub2-grub.cfg" \ cp -f "$REPO_DIR/grub/boot-grub2-grub.cfg" \
"$WORK_DIR/boot/grub2/grub.cfg" "$WORK_DIR/boot/grub2/grub.cfg"
else
# Mint hat, anders als Fedora, keine zweite ESP/FAT-Kopie des GRUB-Menüs,
# die separat gepatcht werden müsste (real gegen die vorhandene
# tuxflotte-mint-test.iso verifiziert - x86_64-efi/grub.cfg ist nur ein
# Loader-Stub, der per `source /boot/grub/grub.cfg` zurückverweist).
cp -f "$REPO_DIR/grub/mint-boot-grub.cfg" \
"$WORK_DIR/boot/grub/grub.cfg"
cp -f "$REPO_DIR/grub/mint-isolinux-live.cfg" \
"$WORK_DIR/isolinux/live.cfg"
fi
} }
verify_workdir() { verify_workdir() {
echo "Verifying workdir..." echo "Verifying workdir..."
if [[ "$BACKEND" == "fedora" ]]; then
grep -q "Tuxflotte" "$WORK_DIR/EFI/BOOT/grub.cfg" grep -q "Tuxflotte" "$WORK_DIR/EFI/BOOT/grub.cfg"
grep -q "Tuxflotte" "$WORK_DIR/boot/grub2/grub.cfg" grep -q "Tuxflotte" "$WORK_DIR/boot/grub2/grub.cfg"
else
grep -q "Tuxflotte" "$WORK_DIR/boot/grub/grub.cfg"
grep -q "Tuxflotte" "$WORK_DIR/isolinux/live.cfg"
fi
echo "GRUB verification passed." echo "GRUB verification passed."
} }
bake_test_preseed() {
[[ "$BACKEND" == "mint" ]] || return 0
echo "Rendering test preseed (nicht personalisiert, siehe TEST_DEVICE_ID)..."
local preseed_dir="$WORK_DIR/preseed"
local runtime_blueprint_file="$WORK_DIR/runtime_blueprint.json"
local server_response_file="$WORK_DIR/response.json"
mkdir -p "$preseed_dir"
cat > "$runtime_blueprint_file" <<EOF
{
"runtime_blueprint": {
"backend_id": "mint",
"blueprints": [],
"installation_directives": {
"disk_encryption": false,
"partitioning": {"scheme": "single", "root_filesystem": "ext4"},
"secure_boot_required": false
}
}
}
EOF
cat > "$server_response_file" <<EOF
{
"device": {"hostname": "${TEST_HOSTNAME}", "id": "${TEST_DEVICE_ID}"}
}
EOF
# backend.sh direkt wiederverwenden statt die Templating-Logik hier zu
# duplizieren - Variablen sind bewusst nicht readonly (siehe backend.sh),
# backend_init() wird übersprungen (braucht root für --owner/--group,
# hier nicht nötig, mkdir reicht für den Build-Kontext).
# shellcheck disable=SC1091
source "$REPO_DIR/backends/mint/backend.sh"
RUNTIME_BLUEPRINT_FILE="$runtime_blueprint_file"
SERVER_RESPONSE_FILE="$server_response_file"
RUNTIME_DIR="$preseed_dir"
CONFIG_FILE="$preseed_dir/tuxflotte.seed"
backend_validate
backend_generate_config
echo "Test-Preseed erzeugt: $CONFIG_FILE"
}
prepare_updates() { prepare_updates() {
echo "Assembling live-updates payload..." echo "Assembling updates payload..."
local updates_dir="$WORK_DIR/updates" local updates_dir="$WORK_DIR/updates"
rm -rf "$updates_dir" rm -rf "$updates_dir"
mkdir -p "$updates_dir/opt/tuxflotte" mkdir -p "$updates_dir/opt/tuxflotte"
cp -a "$REPO_DIR/live-updates/." "$updates_dir/"
cp -a "$REPO_DIR/scripts" "$updates_dir/opt/tuxflotte/" cp -a "$REPO_DIR/scripts" "$updates_dir/opt/tuxflotte/"
cp -a "$REPO_DIR/backends" "$updates_dir/opt/tuxflotte/" cp -a "$REPO_DIR/backends" "$updates_dir/opt/tuxflotte/"
cp -a "$REPO_DIR/config" "$updates_dir/opt/tuxflotte/" cp -a "$REPO_DIR/config" "$updates_dir/opt/tuxflotte/"
chmod 0440 "$updates_dir/etc/sudoers.d/90-tuxflotte"
}
patch_initrd() {
[[ "$BACKEND" == "mint" ]] || return 0
echo "Injecting casper-bottom updates-hook into initrd..."
inject_casper_bottom_hooks "$REPO_DIR" \
"$WORK_DIR/casper/initrd.lz" \
"$WORK_DIR/casper/initrd-tuxflotte.lz"
} }
create_iso() { create_iso() {
echo "Creating Tuxflotte ISO..." echo "Creating Tuxflotte ISO..."
local output_iso local output_iso="$OUTPUT_DIR/tuxflotte-provisioning-0.2.iso"
local -a grub_map_args
if [[ "$BACKEND" == "fedora" ]]; then
output_iso="$OUTPUT_DIR/tuxflotte-provisioning-0.2.iso"
grub_map_args=(
-map "$REPO_DIR/grub/EFI-BOOT-grub.cfg" /EFI/BOOT/grub.cfg
-map "$REPO_DIR/grub/boot-grub2-grub.cfg" /boot/grub2/grub.cfg
)
else
output_iso="$OUTPUT_DIR/tuxflotte-mint-provisioning-0.1.iso"
grub_map_args=(
-map "$REPO_DIR/grub/mint-boot-grub.cfg" /boot/grub/grub.cfg
-map "$REPO_DIR/grub/mint-isolinux-live.cfg" /isolinux/live.cfg
-map "$WORK_DIR/preseed/tuxflotte.seed" /preseed/tuxflotte.seed
-map "$WORK_DIR/casper/initrd-tuxflotte.lz" /casper/initrd.lz
)
fi
rm -f "$output_iso" rm -f "$output_iso"
@ -224,7 +112,8 @@ create_iso() {
-outdev "$output_iso" \ -outdev "$output_iso" \
-compliance no_emul_toc \ -compliance no_emul_toc \
-volid TUXFLOTTE \ -volid TUXFLOTTE \
"${grub_map_args[@]}" \ -map "$REPO_DIR/grub/EFI-BOOT-grub.cfg" /EFI/BOOT/grub.cfg \
-map "$REPO_DIR/grub/boot-grub2-grub.cfg" /boot/grub2/grub.cfg \
-map "$WORK_DIR/updates" /updates \ -map "$WORK_DIR/updates" /updates \
-chown_r 0 /updates -- \ -chown_r 0 /updates -- \
-chgrp_r 0 /updates -- \ -chgrp_r 0 /updates -- \
@ -240,9 +129,7 @@ main() {
extract_iso extract_iso
patch_grub patch_grub
verify_workdir verify_workdir
bake_test_preseed
prepare_updates prepare_updates
patch_initrd
create_iso create_iso
echo echo

View File

@ -1,42 +0,0 @@
#!/usr/bin/env bash
# Wird von build.sh und build_customer_iso.sh eingebunden (source), nicht
# eigenstaendig ausgefuehrt.
# Haengt initrd-hooks/casper-bottom/* als zusaetzliche cpio-Schicht an ein
# bestehendes Casper-Initrd an - derselbe Mechanismus, den
# backend_launch() (backends/mint/backend.sh) bereits fuer das
# Kexec-Preseed nutzt und der per QEMU verifiziert ist: der Kernel haengt
# spaeter aneinandergereihte cpio-Archive additiv an ein bestehendes
# initramfs an. Noetig, weil Casper keinen automatischen /updates-Merge
# kennt (real per QEMU-Boot widerlegt) - der Hook selbst kopiert
# /cdrom/updates beim Boot auf das Root-Dateisystem, siehe
# initrd-hooks/casper-bottom/21tuxflotte_updates.
inject_casper_bottom_hooks() {
local repo_dir="$1"
local src_initrd="$2"
local dest_initrd="$3"
local hook_dir="${repo_dir}/initrd-hooks"
local layer_dir
layer_dir="$(mktemp -d)"
# Ausserhalb von layer_dir, damit die Archiv-Datei sich beim Bauen nicht
# selbst als leeren Eintrag einfaengt (find laeuft im selben Moment, in
# dem die Shell-Redirection sie bereits anlegt).
local cpio_out
cpio_out="$(mktemp)"
cp -a "${hook_dir}/casper-bottom" "${layer_dir}/scripts-casper-bottom-tmp"
mkdir -p "${layer_dir}/scripts"
mv "${layer_dir}/scripts-casper-bottom-tmp" "${layer_dir}/scripts/casper-bottom"
chmod 0755 "${layer_dir}"/scripts/casper-bottom/*
(cd "${layer_dir}" && find . -mindepth 1 | cpio -o -H newc 2>/dev/null | gzip) \
>"${cpio_out}" ||
{ echo "Error: Casper-Bottom-Hook-Schicht konnte nicht gebaut werden." >&2; rm -rf "${layer_dir}"; rm -f "${cpio_out}"; return 1; }
cat "${src_initrd}" "${cpio_out}" >"${dest_initrd}" ||
{ echo "Error: Initrd konnte nicht mit Hook-Schicht zusammengesetzt werden." >&2; rm -rf "${layer_dir}"; rm -f "${cpio_out}"; return 1; }
rm -rf "${layer_dir}"
rm -f "${cpio_out}"
}