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:
1 parent
030401f800
commit
38ca3ab5ab
14 files changed
+293
-40
No files matched your search
@@ -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
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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; }
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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 }'
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+125
@@ -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 "$@"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Executable
+52
@@ -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)"
|
||||
Reference in new issue
Block a user