diff --git a/initrd-hooks/casper-bottom/99casperboot b/initrd-hooks/casper-bottom/99casperboot new file mode 100755 index 0000000..6d5dd9c --- /dev/null +++ b/initrd-hooks/casper-bottom/99casperboot @@ -0,0 +1,38 @@ +#! /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 + +touch /run/.casper-boot diff --git a/live-updates/opt/tuxflotte/kiosk/start-kiosk.sh b/live-updates/opt/tuxflotte/kiosk/start-kiosk.sh index 28a177d..a6e5b1b 100755 --- a/live-updates/opt/tuxflotte/kiosk/start-kiosk.sh +++ b/live-updates/opt/tuxflotte/kiosk/start-kiosk.sh @@ -30,4 +30,13 @@ 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/liveuser/.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/liveuser/.local/share/tuxflotte-kiosk file:///opt/tuxflotte/kiosk/index.html diff --git a/scripts/build.sh b/scripts/build.sh index 6a52756..e9cd683 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,6 +10,9 @@ BUILD_DIR="$BASE_DIR/build" WORK_DIR="$BUILD_DIR/work" 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 @@ -50,7 +53,7 @@ check_dependencies() { done if [[ "$BACKEND" == "mint" ]]; then - for cmd in jq envsubst base64; do + for cmd in jq envsubst base64 cpio; do if ! command -v "$cmd" >/dev/null 2>&1; then echo "Error: missing dependency: $cmd" exit 1 @@ -61,6 +64,9 @@ check_dependencies() { prepare_dirs() { mkdir -p "$BUILD_DIR" "$OUTPUT_DIR" + # xorriso extrahiert Verzeichnisse teils mit den Original-ISO-Rechten + # (z.B. 555, kein Write-Bit) - ohne chmod schlaegt rm -rf darauf fehl. + chmod -R u+w "$WORK_DIR" 2>/dev/null || true rm -rf "$WORK_DIR" mkdir -p "$WORK_DIR" } @@ -68,26 +74,34 @@ prepare_dirs() { extract_iso() { echo "Extracting ISO..." xorriso -indev "$SOURCE_ISO" -osirrox on -extract / "$WORK_DIR" >/dev/null + # xorriso uebernimmt die Original-ISO-Rechte (haeufig 555 auf + # Verzeichnissen, 444 auf Dateien) - ohne Write-Bit koennen weder + # spaetere Schritte (patch_grub) noch spaetere Aufraeumlaeufe + # (prepare_dirs) Dateien darin ersetzen/entfernen. + chmod -R u+w "$WORK_DIR" } patch_grub() { echo "Installing Tuxflotte GRUB configuration..." + # -f: xorriso extrahiert ISO-Dateien mit deren Original-Rechten, die auf + # Live-Medien haeufig read-only (444) sind - ohne -f schlaegt cp beim + # Ueberschreiben mit "Keine Berechtigung" fehl. if [[ "$BACKEND" == "fedora" ]]; then - cp "$REPO_DIR/grub/EFI-BOOT-grub.cfg" \ + cp -f "$REPO_DIR/grub/EFI-BOOT-grub.cfg" \ "$WORK_DIR/EFI/BOOT/grub.cfg" - cp "$REPO_DIR/grub/boot-grub2-grub.cfg" \ + cp -f "$REPO_DIR/grub/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 "$REPO_DIR/grub/mint-boot-grub.cfg" \ + cp -f "$REPO_DIR/grub/mint-boot-grub.cfg" \ "$WORK_DIR/boot/grub/grub.cfg" - cp "$REPO_DIR/grub/mint-isolinux-live.cfg" \ + cp -f "$REPO_DIR/grub/mint-isolinux-live.cfg" \ "$WORK_DIR/isolinux/live.cfg" fi } @@ -171,6 +185,16 @@ prepare_updates() { 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() { echo "Creating Tuxflotte ISO..." @@ -189,6 +213,7 @@ create_iso() { -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 @@ -217,6 +242,7 @@ main() { verify_workdir bake_test_preseed prepare_updates + patch_initrd create_iso echo diff --git a/scripts/build_customer_iso.sh b/scripts/build_customer_iso.sh new file mode 100755 index 0000000..1b352d4 --- /dev/null +++ b/scripts/build_customer_iso.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Baut eine personalisierte Kunden-ISO fuer den Self-Service-Flow (Phase 4). +# Anders als build.sh (das fuer Testzwecke einen statischen Preseed mit +# Fake-Geraete-ID einbrennt, siehe bake_test_preseed()) wird hier nur +# config/installer.conf mit den echten, organisationsspezifischen Werten +# befuellt - Hostname/Geraete-ID/Partitionierung werden weiterhin erst live +# auf dem Zielgeraet erzeugt (siehe backend_generate_config() in +# backends/mint/backend.sh). Hier geht es nur um WLAN-Zugangsdaten + +# Enrollment-Session-Code als Boot-Konfiguration, die start-kiosk.sh dazu +# bringt, den Installer beim Hochfahren automatisch zu starten statt der +# interaktiven Kiosk-Startseite. + +usage() { + echo "Usage: $0 [wifi_ssid] [wifi_psk]" >&2 +} + +SOURCE_ISO="${1:-}" +OUTPUT_ISO="${2:-}" +ACTIVATION_CODE="${3:-}" +WIFI_SSID="${4:-}" +WIFI_PSK="${5:-}" + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WORK_DIR="$(mktemp -d)" + +# shellcheck source=lib/initrd.sh +source "$REPO_DIR/scripts/lib/initrd.sh" + +cleanup() { + # xorriso extrahiert Verzeichnisse teils mit den Original-ISO-Rechten + # (z.B. 555, kein Write-Bit) - ohne chmod schlaegt rm -rf darauf fehl. + chmod -R u+w "${WORK_DIR}" 2>/dev/null || true + rm -rf "${WORK_DIR}" +} +trap cleanup EXIT + +if [[ -z "${SOURCE_ISO}" || -z "${OUTPUT_ISO}" || -z "${ACTIVATION_CODE}" ]]; then + usage + exit 1 +fi + +[[ -f "${SOURCE_ISO}" ]] || + { echo "Error: Quell-ISO nicht gefunden: ${SOURCE_ISO}" >&2; exit 1; } + +for cmd in xorriso cpio; do + command -v "${cmd}" >/dev/null 2>&1 || + { echo "Error: fehlendes Werkzeug: ${cmd}" >&2; exit 1; } +done + +echo "Extrahiere Quell-ISO..." +xorriso -indev "${SOURCE_ISO}" -osirrox on -extract / "${WORK_DIR}" >/dev/null +# xorriso uebernimmt die Original-ISO-Rechte (haeufig 555 auf +# Verzeichnissen, 444 auf Dateien) - ohne Write-Bit koennen weder +# spaetere Schritte noch der Cleanup-Trap Dateien darin ersetzen/entfernen. +chmod -R u+w "${WORK_DIR}" + +echo "Installiere Tuxflotte-GRUB-Konfiguration..." +# -f: xorriso extrahiert ISO-Dateien mit deren Original-Rechten, die auf +# Live-Medien haeufig read-only (444) sind - ohne -f schlaegt cp beim +# Ueberschreiben mit "Keine Berechtigung" fehl. +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" + +grep -q "Tuxflotte" "${WORK_DIR}/boot/grub/grub.cfg" +grep -q "Tuxflotte" "${WORK_DIR}/isolinux/live.cfg" + +echo "Baue personalisierte Boot-Konfiguration..." +updates_dir="${WORK_DIR}/updates" +rm -rf "${updates_dir}" +mkdir -p "${updates_dir}" +cp -a "${REPO_DIR}/live-updates/." "${updates_dir}/" +cp -a "${REPO_DIR}/scripts" "${updates_dir}/opt/tuxflotte/" +cp -a "${REPO_DIR}/backends" "${updates_dir}/opt/tuxflotte/" +mkdir -p "${updates_dir}/opt/tuxflotte/config" + +{ + echo "export TUXFLOTTE_AUTO_MODE=true" + printf 'export TUXFLOTTE_ACTIVATION_CODE=%q\n' "${ACTIVATION_CODE}" + [[ -n "${WIFI_SSID}" ]] && printf 'export TUXFLOTTE_WIFI_SSID=%q\n' "${WIFI_SSID}" + [[ -n "${WIFI_PSK}" ]] && printf 'export TUXFLOTTE_WIFI_PSK=%q\n' "${WIFI_PSK}" +} >"${updates_dir}/opt/tuxflotte/config/installer.conf" + +chmod 0440 "${updates_dir}/etc/sudoers.d/90-tuxflotte" + +echo "Injiziere casper-bottom Updates-Hook in die Initrd..." +custom_initrd="${WORK_DIR}/casper/initrd-tuxflotte.lz" +inject_casper_bottom_hooks "${REPO_DIR}" \ + "${WORK_DIR}/casper/initrd.lz" \ + "${custom_initrd}" + +echo "Erzeuge Kunden-ISO..." +rm -f "${OUTPUT_ISO}" + +xorriso \ + -indev "${SOURCE_ISO}" \ + -outdev "${OUTPUT_ISO}" \ + -compliance no_emul_toc \ + -volid TUXFLOTTE \ + -map "${REPO_DIR}/grub/mint-boot-grub.cfg" /boot/grub/grub.cfg \ + -map "${REPO_DIR}/grub/mint-isolinux-live.cfg" /isolinux/live.cfg \ + -map "${custom_initrd}" /casper/initrd.lz \ + -map "${updates_dir}" /updates \ + -chown_r 0 /updates -- \ + -chgrp_r 0 /updates -- \ + -boot_image any replay + +echo "Kunden-ISO erstellt: ${OUTPUT_ISO}" diff --git a/scripts/lib/initrd.sh b/scripts/lib/initrd.sh new file mode 100644 index 0000000..384c150 --- /dev/null +++ b/scripts/lib/initrd.sh @@ -0,0 +1,42 @@ +#!/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}" +}