#!/bin/bash
#######################################################################################
### Script für eine vollständige Installation der Notfallbox, optimiert durch claude.ai
###
### 200   Aufbauend auf 119 neue Version für die Notfallbox 200
###       Überarbeitung: läuft komplett als root, alle Ausgaben ins Logfile,
###       Schritt-Steuerung als Schleife, Konfiguration als Shell-Datei,
###       Fehlerprotokoll mit Zeilennummer, idempotente Schritte
###       Fix: gewähltes Modell ging nach Schritt 0 verloren; Prüfung auf fehlenden Webserver
###       Fix: Fehlermeldung im Auswahldialog auf dem PC (/proc/device-tree); Abbruch ohne notfallbox.sql
### 	  Fortschrittsbalken (whiptail --gauge) mit Laufzeitanzeige für die Schritte 1 bis 5
###############################################################################
script="200"

###############################################################################
### Download-Quellen und Versionen  ---  HIER ANPASSEN
###############################################################################
DL_M="https://login.yoursecurecloud.de/f/ab621f67ff3a46f6a4ce/?dl=1"
DL_Xpi="https://login.yoursecurecloud.de/seafhttp/f/83b6d74af52d4956b70a/?op=view"
DL_Xpc="https://login.yoursecurecloud.de/seafhttp/f/ef0306f66c5249bda41c/?op=view"

#            Version  Update  Setup  Netzwerk
VER_M=(      116      103     103    0   )
VER_Xpi=(    200      107     115    100 )
VER_Xpc=(    200      107     115    100 )

# Erwartete Debian-Hauptversion je Modell (12 = Bookworm, 13 = Trixie)
DEB_M=12
DEB_Xpi=12
DEB_Xpc=13

# Weitere Optionen
bluetoothAus=0                              # 1 = Bluetooth auf dem Pi abschalten
dbPasswort="notfallbox"                     # Passwort für MariaDB root/notfallbox/phpmyadmin und FTP
ftpPasswdFile="/etc/proftpd/ftpd.passwd"    # muss zur AuthUserFile-Zeile in proftpd.conf passen
debug=0                                     # 1 = jeden Befehl zusätzlich ins Logfile schreiben (set -x)

###############################################################################
### Grundeinstellungen
###############################################################################
# Das Script startet sich selbst einmal mit sudo neu -> danach ist kein einziges "sudo" mehr nötig
if [ "$EUID" -ne 0 ]; then
	exec sudo bash "$(readlink -f "$0")" "$@"
fi

# Immer im Verzeichnis des Scripts arbeiten, damit Status-/Konfig-Datei nach einem Abbruch wiedergefunden werden
scriptPfad="$(readlink -f "$0")"
cd "$(dirname "$scriptPfad")" || exit 1

header="### Installation Notfallbox ($script) ###"
webRoot="/var/www/html"
kiwixRoot="$webRoot/kiwix"
temp="tempG"
konfigFile=".notfallbox_config"
statusFile=".notfallbox_status"
installLogfile="Installation_Logfile_Notfallbox"
installiertMarker="/etc/notfallbox.installiert"
debian=$(cut -d '.' -f 1 /etc/debian_version)
installUser="${SUDO_USER:-root}"
installHome=$(getent passwd "$installUser" | cut -d: -f6)

export DEBIAN_FRONTEND=noninteractive
APT=(apt-get -y -q -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold)

###############################################################################
### Hilfsfunktionen
###############################################################################
# Bildschirm-Dialoge: whiptail zeichnet immer direkt aufs Terminal, auch wenn stdout ins Logfile geht
ui_info()  { TERM=ansi whiptail --backtitle "$header" --title "$1" --infobox "$2" "${3:-9}" 100 >/dev/tty 2>&1; }
ui_msg()   { whiptail --clear --backtitle "$header" --title "$1" --msgbox "$2" "${3:-0}" 100 </dev/tty >/dev/tty 2>&1; }
ui_yesno() { whiptail --clear --yes-button "$3" --no-button "$4" --backtitle "$header" --title "$1" --yesno "$2" "${5:-0}" 100 </dev/tty >/dev/tty 2>&1; }
ui_menu()  { local t=$1 txt=$2; shift 2
             whiptail --nocancel --clear --backtitle "$header" --title "$t" --menu "$txt" 0 100 0 "$@" </dev/tty 2>&1 >/dev/tty; }
# Ausgabe direkt an den Benutzer (am Logfile vorbei)
sag()      { echo -e "$*" >/dev/tty; }

# Abschnitts-Überschrift im Logfile
abschnitt() { printf '\n########## %s  [%s] ##########\n' "$*" "$(date '+%F %T')"; }

###############################################################################
### Fortschrittsbalken (whiptail --gauge)
###############################################################################
# So funktioniert es:
#  - whiptail --gauge läuft im Hintergrund und liest seine Anzeige aus einer FIFO (in einem mktemp-Verzeichnis)
#  - Ein "Herzschlag"-Prozess schreibt jede Sekunde den aktuellen Stand hinein:
#    Prozentwert, Text, Gesamtlaufzeit und ein drehendes Zeichen -> man sieht immer, dass es weitergeht
#  - Die Installationsschritte melden nur Meilensteine:   fortschritt VON BIS "Text" [Datei]
#    Zwischen VON und BIS kriecht der Balken langsam weiter, erreicht BIS aber nie von selbst.
#    Ist eine Datei angegeben, wird ihre aktuelle Größe angezeigt (z.B. beim Download).
gaugeDir=""; gaugePid=""; herzPid=""
gaugeTau=60     # Sekunden, nach denen der Balken die Hälfte des Weges zum nächsten Meilenstein geschafft hat

# Meilenstein setzen (und als Kommentar ins Logfile schreiben)
fortschritt() {
	local logtext=${3//\\n/ - }
	printf '### [%s%%] %s\n' "$1" "$logtext"
	[ -n "$gaugeDir" ] || return 0
	local jetzt
	printf -v jetzt '%(%s)T' -1
	printf '%s|%s|%s|%s|%s\n' "$1" "$2" "$jetzt" "${3//|/-}" "${4:-}" > "$gaugeDir/stand.neu" \
		&& mv -f "$gaugeDir/stand.neu" "$gaugeDir/stand"
}

# Läuft im Hintergrund und aktualisiert die Anzeige jede Sekunde
gauge_herz() {
	trap - ERR EXIT
	set +x
	local von bis seit text datei jetzt t pct start letzt=0 n=0 zusatz
	local punkte=(".  " ".. " "..." "   ")
	printf -v start '%(%s)T' -1
	while [ -e "$gaugeDir/laeuft" ]; do
		if IFS='|' read -r von bis seit text datei < "$gaugeDir/stand"; then
			printf -v jetzt '%(%s)T' -1
			t=$(( jetzt - seit ))
			pct=$von
			if (( bis - von > 1 )); then pct=$(( von + (bis - von - 1) * t / (t + gaugeTau) )); fi
			if (( pct < letzt && letzt < bis )); then pct=$letzt; fi
			letzt=$pct
			zusatz=""
			if [ -n "$datei" ] && [ -f "$datei" ]; then
				zusatz="     Geladen: $(( $(stat -c %s "$datei") / 1048576 )) MB"
			fi
			t=$(( jetzt - start ))
			printf 'XXX\n%d\n%s\n\nLaufzeit %02d:%02d %s%s\nXXX\n' \
				"$pct" "$text" $(( t / 60 )) $(( t % 60 )) "${punkte[n++ % 4]}" "$zusatz" >&7
		fi
		sleep 1
	done
}

gauge_start() {
	[ -n "$gaugePid" ] && return 0
	gaugeDir=$(mktemp -d) && mkfifo "$gaugeDir/fifo" || { gaugeDir=""; return 0; }
	fortschritt 0 1 "Die Installation startet ..."
	touch "$gaugeDir/laeuft"
	whiptail --backtitle "$header" --title "Installation läuft - bitte nicht ausschalten!" \
		--gauge "Die Installation startet ..." 11 100 0 <"$gaugeDir/fifo" >/dev/tty 2>&1 &
	gaugePid=$!
	exec 7>"$gaugeDir/fifo"
	gauge_herz &
	herzPid=$!
}

# Balken beenden, optional mit abschließender 100%-Meldung
gauge_stop() {
	[ -n "$gaugePid" ] || return 0
	rm -f "$gaugeDir/laeuft"
	wait "$herzPid" 2>/dev/null || :
	if [ -n "$1" ] && kill -0 "$gaugePid" 2>/dev/null; then
		printf 'XXX\n100\n%b\nXXX\n' "$1" >&7
		sleep 2
	fi
	exec 7>&-
	wait "$gaugePid" 2>/dev/null || :
	rm -rf "$gaugeDir"
	gaugeDir=""; gaugePid=""; herzPid=""
}

# Abbruch mit Meldung - der Status bleibt erhalten, ein erneuter Aufruf macht an dieser Stelle weiter
abbruch() {
	gauge_stop
	abschnitt "ABBRUCH: $1"
	ui_msg "Fehler" "$1\n\nDetails stehen im Logfile:\n$PWD/$installLogfile\n\nNach Behebung des Problems das Script einfach erneut starten,\nes setzt an dieser Stelle wieder auf." 
	exit 1
}

# Wert in der Konfigurationsdatei speichern (als Shell-Variable, wird später mit "source" eingelesen)
cfgSave() { printf '%s=%q\n' "$1" "$2" >> "$konfigFile"; }

# Variable in /etc/environment setzen, ohne bei Wiederholung doppelte Zeilen zu erzeugen
setEnv() {
	sed -i "/^\(export \)\?$1=/d" /etc/environment
	echo "export $1=$2" >> /etc/environment
}

# Versionen und Download-Pfad zum gewählten Modell laden
modell_laden() {
	local -n ver="VER_$1"
	local dl="DL_$1"
	nfbModel="$1"
	nfbVersion=${ver[0]}
	nfbUpdate=${ver[1]}
	nfbSetup=${ver[2]}
	nfbNetzwerk=${ver[3]}
	dlPath="${!dl}"
	nfbArchiv="NFB-$nfbModel-$nfbVersion.zip"
}

###############################################################################
### Schritt 0: Begrüßung und Auswahl der Variante (noch keine Änderungen am System)
###############################################################################
s0_auswahl() {
	abschnitt "Schritt 0: Auswahl der Variante"

	local infoText="Die Installation besteht aus folgenden Teilen:\n"
	infoText+="------------------------------------------------\n"
	infoText+="    a) Auswahl der Notfallbox-Variante\n"
	infoText+="    b) Betriebssystem ergänzen und konfigurieren\n"
	infoText+="    c) Installation des Webservers\n"
	infoText+="    d) Installation der Webserver-Inhalte\n\n"
	infoText+="Nach Abschluss aller Schritte erhält man eine betriebsfertige Notfallbox in der Grundausstattung.\n\n"
	infoText+="SOLLEN WIR LOSLEGEN?"
	if ! ui_yesno "Herzlich Willkommen !!!" "$infoText" "Let's Rock'n'Roll" "Lieber doch nicht!"; then
		sag "\nDie Installation wurde ohne Änderungen am System abgebrochen.\n"
		rm -f "$statusFile" "$konfigFile"
		exit 0
	fi

	# Hardware erkennen und nur passende Varianten anbieten
	local arch piModel
	arch=$(dpkg --print-architecture)
	piModel=""
	# /proc/device-tree gibt es nur auf dem Raspberry Pi (und anderen ARM-Boards), nicht auf dem PC
	[ -r /proc/device-tree/model ] && piModel=$(tr -d '\0' < /proc/device-tree/model)
	echo "Architektur: $arch / Modell: ${piModel:-unbekannt} / Debian: $debian"

	local txtM="Mini  - Raspberry Pi Zero, Zero W, Zero 2W (Raspberry OS Bookworm)"
	local txtXpi="eXtra - Raspberry Pi Zero 2W, 3, 4, 5 (Raspberry OS Bookworm)"
	local txtXpc="eXtra - Intel/AMD-PC x86_64 (Debian Trixie)"
	local auswahl=()
	case "$arch" in
		amd64)	auswahl=(Xpc "$txtXpc") ;;
		arm*)	case "$piModel" in
					*"Zero 2"*)                       auswahl=(M "$txtM" Xpi "$txtXpi") ;;
					*Zero*)                           auswahl=(M "$txtM") ;;
					*"Pi 3"*|*"Pi 4"*|*"Pi 5"*)       auswahl=(Xpi "$txtXpi") ;;
					*)                                auswahl=(M "$txtM" Xpi "$txtXpi") ;;
				esac ;;
		*)		abbruch "Die Architektur '$arch' wird von der Notfallbox nicht unterstützt." ;;
	esac

	cfgModel=""   # bewusst NICHT local - wird in allen späteren Schritten gebraucht
	while [ -z "$cfgModel" ]; do
		cfgModel=$(ui_menu "a) Installationsoptionen" "\nErkannt: ${piModel:-PC ($arch)}, Debian $debian\n\nBitte verrate uns, welche Variante der Notfallbox Du installieren willst." "${auswahl[@]}")
	done
	echo "Gewähltes Modell: $cfgModel"

	# Passt die Debian-Version zum Modell?
	local deb="DEB_$cfgModel"
	if [ "$debian" != "${!deb}" ]; then
		if ! ui_yesno "Achtung" "Die Variante $cfgModel ist für Debian ${!deb} gedacht, installiert ist aber Debian $debian.\n\nTrotzdem fortfahren?" "Trotzdem weiter" "Abbrechen"; then
			sag "\nDie Installation wurde ohne Änderungen am System abgebrochen.\n"
			exit 0
		fi
	fi

	local cfgFinText="Jetzt werden die notwendigen Dateien heruntergeladen, platziert und installiert. "
	cfgFinText+="Dies wird - abhängig vom verwendeten Raspberry PI / Computer - ein wenig dauern.\n\n"
	cfgFinText+="Willst Du nun die Installation starten?"
	if ! ui_yesno "Installation" "$cfgFinText" "Yes, hau' rein!" "STOP! Besser doch nicht!" 11; then
		sag "\nDie Installation wurde ohne Änderungen am System abgebrochen.\nZum erneuten Aufruf bitte $0 erneut starten.\n"
		exit 0
	fi

	rm -f "$konfigFile"
	cfgSave debian "$debian"
	cfgSave cfgModel "$cfgModel"
	modell_laden "$cfgModel"
	echo "Download-Pfad: $dlPath"
}

###############################################################################
### Schritt 1: Paketquellen aktualisieren, Hilfsprogramme installieren
###############################################################################
s1_pakete() {
	abschnitt "Schritt 1: Paketquellen und Hilfsprogramme"
	fortschritt 1 4 "Schritt 1 von 5: Betriebssystem\nDie Paketquellen werden aktualisiert ..."
	"${APT[@]}" update || abbruch "Die Paketquellen konnten nicht aktualisiert werden (Internetverbindung?)."

	# HINWEIS: dnsmasq hängt von dnsmasq-base ab - es wird also gleich wieder installiert (siehe Analyse)
	fortschritt 4 6 "Schritt 1 von 5: Betriebssystem\nAlte Pakete werden entfernt ..."
	"${APT[@]}" remove --purge dnsmasq-base

	fortschritt 6 15 "Schritt 1 von 5: Betriebssystem\nHilfsprogramme werden installiert ..."
	local pkgs=(unzip ftp netcat-openbsd iw rfkill xmlstarlet whiptail hostapd dnsmasq iptables network-manager)
	"${APT[@]}" install "${pkgs[@]}" || abbruch "Die Hilfsprogramme konnten nicht installiert werden."
}

###############################################################################
### Schritt 2: Betriebssystem anpassen
###############################################################################
s2_system() {
	abschnitt "Schritt 2: Betriebssystem anpassen"
	if [ "$cfgModel" != "Xpc" ]; then
		fortschritt 15 17 "Schritt 2 von 5: Betriebssystem anpassen\nWLAN-Land wird eingestellt ..."
		raspi-config nonint do_wifi_country DE
		fortschritt 17 20 "Schritt 2 von 5: Betriebssystem anpassen\nDas Dateisystem wird vergrößert ..."
		raspi-config nonint do_expand_rootfs

		if [ "$bluetoothAus" = "1" ]; then
			echo "### Bluetooth abschalten"
			local configTXT="/boot/firmware/config.txt"
			[ -f "$configTXT" ] || configTXT="/boot/config.txt"
			grep -q '^dtoverlay=disable-bt' "$configTXT" || printf '\n### Bluetooth abschalten\ndtoverlay=disable-bt\n' >> "$configTXT"
		fi
	fi

	fortschritt 20 25 "Schritt 2 von 5: Betriebssystem anpassen\nDie deutsche Sprachunterstützung wird eingerichtet ..."
	sed -i 's/^# *\(de_DE.UTF-8 UTF-8\)/\1/' /etc/locale.gen
	grep -q '^de_DE.UTF-8 UTF-8' /etc/locale.gen || echo 'de_DE.UTF-8 UTF-8' >> /etc/locale.gen
	locale-gen
	update-locale LANG=de_DE.UTF-8
}

###############################################################################
### Schritt 3: Webserver
###############################################################################
installiere_shellinabox() {
	"${APT[@]}" install openssl shellinabox
	sed -i 's|^SHELLINABOX_PORT=.*|SHELLINABOX_PORT=666|' /etc/default/shellinabox
	systemctl restart shellinabox
}

webserver_M() {
	fortschritt 25 45 "Schritt 3 von 5: Webserver\nlighttpd und PHP werden installiert ..."
	"${APT[@]}" install lighttpd php-cgi || abbruch "lighttpd konnte nicht installiert werden."
	lighttpd-enable-mod fastcgi fastcgi-php
	systemctl force-reload lighttpd
	fortschritt 45 55 "Schritt 3 von 5: Webserver\nDie Web-Konsole (Shellinabox) wird installiert ..."
	installiere_shellinabox
}

webserver_X() {
	fortschritt 25 45 "Schritt 3 von 5: Webserver\nApache, PHP, MariaDB und phpMyAdmin werden installiert ..."
	debconf-set-selections <<EOF
phpmyadmin phpmyadmin/reconfigure-webserver multiselect apache2
phpmyadmin phpmyadmin/dbconfig-install boolean true
phpmyadmin phpmyadmin/mysql/admin-user string root
phpmyadmin phpmyadmin/mysql/admin-pass password $dbPasswort
phpmyadmin phpmyadmin/mysql/app-pass password $dbPasswort
phpmyadmin phpmyadmin/app-password-confirm password $dbPasswort
EOF
	"${APT[@]}" install apache2 php php-mysql mariadb-server mariadb-client phpmyadmin \
		|| abbruch "Apache/MariaDB/phpMyAdmin konnten nicht installiert werden."

	fortschritt 45 47 "Schritt 3 von 5: Webserver\nDie Datenbank wird eingerichtet ..."
	# root darf sich weiterhin per sudo ohne Passwort (unix_socket) ODER mit Passwort (phpMyAdmin) anmelden
	mariadb -u root <<EOS
ALTER USER 'root'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('$dbPasswort');
DROP USER IF EXISTS ''@'localhost';
DROP USER IF EXISTS ''@'%';
DROP USER IF EXISTS 'root'@'%';
DROP DATABASE IF EXISTS test;
CREATE USER IF NOT EXISTS 'notfallbox'@'localhost' IDENTIFIED BY '$dbPasswort';
GRANT ALL PRIVILEGES ON notfallbox.* TO 'notfallbox'@'localhost';
-- TODO Sicherheit: phpmyadmin bekommt Vollzugriff auf ALLE Datenbanken (siehe Analyse)
GRANT ALL PRIVILEGES ON *.* TO 'phpmyadmin'@'localhost';
FLUSH PRIVILEGES;
EOS

	fortschritt 47 51 "Schritt 3 von 5: Webserver\nDie Web-Konsole (Shellinabox) wird installiert ..."
	installiere_shellinabox

	fortschritt 51 55 "Schritt 3 von 5: Webserver\nDer FTP-Server wird installiert ..."
	"${APT[@]}" install proftpd
	mkdir -p /home/ftp
	echo "$dbPasswort" | ftpasswd --passwd --file="$ftpPasswdFile" --name notfallbox \
		--uid 33 --gid 33 --home /home/ftp --shell /bin/false --stdin
}

s3_webserver() {
	abschnitt "Schritt 3: Webserver ($cfgModel)"
	case "$cfgModel" in
		M)		webserver_M ;;
		Xpi|Xpc)	webserver_X ;;
		*)		abbruch "Unbekanntes Modell '$cfgModel' - die Konfiguration ist fehlerhaft." ;;
	esac
}

###############################################################################
### Schritt 4: Inhalte des Webservers
###############################################################################
s4_inhalte() {
	abschnitt "Schritt 4: Inhalte herunterladen und installieren"

	# Ohne Webserver aus Schritt 3 gibt es kein Webverzeichnis
	if [ ! -d "$webRoot" ]; then
		echo 3 > "$statusFile"
		abbruch "Das Webverzeichnis $webRoot fehlt - der Webserver (Schritt 3) ist nicht installiert.\nDer Status wurde auf Schritt 3 zurückgesetzt."
	fi

	mkdir -p "$temp"
	local archiv="$temp/$nfbArchiv"

	# Ein vorhandenes, aber defektes Archiv (z.B. abgebrochener Download) wird verworfen
	[ -f "$archiv" ] && fortschritt 55 57 "Schritt 4 von 5: Inhalte\nDas vorhandene Archiv wird geprüft ..."
	if [ -f "$archiv" ] && ! unzip -tq "$archiv" >/dev/null; then
		echo "### Vorhandenes Archiv ist defekt und wird neu geladen"
		rm -f "$archiv"
	fi
	if [ ! -f "$archiv" ]; then
		echo "### Download: $dlPath"
		fortschritt 57 72 "Schritt 4 von 5: Inhalte\nDas Installationsarchiv wird heruntergeladen ..." "$archiv"
		wget -nv -O "$archiv" "$dlPath" || { rm -f "$archiv"; abbruch "Das Installationsarchiv konnte nicht heruntergeladen werden."; }
	fi
	fortschritt 72 76 "Schritt 4 von 5: Inhalte\nDas Archiv wird auf Vollständigkeit geprüft ..."
	unzip -tq "$archiv" >/dev/null || { rm -f "$archiv"; abbruch "Die heruntergeladene Datei ist kein gültiges ZIP-Archiv.\nBitte den Download-Link prüfen."; }

	# Ohne notfallbox.sql läuft die Anwendung nicht (leere Seite im Browser) -> schon VOR dem Entpacken prüfen
	if [ "$cfgModel" != "M" ]; then
		echo "### Prüfe, ob notfallbox.sql im Archiv enthalten ist"
		if ! unzip -l "$archiv" notfallbox.sql >/dev/null; then
			rm -f "$archiv"
			abbruch "Im Archiv $nfbArchiv fehlt die Datenbank notfallbox.sql.\nOhne sie zeigt die Notfallbox im Browser nur eine leere Seite.\n\nBitte das Archiv auf dem Server korrigieren. Beim nächsten Start wird es neu heruntergeladen."
		fi
	fi

	fortschritt 76 88 "Schritt 4 von 5: Inhalte\nDas Archiv wird nach $webRoot entpackt ..."
	unzip -oq "$archiv" -d "$webRoot" || abbruch "Das Archiv konnte nicht entpackt werden."

	# Kiwix einrichten und starten
	if [ -f "$kiwixRoot/kiwix.service" ]; then
		fortschritt 88 90 "Schritt 4 von 5: Inhalte\nKiwix wird eingerichtet und gestartet ..."
		local hw
		hw=$(uname -m)
		if [ ! -d "$kiwixRoot/kiwix_$hw" ]; then
			case "$hw" in
				armv7l)	hw=armhf ;;
				armv6l)	hw=armv6 ;;
			esac
		fi
		if [ -d "$kiwixRoot/kiwix_$hw" ]; then
			cp -a "$kiwixRoot/kiwix_$hw/." "$kiwixRoot/"
		else
			echo "!!! Keine Kiwix-Programme für Architektur $(uname -m) im Archiv gefunden"
		fi
		rm -rf "$kiwixRoot"/kiwix_*/
		install -m 0644 "$kiwixRoot/kiwix.service" /etc/systemd/system/kiwix.service
		systemctl daemon-reload
		systemctl enable --now kiwix.service
	fi

	# Datenbank erstellen und Inhalte einspielen
	if [ "$cfgModel" != "M" ]; then
		[ -f "$webRoot/notfallbox.sql" ] || abbruch "Die Datenbank $webRoot/notfallbox.sql wurde nach dem Entpacken nicht gefunden."
		fortschritt 90 93 "Schritt 4 von 5: Inhalte\nDie Datenbank wird erstellt und eingelesen ..."
		mariadb -u root < "$webRoot/notfallbox.sql" || abbruch "Die Datenbank konnte nicht eingelesen werden."
		rm -f "$webRoot/notfallbox.sql"
	fi

	# Nacharbeiten
	if [ -f "$webRoot/index.php" ]; then
		fortschritt 93 96 "Schritt 4 von 5: Inhalte\nDateirechte werden gesetzt ..."
		rm -f "$webRoot/index.html" "$webRoot/index.lighttpd.html"
		chown -R www-data:www-data "$webRoot"
		# TODO Sicherheit: 777 macht alles für jeden beschreib- und ausführbar (siehe Analyse)
		chmod -R 777 "$webRoot"
	fi

	fortschritt 96 97 "Schritt 4 von 5: Inhalte\nDie Notfallbox-Befehle werden installiert ..."
	setEnv nfbModel    "$cfgModel"
	setEnv nfbVersion  "$nfbVersion"
	setEnv nfbSetup    "$nfbSetup"
	setEnv nfbUpdate   "$nfbUpdate"
	setEnv nfbNetzwerk "$nfbNetzwerk"

	echo "### Befehle Setup, Update, Version, Netzwerk installieren"
	install -m 0555 "$webRoot"/install/{Setup,Update,Version,Netzwerk} /usr/bin/ || abbruch "Die Notfallbox-Befehle fehlen im Archiv."
	local b
	for b in setup nfbSetup nfbsetup;       do ln -sf /usr/bin/Setup    "/usr/bin/$b"; done
	for b in update nfbUpdate nfbupdate;    do ln -sf /usr/bin/Update   "/usr/bin/$b"; done
	for b in version nfbVersion nfbversion; do ln -sf /usr/bin/Version  "/usr/bin/$b"; done
	for b in netzwerk nfbnetzwerk;          do ln -sf /usr/bin/Netzwerk "/usr/bin/$b"; done
	rm -rf "$webRoot/install"
}

###############################################################################
### Schritt 5: Abschluss
###############################################################################
s5_abschluss() {
	abschnitt "Schritt 5: Abschluss"
	local tmpdatei

	fortschritt 97 99 "Schritt 5 von 5: Abschluss\nUSB-Automount und Rechte werden eingerichtet ..."
	systemd-umount /var/www/html/admin/usb
	systemctl reset-failed
	tmpdatei=$(mktemp)
	cat > "$tmpdatei" <<'EOF'
# FAT/exFAT/NTFS
ACTION=="add", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", ENV{ID_FS_USAGE}=="filesystem", ENV{ID_FS_TYPE}=="vfat|exfat|ntfs", RUN{program}+="/usr/bin/systemd-mount --no-block --automount=yes --collect --options=uid=1000,gid=1000,umask=022 $devnode /var/www/html/admin/usb"

# ext4 und andere Linux-Dateisysteme
ACTION=="add", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", ENV{ID_FS_USAGE}=="filesystem", ENV{ID_FS_TYPE}!="vfat|exfat|ntfs", RUN{program}+="/usr/bin/systemd-mount --no-block --automount=yes --collect $devnode /var/www/html/admin/usb"
EOF
	install -o root -g root -m 0644 "$tmpdatei" /etc/udev/rules.d/99-usb-automount.rules
	rm -f "$tmpdatei"
	udevadm control --reload-rules

	echo "### Sonderrechte für www-data"
	tmpdatei=$(mktemp)
	# Befehle, die das Webinterface (apache2/lighttpd als www-data) per sudo aufrufen darf.
	# Gruppen mit fest vorgegebenen Argumenten sind sicher. Die Gruppen ohne Argument-Einschränkung
	# (NFB_DIENSTE, NFB_PAKETE, NFB_DATEIEN) erlauben www-data faktisch weiterhin alles -> siehe TODO.
	cat > "$tmpdatei" <<'EOF'
# --- Notfallbox: sudo-Rechte für das Webinterface ---
# Fest eingeschränkt
Cmnd_Alias NFB_SYSTEM  = /usr/sbin/reboot, /usr/sbin/halt, /usr/bin/date -s *
Cmnd_Alias NFB_USB     = /usr/bin/systemd-umount /var/www/html/admin/usb, \
                         /usr/bin/umount -l /var/www/html/admin/usb
Cmnd_Alias NFB_DB      = /usr/bin/mysqldump
Cmnd_Alias NFB_GPIO    = /var/www/html/gpio/dienst

# TODO Sicherheit: ohne Argument-Einschränkung = volle root-Rechte, bitte auf die tatsächlich genutzten Aufrufe eingrenzen
Cmnd_Alias NFB_DIENSTE = /usr/bin/systemctl
Cmnd_Alias NFB_PAKETE  = /usr/bin/apt, /usr/bin/raspi-config
Cmnd_Alias NFB_DATEIEN = /usr/bin/cp, /usr/bin/rm, /usr/bin/chmod, /usr/bin/chown

www-data ALL=(root) NOPASSWD: NFB_SYSTEM, NFB_USB, NFB_DB, NFB_GPIO, NFB_DIENSTE, NFB_PAKETE, NFB_DATEIEN
notfallbox 	ALL=(ALL:ALL)	NOPASSWD: ALL
EOF
	# Erst prüfen, dann installieren - eine fehlerhafte sudoers-Datei würde sudo komplett lahmlegen
	if visudo -cf "$tmpdatei"; then
		install -o root -g root -m 0440 "$tmpdatei" /etc/sudoers.d/notfallbox
	else
		echo "!!! sudoers-Datei fehlerhaft - wurde NICHT installiert"
	fi
	rm -f "$tmpdatei"

	echo "### Verzeichnisrechte"
	# TODO Sicherheit: 777 auf dem Home-Verzeichnis, siehe Analyse (u.a. SSH-Schlüssel-Login)
	chmod 777 /home/ftp 2>/dev/null
	[ -n "$installHome" ] && [ "$installHome" != "/root" ] && chmod 777 "$installHome"

	gauge_stop "Schritt 5 von 5: Abschluss\nDie Installation ist abgeschlossen!"

	# Infos für den Benutzer
	local t1="Die Installation der Notfallbox wurde vollständig beendet!\n\n"
	t1+="Weitere Informationen zum System findest Du unter https://notfallbox.info\n"
	t1+="Hilfe findest Du in unserem Helpdesk unter https://notfallbox.info/helpdesk\n\n"
	t1+="Die Notfallbox wurde mit Standard-Einstellungen installiert.\n\n"
	t1+="Dir stehen ab sofort folgende zusätzliche Befehle in der Kommandozeile zur Verfügung:\n"
	t1+="---------------------------------------------------------------------------------\n"
	t1+="setup    ==> Grundeinstellung der Notfallbox\n"
	t1+="netzwerk ==> Grundeinstellung der Netzwerk-Konfiguration\n"
	t1+="update   ==> Updates einspielen\n"
	t1+="version  ==> Versionen der eingesetzten Komponenten\n"
	ui_msg "INFORMATION" "$t1" 19

	local t2="... und nun können wir nur noch hoffen, dass das ganze System niemals ernsthaft zum Einsatz kommen muss. "
	t2+="Es wäre zwar schade um die viele Arbeit - aber es muss ja nicht sein, dass wir in eine Katastrophe hineinschlittern, "
	t2+="in welcher wir um den Einsatz von Notfallboxen nicht herumkommen.\n\n"
	t2+="Beste Grüße und alles Gute wünscht Guido, DJ1NG, DN1NF, Manager von DL0JRK und DF0BJ"
	ui_msg "INFORMATION" "$t2" 14

	ui_msg "Neustart notwendig" "Nun muss die Notfallbox zum Abschluss noch einmal neu gestartet werden." 9

	echo "### Aufräumen und Neustart"
	echo "Notfallbox $cfgModel installiert am $(date '+%F %T') mit Installer $script" > "$installiertMarker"
	rm -rf "$temp"
	rm -f "$konfigFile" "$statusFile" "$scriptPfad"
	clear >/dev/tty
	reboot
}

# Wird aufgerufen, wenn die Notfallbox bereits installiert ist
schon_installiert() {
	local t="Unglaublich, aber wahr: Die Notfallbox wurde bereits vollständig installiert.\n"
	t+="Ein erneuter Installationsvorgang würde das System massiv beschädigen.\n\n"
	t+="Dir stehen jederzeit folgende Befehle in der Kommandozeile zur Verfügung:\n"
	t+="---------------------------------------------------------------------------------\n"
	t+="setup    ==> Grundeinstellung der Notfallbox\n"
	t+="netzwerk ==> Grundeinstellung der Netzwerk-Konfiguration\n"
	t+="update   ==> Updates einspielen\n"
	t+="version  ==> Versionen der eingesetzten Komponenten"
	ui_msg "INFORMATION" "$t"
	sag "\nNichts passiert ;-)\n"
	exit 0
}

###############################################################################
### Ablaufsteuerung
###############################################################################
[ -f "$installiertMarker" ] && schon_installiert

schritte=(s0_auswahl s1_pakete s2_system s3_webserver s4_inhalte s5_abschluss)

status=$(cat "$statusFile" 2>/dev/null)
[[ "$status" =~ ^[0-9]+$ ]] || status=0

# Neuer Durchlauf: alte Reste entfernen
if [ "$status" -eq 0 ]; then
	rm -f "$konfigFile" "$installLogfile"
fi

# Ab hier landet ALLES (stdout + stderr) im Logfile; Dialoge gehen direkt ans Terminal
exec >>"$installLogfile" 2>&1
if [ "$debug" = "1" ]; then
	exec 5>>"$installLogfile"
	BASH_XTRACEFD=5
	set -x
fi
# Jeder fehlgeschlagene Befehl wird mit Zeilennummer protokolliert (ohne die Installation abzubrechen)
set -E
# Meldung geht nach stderr (= Logfile), damit sie nie in einem $(...) eines Dialogs landet
trap 'echo "!!! FEHLER (rc=$?) in Zeile $LINENO: $BASH_COMMAND" >&2' ERR
# Fortschrittsbalken beim Beenden (auch bei Abbruch/Strg+C) immer sauber schließen
trap gauge_stop EXIT

abschnitt "Installer $script gestartet als $installUser, Status $status"

# Bei Wiederaufnahme: Konfiguration einlesen
if [ "$status" -gt 0 ]; then
	[ -f "$konfigFile" ] || abbruch "Die Konfigurationsdatei $konfigFile fehlt.\nBitte $statusFile löschen und neu beginnen."
	ui_info "Fortsetzung" "Die Installation wird bei Schritt $status fortgesetzt.\n\nEinen Moment bitte."
	# shellcheck source=/dev/null
	source "$konfigFile"
	[ -n "$cfgModel" ] || abbruch "In $konfigFile ist kein Modell gespeichert.\nBitte $statusFile löschen und neu beginnen."
	modell_laden "$cfgModel"
fi

for ((i = status; i < ${#schritte[@]}; i++)); do
	(( i >= 1 )) && gauge_start
	"${schritte[i]}"
	echo $((i + 1)) > "$statusFile"
done
