Medium-Endstand: Publish-Schritt mit Versionskennung, Vorabpruefung, USB-Ausschluss bei der Zielplatte, Offline-Medium, Neuinstallation ohne Rueckfrage, toram-Reserve, Platzpruefung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-03 12:17:31 +02:00
1 parent 030401f800
commit 38ca3ab5ab
14 files changed
+293 -40

No files matched your search

+3
View File
@@ -33,3 +33,6 @@ __pycache__/
# Kopie von syslinux_common/splash.svg, von build_boot_medium.sh erzeugt
boot-medium/config/bootloaders/grub-pc/splash.svg
# Versionskennung, von build_boot_medium.sh pro Build erzeugt
boot-medium/config/includes.chroot/opt/tuxflotte/BOOT_MEDIUM_VERSION
+2 -10
View File
@@ -78,16 +78,8 @@ backend_fatal() {
# siehe dort fuer die ausfuehrliche Begruendung (Boot-Medium-Ausschluss,
# Mehrplatten-Heuristik nach Groessen-Rang statt lsblk-Reihenfolge).
_debian_image_detect_target_disk() {
local rank="${1:-1}"
local boot_medium_disk
boot_medium_disk="$(tuxflotte_boot_medium_disk || true)"
lsblk --nodeps --noheadings --bytes --output NAME,TYPE,SIZE --paths |
awk -v exclude="${boot_medium_disk}" '
$2 == "disk" && $3 > 0 && $1 !~ /(nbd|zram|loop)[0-9]*$/ && $1 != exclude { print $3, $1 }
' |
sort -k1,1nr -k2,2 |
awk -v rank="${rank}" 'NR == rank { print $2 }'
# Gemeinsame Auswahl inkl. USB-Ausschluss, siehe lib/boot_medium.sh.
tuxflotte_detect_target_disk "${1:-1}"
}
backend_init() {
+2 -10
View File
@@ -81,16 +81,8 @@ backend_fatal() {
# bleibt. Kommt aus installation_directives.partitioning.target_disk_rank
# der Bereitstellungsvorlage (Default 1 = "groesste Platte").
_mint_image_detect_target_disk() {
local rank="${1:-1}"
local boot_medium_disk
boot_medium_disk="$(tuxflotte_boot_medium_disk || true)"
lsblk --nodeps --noheadings --bytes --output NAME,TYPE,SIZE --paths |
awk -v exclude="${boot_medium_disk}" '
$2 == "disk" && $3 > 0 && $1 !~ /(nbd|zram|loop)[0-9]*$/ && $1 != exclude { print $3, $1 }
' |
sort -k1,1nr -k2,2 |
awk -v rank="${rank}" 'NR == rank { print $2 }'
# Gemeinsame Auswahl inkl. USB-Ausschluss, siehe lib/boot_medium.sh.
tuxflotte_detect_target_disk "${1:-1}"
}
backend_init() {
+2 -10
View File
@@ -84,16 +84,8 @@ backend_fatal() {
# siehe dort fuer die ausfuehrliche Begruendung (Boot-Medium-Ausschluss,
# Mehrplatten-Heuristik nach Groessen-Rang statt lsblk-Reihenfolge).
_ubuntu_image_detect_target_disk() {
local rank="${1:-1}"
local boot_medium_disk
boot_medium_disk="$(tuxflotte_boot_medium_disk || true)"
lsblk --nodeps --noheadings --bytes --output NAME,TYPE,SIZE --paths |
awk -v exclude="${boot_medium_disk}" '
$2 == "disk" && $3 > 0 && $1 !~ /(nbd|zram|loop)[0-9]*$/ && $1 != exclude { print $3, $1 }
' |
sort -k1,1nr -k2,2 |
awk -v rank="${rank}" 'NR == rank { print $2 }'
# Gemeinsame Auswahl inkl. USB-Ausschluss, siehe lib/boot_medium.sh.
tuxflotte_detect_target_disk "${1:-1}"
}
backend_init() {
@@ -17,7 +17,17 @@ sed -i \
-e 's|rsync -a --progress |rsync -a --info=progress2 --no-inc-recursive |g' \
-e 's|echo " \* Copying whole medium to RAM"|echo "Tuxflotte wird in den Arbeitsspeicher geladen ..."|' \
-e 's|echo " \* Copying $MODULETORAMFILE to RAM"|echo "Tuxflotte wird in den Arbeitsspeicher geladen ..."|' \
-e 's|if \[ "${freespace}" -lt "${size}" \]|if [ "${freespace}" -lt "$(( size + 1572864 ))" ]|' \
"${TORAM_SCRIPT}"
# Reserve (Offline-Medium, Roadmap V5): live-boot kopiert nur, wenn das
# Medium in den RAM passt - ohne jede Reserve. Mit eingebettetem Golden Image
# (3-4,5 GB) bliebe auf kleinen Geraeten sonst kaum RAM fuer die Installation
# selbst. Mit 1,5 GB Reserve laeuft ein solches Geraet stattdessen vom Stick
# (live-boot faellt bei zu wenig Platz von selbst darauf zurueck; der fruehe
# Abzieh-Hinweis in boot-autostart.sh erscheint dann korrekt nicht).
grep -q -- 'size + 1572864' "${TORAM_SCRIPT}" ||
{ echo "tuxflotte: toram-Reserve-Patch hat nicht gegriffen (live-boot geaendert?)." >&2; exit 1; }
grep -q -- '--info=progress2' "${TORAM_SCRIPT}" ||
{ echo "tuxflotte: toram-Patch hat nicht gegriffen (live-boot geaendert?)." >&2; exit 1; }
+26 -2
View File
@@ -33,7 +33,9 @@ set -Eeuo pipefail
# Cinnamon stemmen soll, unproblematisch.
usage() {
echo "Usage: $0 [--dev] [--local-test-anode-url=http://HOST:PORT] [--embed-golden-image=PFAD] [--serial-installer-console]" >&2
echo "Usage: $0 [--dev] [--local-test-anode-url=http://HOST:PORT] [--embed-golden-image=PFAD] [--serial-installer-console] [--publish]" >&2
echo " --publish NUR Produktiv-Build: fertiges Medium per publish_boot_medium.sh als" >&2
echo " Basis aller Kunden-ISOs uebernehmen (siehe dort)" >&2
echo " --dev SSH-Debug-Zugang aktivieren (tuxflotte/test123)" >&2
echo " --local-test-anode-url=... NUR fuer LAN-lokale E2E-Tests: laesst das Boot-Medium" >&2
echo " anode per Klartext-HTTP direkt im LAN ansprechen statt" >&2
@@ -62,6 +64,7 @@ DEV_BUILD=false
LOCAL_TEST_ANODE_URL=""
EMBED_GOLDEN_IMAGE=""
SERIAL_INSTALLER_CONSOLE=false
PUBLISH=false
for arg in "$@"; do
case "$arg" in
--dev) DEV_BUILD=true ;;
@@ -78,6 +81,7 @@ for arg in "$@"; do
{ echo "Error: --embed-golden-image: Datei nicht lesbar: ${EMBED_GOLDEN_IMAGE}" >&2; exit 1; }
;;
--serial-installer-console) SERIAL_INSTALLER_CONSOLE=true ;;
--publish) PUBLISH=true ;;
-h|--help) usage; exit 0 ;;
*) echo "Error: Unbekannter Parameter: $arg" >&2; usage; exit 1 ;;
esac
@@ -129,6 +133,7 @@ cd "${BOOT_MEDIUM_DIR}"
echo "==> Synchronisiere scripts/backends ins Boot-Medium (verhindert Sync-Drift)"
rsync -a --delete \
--exclude='build_boot_medium.sh' \
--exclude='publish_boot_medium.sh' \
--exclude='build_customer_iso.sh' \
--exclude='build_golden_image.sh' \
--exclude='build.sh' \
@@ -218,6 +223,21 @@ if [[ "${SERIAL_INSTALLER_CONSOLE}" == true || -n "${LOCAL_TEST_ANODE_URL}" ]];
SERIAL_BOOTAPPEND=" console=ttyS0,115200n8 console=tty0"
fi
# Versionskennung im Medium (Kunden-ISO-Test 02.10.2026, B2): das
# veroeffentlichte Boot-Medium war wochenlang veraltet, ohne dass es
# irgendwo sichtbar war. Installer meldet sie bei installation_started,
# publish_boot_medium.sh zeigt sie an.
VERSION_FILE="${BOOT_MEDIUM_DIR}/config/includes.chroot/opt/tuxflotte/BOOT_MEDIUM_VERSION"
BUILD_KIND="produktiv"
[[ "${DEV_BUILD}" == true ]] && BUILD_KIND="dev"
[[ -n "${LOCAL_TEST_ANODE_URL}" || -n "${EMBED_GOLDEN_IMAGE}" ]] && BUILD_KIND="lokal-test"
printf '%s %s %s\n' "$(git -C "${REPO_DIR}" rev-parse --short HEAD)" "$(date '+%Y-%m-%dT%H:%M%z')" "${BUILD_KIND}" > "${VERSION_FILE}"
if [[ "${PUBLISH}" == true && "${BUILD_KIND}" != "produktiv" ]]; then
echo "Error: --publish nur fuer Produktiv-Builds (ohne --dev/--local-test-anode-url/--embed-golden-image)." >&2
exit 1
fi
echo "==> lb clean"
lb clean
@@ -238,4 +258,8 @@ if [[ "${OUTPUT_ISO_NAME}" != "live-image-amd64.hybrid.iso" ]]; then
mv -- "live-image-amd64.hybrid.iso" "${OUTPUT_ISO_NAME}"
fi
echo "==> Boot-Medium gebaut: ${BOOT_MEDIUM_DIR}/${OUTPUT_ISO_NAME}"
echo "==> Boot-Medium gebaut: ${BOOT_MEDIUM_DIR}/${OUTPUT_ISO_NAME} ($(cat "${VERSION_FILE}"))"
if [[ "${PUBLISH}" == true ]]; then
bash "${REPO_DIR}/scripts/publish_boot_medium.sh" "${BOOT_MEDIUM_DIR}/${OUTPUT_ISO_NAME}"
fi
+39 -1
View File
@@ -33,7 +33,9 @@ set -euo pipefail
usage() {
echo "Usage: $0 <source.iso> <output.iso> <activation_code> [wifi_ssid] [wifi_psk|-] [volid]" >&2
echo " wifi_psk \"-\": Geheimnisse zeilenweise als KEY=VALUE von stdin lesen" >&2
echo " (WIFI_PSK, ROOT_PASSWORD_HASH, VORLAGE_LABEL) statt als Argument." >&2
echo " (WIFI_PSK, ROOT_PASSWORD_HASH, VORLAGE_LABEL, GOLDEN_IMAGE_FILE) statt als Argument." >&2
echo " GOLDEN_IMAGE_FILE: Offline-Medium - Golden-Image-Archiv wird auf die ISO gelegt" >&2
echo " (/opt/tuxflotte/golden-images/), die Installation laedt dann nichts herunter." >&2
}
SOURCE_ISO="${1:-}"
@@ -43,6 +45,7 @@ WIFI_SSID="${4:-}"
WIFI_PSK="${5:-}"
ROOT_PASSWORD_HASH=""
VORLAGE_LABEL=""
GOLDEN_IMAGE_FILE=""
# Geheimnisse per stdin (Kunden-ISO-Test 02.10.2026, Befund B6): als
# Kommandozeilenargument landete das WLAN-PSK ueber den sudo-Aufruf von
# provisioning-server im Klartext im sudo-Log/Journal (und war in der
@@ -56,6 +59,7 @@ if [[ "${WIFI_PSK}" == "-" ]]; then
WIFI_PSK) WIFI_PSK="${secret_value}" ;;
ROOT_PASSWORD_HASH) ROOT_PASSWORD_HASH="${secret_value}" ;;
VORLAGE_LABEL) VORLAGE_LABEL="${secret_value}" ;;
GOLDEN_IMAGE_FILE) GOLDEN_IMAGE_FILE="${secret_value}" ;;
"") ;;
*) echo "Error: Unbekannter Schluessel auf stdin: ${secret_key}" >&2; exit 1 ;;
esac
@@ -103,6 +107,26 @@ done
# Ohne dieses Verzeichnis wie bisher /var/tmp.
BUILD_TMP_BASE="/var/tmp"
[[ -d /srv/tuxflotte/build-tmp ]] && BUILD_TMP_BASE="/srv/tuxflotte/build-tmp"
# Platzpruefung vor dem Bau (B5): entpacktes + neu gepacktes Squashfs im
# Arbeitsverzeichnis (~3x Quell-ISO), dazu die fertige ISO (Quell-ISO +
# ggf. Golden Image) im Ausgabeverzeichnis - jeweils mit 1 GiB Reserve.
need_kib() { du -k --apparent-size "$1" | cut -f1; }
free_kib() { df -Pk "$1" | awk 'NR == 2 { print $4 }'; }
source_kib="$(need_kib "${SOURCE_ISO}")"
golden_kib=0
[[ -n "${GOLDEN_IMAGE_FILE}" && -r "${GOLDEN_IMAGE_FILE}" ]] && golden_kib="$(need_kib "${GOLDEN_IMAGE_FILE}")"
work_need=$(( source_kib * 3 + 1048576 ))
out_need=$(( source_kib + golden_kib + 1048576 ))
if [[ "$(stat -c %d "${BUILD_TMP_BASE}")" == "$(stat -c %d "$(dirname "${OUTPUT_ISO}")")" ]]; then
work_need=$(( work_need + out_need ))
out_need=0
fi
if (( $(free_kib "${BUILD_TMP_BASE}") < work_need )) || (( $(free_kib "$(dirname "${OUTPUT_ISO}")") < out_need )); then
echo "Error: Nicht genug freier Speicherplatz fuer den ISO-Bau (benoetigt ca. $(( (work_need + out_need) / 1048576 )) GiB)." >&2
exit 1
fi
WORK_DIR="$(mktemp -d --tmpdir="${BUILD_TMP_BASE}")"
cleanup() {
@@ -196,6 +220,19 @@ if [[ -n "${VORLAGE_LABEL}" ]]; then
fi
fi
# Offline-Medium (Roadmap V5): das Golden-Image-Archiv liegt auf der ISO an
# derselben Stelle wie bei Lokal-Test-Builds (--embed-golden-image), der
# Installer bevorzugt es dort automatisch (image_deploy_find_embedded_image).
# Passt das ganze Medium samt Reserve in den RAM, laedt toram es mit; sonst
# laeuft das Live-System vom Stick, der dann bis zum Ende stecken bleibt.
declare -a GOLDEN_IMAGE_MAP=()
if [[ -n "${GOLDEN_IMAGE_FILE}" ]]; then
[[ -r "${GOLDEN_IMAGE_FILE}" && "${GOLDEN_IMAGE_FILE}" == *.tar.zst ]] ||
{ echo "Error: Golden Image fuer Offline-Medium nicht lesbar: ${GOLDEN_IMAGE_FILE}" >&2; exit 1; }
echo "Offline-Medium: lege $(basename "${GOLDEN_IMAGE_FILE}") auf die ISO..."
GOLDEN_IMAGE_MAP=(-map "${GOLDEN_IMAGE_FILE}" "/opt/tuxflotte/golden-images/$(basename "${GOLDEN_IMAGE_FILE}")")
fi
echo "Erzeuge Kunden-ISO..."
rm -f "${OUTPUT_ISO}"
@@ -205,6 +242,7 @@ xorriso \
-volid "${VOLID}" \
-update "${WORK_DIR}/new.squashfs" /live/filesystem.squashfs \
"${MENU_UPDATES[@]}" \
"${GOLDEN_IMAGE_MAP[@]}" \
-boot_image any replay
# Laeuft (ueber run_iso_build()s scoped sudo) als root, die Ausgabedatei
+5 -2
View File
@@ -54,6 +54,7 @@ case "${TUXFLOTTE_PROVISIONING_CONTINUE:-}" in
;;
esac
run_module "$SCRIPT_DIR/modules/20_profile_selection.sh" "always"
run_module "$SCRIPT_DIR/modules/22_preflight_checks.sh" "always"
run_module "$SCRIPT_DIR/modules/25_installation_confirm.sh" "always"
INSTALLATION_STATE_FILE="/run/tuxflotte/installation/state.env"
@@ -67,7 +68,10 @@ source "$INSTALLATION_STATE_FILE"
case "${TUXFLOTTE_INSTALLATION_CONFIRMED:-}" in
true)
log_info "Commit Point bestätigt. Installationsphase wird fortgesetzt."
tuxflotte_report_installation_event "installation_started"
# Boot-Medium-Version mitmelden (B2): im Portal sichtbar, mit welchem
# Medium-Stand ein Geraet installiert wurde.
tuxflotte_report_installation_event "installation_started" \
"Boot-Medium $(cat /opt/tuxflotte/BOOT_MEDIUM_VERSION 2>/dev/null || echo unbekannt)"
;;
false)
log_info "Provisionierung wurde durch den Benutzer kontrolliert beendet."
@@ -81,7 +85,6 @@ esac
run_module "$SCRIPT_DIR/modules/30_runtime_blueprint.sh" "always"
run_module "$SCRIPT_DIR/modules/40_backend.sh" "always"
run_module "$SCRIPT_DIR/modules/20_storage.sh" "dry-run-safe"
run_module "$SCRIPT_DIR/modules/99_finish.sh" "always"
log_success "Tuxflotte Installer abgeschlossen"
+22
View File
@@ -33,3 +33,25 @@ tuxflotte_boot_medium_disk() {
printf '/dev/%s\n' "${parent}"
}
# Zieldatentraeger nach Rang (1 = groesste Platte), gemeinsam fuer alle
# Image-Backends und die Vorabpruefung (22_preflight_checks.sh).
#
# Kunden-ISO-Test 03.10.2026: seit "toram" ist /run/live/medium die
# RAM-Kopie (tmpfs), tuxflotte_boot_medium_disk() findet den Boot-Stick also
# nicht mehr - er blieb Kandidat und waere bei einem grossen Stick/kleiner
# SSD als Ziel gewaehlt worden. Deshalb zusaetzlich alle USB-Datentraeger
# ausschliessen (Installation auf eine USB-Platte ist kein unterstuetzter
# Fall).
tuxflotte_detect_target_disk() {
local rank="${1:-1}"
local boot_medium_disk
boot_medium_disk="$(tuxflotte_boot_medium_disk || true)"
lsblk --nodeps --noheadings --bytes --output NAME,TYPE,SIZE,TRAN --paths |
awk -v exclude="${boot_medium_disk}" '
$2 == "disk" && $3 > 0 && $1 !~ /(nbd|zram|loop)[0-9]*$/ && $1 != exclude && $4 != "usb" { print $3, $1 }
' |
sort -k1,1nr -k2,2 |
awk -v rank="${rank}" 'NR == rank { print $2 }'
}
+3 -3
View File
@@ -55,14 +55,14 @@ declare -A TUXFLOTTE_STEP_LABELS=(
[25_installation_confirm.sh]="Installationsbestätigung"
[30_runtime_blueprint.sh]="Installationsplan wird aufgelöst"
[40_backend.sh]="Golden Image wird geladen und installiert"
[20_storage.sh]="Speicher-Zusatzprüfung"
[22_preflight_checks.sh]="Gerät wird geprüft (Festplatte, Speicher, Stromversorgung)"
[99_finish.sh]="Abschluss"
)
readonly -a TUXFLOTTE_STEP_ORDER=(
00_preflight.sh 05_network.sh 10_hardware.sh 12_enrollment_auth.sh
15_server_handshake.sh 17_device_status.sh 20_profile_selection.sh
25_installation_confirm.sh 30_runtime_blueprint.sh 40_backend.sh
20_storage.sh 99_finish.sh
22_preflight_checks.sh 25_installation_confirm.sh 30_runtime_blueprint.sh
40_backend.sh 99_finish.sh
)
TUXFLOTTE_STEP_COUNT=0
+1 -1
View File
@@ -47,7 +47,7 @@ _tuxflotte_module_is_backend() {
_tuxflotte_device_previously_installed() {
local response_file="/run/tuxflotte/server/response.json"
[[ -r "${response_file}" ]] || return 1
jq --exit-status '.device.previously_installed == true' "${response_file}" >/dev/null 2>&1
jq --exit-status '.device.previously_installed == true and (.device.reinstall_without_confirmation != true)' "${response_file}" >/dev/null 2>&1
}
# Kompakte Modul-Ausfuehrung fuer den unbeaufsichtigten Auto-Modus (siehe
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
# Vorabpruefung (Kunden-ISO-Test 03.10.2026): alles, was die Installation
# vorhersehbar scheitern liesse, wird VOR der Installationsbestaetigung und
# vor jeder Aenderung an der Platte geprueft - bei einem Problem bricht die
# Installation mit einem verstaendlichen Hinweis ab, statt mittendrin mit
# einer halb beschriebenen Platte zu scheitern.
set -Eeuo pipefail
SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly SCRIPT_NAME
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly TEMPLATE_FILE="${TUXFLOTTE_TEMPLATE_FILE:-/run/tuxflotte/assignment/template.json}"
# Mindestgroessen in GiB. Root muss das entpackte Golden Image plus die
# per Ansible nachinstallierten Programme aufnehmen (Ubuntu-Image + Apps
# liegt bei ~15 GiB), mit Reserve fuer Updates.
readonly MIN_ROOT_GIB=25
readonly MIN_EXTRA_PARTITION_GIB=2
readonly MIN_RAM_MIB=2048
readonly MIN_BATTERY_PERCENT=30
# shellcheck source=../lib/boot_medium.sh
source "${SCRIPT_DIR}/../lib/boot_medium.sh"
problems=()
# Nur ins Logfile: bei einem Abbruch soll am Bildschirm ausschliesslich die
# verstaendliche Problemliste stehen, keine Skriptnamen.
log() {
printf '[%s] %s\n' "${SCRIPT_NAME}" "$*" >>"${TUXFLOTTE_LOG_FILE:-/dev/null}" 2>/dev/null || true
}
gib() {
awk -v b="$1" 'BEGIN { printf "%.0f", b / 1024 / 1024 / 1024 }'
}
check_disk() {
local rank disk size_bytes size_gib scheme extra_percent root_gib
rank="$(jq -r '.template.partitioning.target_disk_rank // 1' "${TEMPLATE_FILE}" 2>/dev/null || echo 1)"
disk="$(tuxflotte_detect_target_disk "${rank}")"
if [[ -z "${disk}" ]]; then
problems+=("Es wurde keine interne Festplatte gefunden (USB-Datenträger werden nicht als Ziel verwendet).")
return
fi
size_bytes="$(lsblk --nodeps --noheadings --bytes --output SIZE "${disk}" | tr -d ' ')"
size_gib="$(gib "${size_bytes}")"
scheme="$(jq -r '.template.partitioning.scheme // "single"' "${TEMPLATE_FILE}" 2>/dev/null || echo single)"
extra_percent=0
if [[ "${scheme}" == "custom" ]]; then
extra_percent="$(jq '[.template.partitioning.extra_partitions[]?.percent] | add // 0' "${TEMPLATE_FILE}")"
while IFS=$'\t' read -r mountpoint percent; do
[[ -n "${mountpoint}" ]] || continue
local part_gib=$(( size_gib * percent / 100 ))
if (( part_gib < MIN_EXTRA_PARTITION_GIB )); then
problems+=("Die Partition ${mountpoint} (${percent} % der Festplatte) wäre nur ${part_gib} GB groß - mindestens ${MIN_EXTRA_PARTITION_GIB} GB nötig.")
fi
done < <(jq -r '.template.partitioning.extra_partitions[]? | [.mountpoint, (.percent|tostring)] | @tsv' "${TEMPLATE_FILE}")
fi
root_gib=$(( size_gib * (100 - extra_percent) / 100 ))
log "Zielplatte ${disk}: ${size_gib} GB, davon ca. ${root_gib} GB für das System."
if (( root_gib < MIN_ROOT_GIB )); then
problems+=("Die Festplatte ${disk} ist zu klein: für das System blieben ${root_gib} GB, mindestens ${MIN_ROOT_GIB} GB sind nötig.")
fi
}
check_ram() {
local ram_mib
ram_mib="$(awk '/^MemTotal:/ { printf "%d", $2 / 1024 }' /proc/meminfo)"
log "Arbeitsspeicher: ${ram_mib} MB."
if (( ram_mib < MIN_RAM_MIB )); then
problems+=("Das Gerät hat nur ${ram_mib} MB Arbeitsspeicher - mindestens ${MIN_RAM_MIB} MB sind nötig.")
fi
}
# Installation + erster Agent-Lauf dauern ~10 Minuten - ein Notebook, das
# mittendrin ausgeht, hinterlaesst eine halb beschriebene Platte.
check_power() {
local ac_online=0 bat capacity status lowest=""
for supply in /sys/class/power_supply/*; do
[[ -r "${supply}/type" ]] || continue
case "$(cat "${supply}/type")" in
Mains|USB) [[ "$(cat "${supply}/online" 2>/dev/null || echo 0)" == "1" ]] && ac_online=1 ;;
esac
done
(( ac_online == 1 )) && return 0
for bat in /sys/class/power_supply/BAT*; do
[[ -r "${bat}/capacity" ]] || continue
capacity="$(cat "${bat}/capacity")"
status="$(cat "${bat}/status" 2>/dev/null || echo unknown)"
log "Akku ${bat##*/}: ${capacity} % (${status})."
[[ -z "${lowest}" || "${capacity}" -lt "${lowest}" ]] && lowest="${capacity}"
done
if [[ -n "${lowest}" ]] && (( lowest < MIN_BATTERY_PERCENT )); then
problems+=("Der Akku ist nur zu ${lowest} % geladen und kein Netzteil angeschlossen - bitte Netzteil anschließen.")
fi
}
main() {
[[ -r "${TEMPLATE_FILE}" ]] || { log "FEHLER: ${TEMPLATE_FILE} fehlt."; exit 1; }
check_disk
check_ram
check_power
if (( ${#problems[@]} > 0 )); then
{
printf '\n'
printf ' Die Installation wurde NICHT gestartet - an diesem Gerät wurde nichts verändert.\n\n'
for problem in "${problems[@]}"; do
printf ' - %s\n' "${problem}"
done
printf '\n Nach dem Beheben das Gerät mit dem Installationsmedium neu starten.\n\n'
} >&2
exit 1
fi
log "Alle Vorabprüfungen bestanden."
}
main "$@"
+1 -1
View File
@@ -68,7 +68,7 @@ validate_template() {
device_previously_installed() {
[[ -r "${SERVER_RESPONSE_FILE}" ]] || return 1
jq --exit-status '.device.previously_installed == true' \
jq --exit-status '.device.previously_installed == true and (.device.reinstall_without_confirmation != true)' \
"${SERVER_RESPONSE_FILE}" >/dev/null 2>&1
}
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Uebernimmt ein fertig gebautes Produktiv-Boot-Medium als Basis aller
# Kunden-ISOs (Kunden-ISO-Test 02.10.2026, B2: bisher ein manueller
# install-Befehl, der nach den Fixes vom 19.-21.09. schlicht vergessen wurde -
# Kunden bekamen wochenlang ein veraltetes Medium).
#
# Prueft vor dem Austausch, dass es wirklich ein Produktiv-Build ist (kein
# Dev-/Lokal-Test-Anteil, Versionskennung vorhanden), behaelt das bisherige
# Medium als *.vorher und tauscht atomar (mv), damit ein gerade laufender
# Kunden-ISO-Bau nie eine halb kopierte Datei liest.
#
# Usage: publish_boot_medium.sh [quelle.iso] [ziel.iso]
set -Eeuo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SOURCE_ISO="${1:-${REPO_DIR}/boot-medium/live-image-amd64.hybrid.iso}"
TARGET_ISO="${2:-/opt/provisioning-server/data/upstream/tuxflotte-boot-medium.iso}"
OWNER="provisioning-server"
[[ "${EUID}" -eq 0 ]] || { echo "Error: muss als root laufen (Loop-Mount zur Pruefung)." >&2; exit 1; }
[[ -r "${SOURCE_ISO}" ]] || { echo "Error: Quell-ISO nicht lesbar: ${SOURCE_ISO}" >&2; exit 1; }
MOUNT_DIR="$(mktemp -d)"
cleanup() { umount "${MOUNT_DIR}" 2>/dev/null || true; rmdir "${MOUNT_DIR}" 2>/dev/null || true; }
trap cleanup EXIT
mount -o loop,ro "${SOURCE_ISO}" "${MOUNT_DIR}"
SQUASHFS="${MOUNT_DIR}/live/filesystem.squashfs"
[[ -r "${SQUASHFS}" ]] || { echo "Error: kein Live-Squashfs in ${SOURCE_ISO}." >&2; exit 1; }
version="$(unsquashfs -cat "${SQUASHFS}" opt/tuxflotte/BOOT_MEDIUM_VERSION 2>/dev/null || true)"
[[ -n "${version}" ]] || { echo "Error: Medium hat keine Versionskennung (zu alter Build?)." >&2; exit 1; }
[[ "${version}" == *" produktiv" ]] || { echo "Error: kein Produktiv-Build (${version})." >&2; exit 1; }
if unsquashfs -l "${SQUASHFS}" | grep -q -e 'etc/tuxflotte-dev-build$' -e 'local_test_override.conf$'; then
echo "Error: Medium enthaelt Dev-/Lokal-Test-Anteile." >&2
exit 1
fi
if [[ -d "${MOUNT_DIR}/opt/tuxflotte/golden-images" ]]; then
echo "Error: Medium enthaelt ein eingebettetes Golden Image (Lokal-Test-Build)." >&2
exit 1
fi
umount "${MOUNT_DIR}"
install -d -o "${OWNER}" -g "${OWNER}" "$(dirname "${TARGET_ISO}")"
tmp="${TARGET_ISO}.neu"
install -o "${OWNER}" -g "${OWNER}" -m 0644 "${SOURCE_ISO}" "${tmp}"
[[ -e "${TARGET_ISO}" ]] && ln -f "${TARGET_ISO}" "${TARGET_ISO}.vorher"
mv -f "${tmp}" "${TARGET_ISO}"
echo "==> Boot-Medium veroeffentlicht: ${TARGET_ISO}"
echo " Version: ${version}"
echo " SHA256: $(sha256sum "${TARGET_ISO}" | cut -d' ' -f1)"