Neue scripts/lib/reporting.sh (tuxflotte_report_installation_event())
meldet best-effort an POST /api/v1/devices/{device_id}/installation-events
(device_fingerprint-authentifiziert, siehe provisioning-server-Commit
51fbf0f) - ein Fehlschlag beim Melden selbst darf die eigentliche
Installation nie aufhalten.
Vier Meldepunkte:
- "activation_confirmed" direkt nach erfolgreichem Provisioning-Handshake
(15_server_handshake.sh)
- "installation_started" direkt nach bestaetigtem Commit Point
(installer.sh)
- "installation_completed" ganz am Ende von backend_postinstall(), vor dem
finalen Reboot (backend.sh)
- "installation_failed" zentral in boot-autostart.sh (einziger Ort, an dem
der echte Exit-Code von installer.sh bekannt ist, ohne jedes Modul
einzeln instrumentieren zu muessen) - Detail = letzte 40 Logzeilen,
enthalten bereits die eigentliche [FEHLER]-Meldung des gescheiterten
Moduls. Nur bei STATUS != 0, nicht bei einem kontrollierten Abbruch an
einem Commit-Gate (STATUS = 0).
Nutzer-Wunsch (01.09.2026, nach dem ersten echten Hardware-Test):
Fehlschlaege waehrend der Installation sichtbar machen, ohne am
Bildschirm mitschreiben zu muessen ("das ist kein Flow... fuer die
Weiterentwicklung eines irgendwann fehlerfreien Betriebs sind fuer uns
diese Meldungen Gold wert").
Live in QEMU end-to-end verifiziert: alle drei Erfolgs-Meilensteine
korrekt in device_installation_events angekommen (activation_confirmed,
installation_started, installation_completed), vollstaendiger Durchlauf
bis zum echten Mint-Cinnamon-Login-Bildschirm nach Reboot bestaetigt.
361 lines
16 KiB
Bash
361 lines
16 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
# Dieses Skript wird von einem Orchestrator-Modul (40_backend.sh) per
|
|
# `source` in dessen Shell geladen. Variablen bleiben deshalb bewusst nicht
|
|
# readonly, um Namenskollisionen mit dem ladenden Modul zu vermeiden.
|
|
#
|
|
# Golden-Image-Deployment-Backend (siehe ADR-0024) - ersetzt die
|
|
# Ubiquity-Automatisierung von backends/mint/ durch das curtin/FAI-Muster:
|
|
# Zieldatentraeger direkt partitionieren, ein fertiges Root-Filesystem-
|
|
# Image entpacken, per chroot nacharbeiten. Kein GUI-Installer, kein
|
|
# Preseed/Kickstart mehr - die eigentliche Mechanik steckt in
|
|
# scripts/lib/image_deploy.sh (Phase 1, isoliert live verifiziert).
|
|
#
|
|
# backends/mint/ bleibt unveraendert als Referenz bestehen - dieses
|
|
# Backend ist ein bewusst NEUER backend_id ("mint-image"), nichts wird
|
|
# live umgeschaltet.
|
|
BACKEND_KEY="mint-image"
|
|
|
|
BACKEND_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_DIR="$(cd "${BACKEND_DIR}/../.." && pwd)"
|
|
POSTINSTALL_SCRIPT="${BACKEND_DIR}/postinstall.sh"
|
|
IMAGE_DEPLOY_LIB="${REPO_DIR}/scripts/lib/image_deploy.sh"
|
|
BOOT_MEDIUM_LIB="${REPO_DIR}/scripts/lib/boot_medium.sh"
|
|
REPORTING_LIB="${REPO_DIR}/scripts/lib/reporting.sh"
|
|
|
|
# Vom Netzwerkmodul geschuetzt abgelegtes WLAN-/Ethernet-Verbindungsprofil
|
|
# (siehe export_connection_profile() in 05_network.sh) - existiert nur,
|
|
# wenn das Provisionierungsinterface ueber ein aktives NetworkManager-
|
|
# Profil lief (z.B. WLAN mit PSK).
|
|
NETWORK_PROFILE_EXPORT="/run/tuxflotte/network/connection.nmconnection"
|
|
|
|
RUNTIME_BLUEPRINT_FILE="/run/tuxflotte/runtime/runtime_blueprint.json"
|
|
SERVER_RESPONSE_FILE="/run/tuxflotte/server/response.json"
|
|
HARDWARE_FILE="/run/tuxflotte/hardware/hardware.json"
|
|
|
|
RUNTIME_DIR="/run/tuxflotte/backend"
|
|
CONFIG_FILE="${RUNTIME_DIR}/config.json"
|
|
|
|
# Ziel-Mountpunkt fuer die Deployment-Mechanik - global, da backend_launch()
|
|
# und backend_postinstall() (separate Funktionsaufrufe, aber dieselbe
|
|
# Shell/derselbe Prozess, siehe 40_backend.sh) sich denselben Baum teilen.
|
|
TARGET_DIR="/target"
|
|
declare -a MOUNT_STACK=()
|
|
|
|
# Aus einer manuell in Proxmox installierten Referenz-VM gezogen (nicht
|
|
# debootstrap - siehe ADR-0024-Nachtrag "Referenz-VM statt debootstrap",
|
|
# 31.08.2026), bereinigt via scripts/package_golden_image.sh, gehostet
|
|
# ueber die unauthentifizierte /golden-images/-Route in
|
|
# provisioning-server (analog ks.cfg) - live verifiziert per Public-HTTPS-
|
|
# Download (200, byte-exakte Groesse) am 31.08.2026.
|
|
GOLDEN_IMAGE_URL="https://anode.tuxflotte.de/golden-images/linux-mint-22.3-cinnamon.tar.zst"
|
|
|
|
backend_log() {
|
|
printf '[backend:%s] %s\n' "${BACKEND_KEY}" "$*" >&2
|
|
}
|
|
|
|
backend_fatal() {
|
|
printf '[backend:%s] FEHLER: %s\n' "${BACKEND_KEY}" "$*" >&2
|
|
return 1
|
|
}
|
|
|
|
# Zieldatentraeger-Erkennung - schliesst das Boot-Medium selbst aus (siehe
|
|
# tuxflotte_boot_medium_disk() in scripts/lib/boot_medium.sh: auf echter
|
|
# USB-Stick-Hardware enumeriert das Medium als normales "disk"-Blockgeraet,
|
|
# genau wie die Zielplatte - ohne Ausschluss schlaegt "parted mklabel"
|
|
# darauf IMMER fehl, weil man nicht die Platte partitionieren kann, von der
|
|
# man gerade lebt).
|
|
_mint_image_detect_target_disk() {
|
|
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 $1; exit }
|
|
'
|
|
}
|
|
|
|
backend_init() {
|
|
# Historisch (bis zum Umstieg auf das eigenstaendige, per live-build
|
|
# gebaute Boot-Medium) wurden diese Werkzeuge hier noch zur Laufzeit per
|
|
# apt-get nachinstalliert, weil das damalige Boot-Medium (eine gepatchte
|
|
# Linux-Mint-Live-ISO) sie nicht immer mitbrachte. Das eigenstaendige
|
|
# Boot-Medium bringt sie bereits im Paketsatz mit (siehe
|
|
# boot-medium/config/package-lists/tuxflotte.list.chroot) - hier bleibt
|
|
# nur noch eine reine Assertion, damit ein kuenftiger Paketlisten-Fehler
|
|
# fruh und klar auffaellt.
|
|
local missing=()
|
|
|
|
command -v jq >/dev/null 2>&1 || missing+=(jq)
|
|
command -v envsubst >/dev/null 2>&1 || missing+=(gettext-base)
|
|
command -v parted >/dev/null 2>&1 || missing+=(parted)
|
|
command -v mkfs.vfat >/dev/null 2>&1 || missing+=(dosfstools)
|
|
command -v mkfs.ext4 >/dev/null 2>&1 || missing+=(e2fsprogs)
|
|
command -v mkfs.btrfs >/dev/null 2>&1 || missing+=(btrfs-progs)
|
|
command -v zstd >/dev/null 2>&1 || missing+=(zstd)
|
|
command -v curl >/dev/null 2>&1 || missing+=(curl)
|
|
|
|
[[ "${#missing[@]}" -eq 0 ]] ||
|
|
{ backend_fatal "Werkzeuge fehlen auf dem Boot-Medium (Paketliste pruefen): ${missing[*]}"; return 1; }
|
|
|
|
[[ -r "${IMAGE_DEPLOY_LIB}" ]] ||
|
|
{ backend_fatal "Deployment-Bibliothek nicht gefunden: ${IMAGE_DEPLOY_LIB}"; return 1; }
|
|
# shellcheck source=../../scripts/lib/image_deploy.sh
|
|
source "${IMAGE_DEPLOY_LIB}"
|
|
|
|
[[ -r "${BOOT_MEDIUM_LIB}" ]] ||
|
|
{ backend_fatal "Boot-Medium-Bibliothek nicht gefunden: ${BOOT_MEDIUM_LIB}"; return 1; }
|
|
# shellcheck source=../../scripts/lib/boot_medium.sh
|
|
source "${BOOT_MEDIUM_LIB}"
|
|
|
|
[[ -r "${REPORTING_LIB}" ]] ||
|
|
{ backend_fatal "Reporting-Bibliothek nicht gefunden: ${REPORTING_LIB}"; return 1; }
|
|
# shellcheck source=../../scripts/lib/reporting.sh
|
|
source "${REPORTING_LIB}"
|
|
|
|
[[ -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; }
|
|
|
|
# Phase 2 deckt bewusst nur das einfache Schema ab (ESP/biosgrub +
|
|
# eine Root-Partition, siehe image_deploy_partition()) - "custom" mit
|
|
# extra_partitions (/home, /var) ist noch nicht auf die neue
|
|
# parted-basierte Mechanik uebertragen. Klarer Fehler statt stiller
|
|
# Fehlinterpretation.
|
|
local scheme
|
|
scheme="$(jq --raw-output '.runtime_blueprint.installation_directives.partitioning.scheme // "single"' "${RUNTIME_BLUEPRINT_FILE}")"
|
|
[[ "${scheme}" == "single" ]] ||
|
|
{ backend_fatal "Partitionierungsschema '${scheme}' wird von diesem Backend noch nicht unterstützt (nur 'single')."; return 1; }
|
|
|
|
if [[ "$(jq --raw-output '.runtime_blueprint.installation_directives.disk_encryption' "${RUNTIME_BLUEPRINT_FILE}")" == "true" ]]; then
|
|
backend_fatal "disk_encryption=true wird von diesem Backend derzeit nicht unterstützt."
|
|
return 1
|
|
fi
|
|
|
|
backend_log "Runtime Blueprint ist gültig für Backend '${BACKEND_KEY}'."
|
|
}
|
|
|
|
backend_generate_config() {
|
|
local hostname device_id device_fingerprint
|
|
local root_filesystem partitioning_json blueprints_json
|
|
|
|
[[ -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; }
|
|
|
|
[[ -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}")"
|
|
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
|
|
|
|
blueprints_json="$(jq --compact-output '.runtime_blueprint.blueprints' "${RUNTIME_BLUEPRINT_FILE}")"
|
|
|
|
jq --null-input \
|
|
--arg hostname "${hostname}" \
|
|
--arg device_id "${device_id}" \
|
|
--arg device_fingerprint "${device_fingerprint}" \
|
|
--arg root_filesystem "${root_filesystem}" \
|
|
--argjson blueprints "${blueprints_json}" \
|
|
'{
|
|
hostname: $hostname,
|
|
device_id: $device_id,
|
|
device_fingerprint: $device_fingerprint,
|
|
root_filesystem: $root_filesystem,
|
|
blueprints: $blueprints
|
|
}' > "${CONFIG_FILE}" ||
|
|
{ backend_fatal "Konfigurationsdatei konnte nicht erzeugt werden."; return 1; }
|
|
|
|
chmod 0600 "${CONFIG_FILE}"
|
|
|
|
backend_log "Konfiguration erzeugt: ${CONFIG_FILE}"
|
|
}
|
|
|
|
backend_launch() {
|
|
local disk is_efi root_fs boot_part root_part
|
|
|
|
disk="$(_mint_image_detect_target_disk)"
|
|
[[ -n "${disk}" ]] ||
|
|
{ backend_fatal "Zieldatenträger konnte nicht ermittelt werden."; return 1; }
|
|
|
|
[[ -d /sys/firmware/efi ]] && is_efi="true" || is_efi="false"
|
|
backend_log "Zieldatenträger: ${disk} (Firmware: $([ "${is_efi}" = true ] && echo UEFI || echo BIOS))"
|
|
|
|
root_fs="$(jq --raw-output '.root_filesystem' "${CONFIG_FILE}")"
|
|
|
|
backend_log "Partitioniere ${disk}"
|
|
read -r boot_part root_part <<<"$(image_deploy_partition "${disk}" "${is_efi}")" ||
|
|
return 1
|
|
|
|
backend_log "Formatiere Partitionen"
|
|
image_deploy_format "${boot_part}" "${root_part}" "${root_fs}" || return 1
|
|
|
|
backend_log "Mounte unter ${TARGET_DIR}"
|
|
image_deploy_mount "${TARGET_DIR}" "${boot_part}" "${root_part}" || return 1
|
|
|
|
# Live gefunden (01.09.2026, echter Hardware-Test): export_connection_profile()
|
|
# in 05_network.sh legt das aktive WLAN-/Ethernet-Profil zwar geschuetzt unter
|
|
# /run/tuxflotte/network/connection.nmconnection ab, aber bis hierhin holte es
|
|
# nie jemand von dort ab - das frisch installierte System stand deshalb beim
|
|
# ersten Boot ohne gespeichertes WLAN-Profil da und fragte den PSK erneut ab.
|
|
if [[ -f "${NETWORK_PROFILE_EXPORT}" ]]; then
|
|
backend_log "Übernehme WLAN-/Netzwerkverbindungsprofil ins Zielsystem"
|
|
image_deploy_install_network_profile "${TARGET_DIR}" "${NETWORK_PROFILE_EXPORT}" || return 1
|
|
fi
|
|
|
|
# Live gefunden (31.08.2026, erster echter End-to-End-Lauf mit dem vollen
|
|
# Referenz-VM-Archiv): "erst nach /run/tuxflotte/... herunterladen, dann
|
|
# entpacken" scheiterte an /run (RAM-Tmpfs, viel kleiner als das
|
|
# 2,3-GB-Archiv - "curl: (23) Failure writing output to destination").
|
|
# Direktes Streamen in die Extraktion braucht nur ein paar MB Puffer,
|
|
# unabhaengig von der Archivgroesse - deshalb erst ab hier (nach
|
|
# Partitionieren/Formatieren/Mounten), kein Zwischenspeichern mehr.
|
|
backend_log "Lade und entpacke Golden Image von ${GOLDEN_IMAGE_URL}"
|
|
image_deploy_extract_image_from_url "${GOLDEN_IMAGE_URL}" "${TARGET_DIR}" || return 1
|
|
|
|
backend_log "Schreibe fstab"
|
|
image_deploy_write_fstab "${TARGET_DIR}" "${boot_part}" "${root_part}" "${root_fs}" || return 1
|
|
|
|
backend_log "Binde /dev, /proc, /sys ein"
|
|
image_deploy_bind_mounts "${TARGET_DIR}" MOUNT_STACK || return 1
|
|
|
|
backend_log "chroot-Fixup (machine-id, SSH-Hostkeys, initramfs)"
|
|
image_deploy_chroot_fixup "${TARGET_DIR}" || return 1
|
|
|
|
backend_log "Installiere Bootloader"
|
|
image_deploy_install_bootloader "${TARGET_DIR}" "${disk}" "${is_efi}" || return 1
|
|
|
|
local hostname
|
|
hostname="$(jq --raw-output '.hostname' "${CONFIG_FILE}")"
|
|
backend_log "Setze Hostname (${hostname})"
|
|
image_deploy_set_hostname "${TARGET_DIR}" "${hostname}" || return 1
|
|
|
|
backend_log "Deployment abgeschlossen."
|
|
}
|
|
|
|
backend_postinstall() {
|
|
local device_id device_fingerprint blueprints_json
|
|
local postinstall_rendered
|
|
|
|
device_id="$(jq --raw-output '.device_id' "${CONFIG_FILE}")"
|
|
device_fingerprint="$(jq --raw-output '.device_fingerprint' "${CONFIG_FILE}")"
|
|
blueprints_json="$(jq --compact-output '.blueprints' "${CONFIG_FILE}")"
|
|
|
|
# Dasselbe Template wie backends/mint/postinstall.sh (per Symlink
|
|
# geteilt, siehe Verzeichnis) - rein distributionsunabhaengiges
|
|
# Bash-Skript (curl/jq gegen anode), hier per chroot statt per
|
|
# ubiquity/success_command ausgefuehrt.
|
|
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
|
|
|
|
printf '%s' "${postinstall_rendered}" > "${TARGET_DIR}/tmp/postinstall.sh"
|
|
chmod 0700 "${TARGET_DIR}/tmp/postinstall.sh"
|
|
|
|
# Live gefunden (31.08.2026, erster vollstaendig durchgelaufener
|
|
# End-to-End-Test): postinstall.sh braucht jq (+curl), aber die echte
|
|
# Mint-Referenz-VM bringt das nicht zwingend mit (anders als das
|
|
# Boot-Medium selbst, das jq ja schon vorinstalliert hat - das hilft
|
|
# dem ausgerollten Zielsystem hier nichts, das ist ein komplett
|
|
# eigener chroot). python3 wird nicht von postinstall.sh selbst
|
|
# gebraucht, aber vom heruntergeladenen agent.py nach dem naechsten
|
|
# Boot - hier gleich mit absichern, um nicht noch einen ganzen
|
|
# Referenz-VM-Neupack-Zyklus wegen eines einzelnen fehlenden Pakets zu
|
|
# brauchen. package_golden_image.sh leert ausserdem /var/lib/apt/lists
|
|
# als Teil der Bereinigung - "apt-get update" ist deshalb hier noetig,
|
|
# bevor "apt-get install" ueberhaupt Pakete finden kann. Netzwerk ist
|
|
# im chroot verfuegbar (resolv.conf wurde schon in
|
|
# image_deploy_bind_mounts kopiert, dieselben Bind-Mounts sind noch
|
|
# aktiv).
|
|
chroot "${TARGET_DIR}" bash -c '
|
|
missing=()
|
|
command -v jq >/dev/null 2>&1 || missing+=(jq)
|
|
command -v curl >/dev/null 2>&1 || missing+=(curl)
|
|
command -v python3 >/dev/null 2>&1 || missing+=(python3)
|
|
[[ "${#missing[@]}" -eq 0 ]] && exit 0
|
|
apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y "${missing[@]}"
|
|
' ||
|
|
{ backend_fatal "jq/curl/python3 konnten im Zielsystem nicht sichergestellt werden."; return 1; }
|
|
|
|
backend_log "Führe Postinstall-Skript im chroot aus."
|
|
chroot "${TARGET_DIR}" /bin/bash /tmp/postinstall.sh ||
|
|
{ backend_fatal "Postinstall-Skript ist im chroot fehlgeschlagen."; return 1; }
|
|
|
|
rm -f "${TARGET_DIR}/tmp/postinstall.sh"
|
|
|
|
backend_log "Hänge Ziel-Dateisystem aus."
|
|
image_deploy_unbind_mounts MOUNT_STACK
|
|
umount --recursive "${TARGET_DIR}" ||
|
|
{ backend_fatal "${TARGET_DIR} konnte nicht ausgehängt werden."; return 1; }
|
|
|
|
tuxflotte_report_installation_event "installation_completed"
|
|
|
|
# Nutzer-Feedback (01.09.2026, echter Hardware-Test): akustischer Hinweis
|
|
# + klare Ansage, dass der USB-Stick jetzt gefahrlos gezogen werden kann
|
|
# (ab hier wird nur noch von der Zielplatte gebootet) - Bestaetigung mit
|
|
# ENTER, sonst automatischer Neustart nach 5 Minuten (kein Warten auf
|
|
# eine Person, die vielleicht schon weitergegangen ist). "\a" (BEL) statt
|
|
# eines externen Tonwerkzeugs - funktioniert auf jeder PC-Lautsprecher-
|
|
# Hardware ohne zusaetzliches Paket.
|
|
printf '\a\a\a'
|
|
echo
|
|
echo "Fertig! Sie können den USB-Stick jetzt entfernen."
|
|
echo "Weiter mit ENTER, oder automatischer Neustart in 5 Minuten."
|
|
read -r -t 300 _ || true
|
|
|
|
backend_log "Starte neu - kein Rücksprung erwartet, ab hier läuft das frisch installierte System."
|
|
reboot
|
|
}
|