Files
Halloween/tools/install_pi_service.sh
2026-10-14 17:40:31 +02:00

109 lines
3.7 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
service_name=halloween-grab.service
unit_path="/etc/systemd/system/$service_name"
apply=false
print_unit=false
bind_host=127.0.0.1
usage() {
cat <<'EOF'
Richtet die Halloween-WebUI als systemd-Dienst auf dem Raspberry Pi ein.
Aufruf: bash tools/install_pi_service.sh [--project-dir BASISORDNER] [--host 127.0.0.1|0.0.0.0] [--apply]
Ohne --apply wird nur die geplante Unit gezeigt. --apply installiert sie mit
sudo, aktiviert den Autostart und startet die WebUI sofort neu.
--print-unit gibt nur die Unit auf stdout aus; interner Renderer für den Upload,
ohne Installation, Paketprüfung oder sudo.
EOF
}
die() {
printf 'Fehler: %s\n' "$*" >&2
exit 2
}
while (($#)); do
case $1 in
--project-dir|--host)
(($# >= 2)) || die "Wert für $1 fehlt"
if [[ $1 == --project-dir ]]; then project_dir=$2; else bind_host=$2; fi
shift 2 ;;
--print-unit) print_unit=true; shift ;;
--apply) apply=true; shift ;;
-h|--help) usage; exit 0 ;;
*) die "Unbekanntes Argument: $1" ;;
esac
done
service_user=$(id -un)
[[ $service_user =~ ^[a-zA-Z_][a-zA-Z0-9_-]*$ ]] || die 'Ungeeigneter Benutzername für systemd'
[[ $service_user != root ]] || die 'Skript als normaler Pi-Benutzer ausführen, nicht als root'
[[ $project_dir =~ ^/[a-zA-Z0-9_./-]+$ && $project_dir != / ]] || die 'Projektpfad enthält für die Unit ungeeignete Zeichen'
[[ /$project_dir/ != *'/../'* && /$project_dir/ != *'/./'* ]] || die 'Projektpfad enthält . oder .. Segmente'
[[ $bind_host == 127.0.0.1 || $bind_host == 0.0.0.0 ]] || die 'Ungültige Bind-Adresse'
code_dir=$project_dir
if [[ -L $project_dir/current ]]; then code_dir=$project_dir/current; fi
unit_contents=$(cat <<EOF
# Managed by Halloween project: tools/install_pi_service.sh
[Unit]
Description=Halloween-Grab WebUI und Kamera
After=network.target
[Service]
Type=simple
User=$service_user
WorkingDirectory=$code_dir
ExecStart=$code_dir/.venv/bin/python -m uvicorn halloween.web:create_app --factory --host $bind_host --port 8000
Environment=HALLOWEEN_STATE_DIR=$project_dir/data
Environment=PYTHONUNBUFFERED=1
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
)
if $print_unit; then
$apply && die '--print-unit kann nicht mit --apply kombiniert werden'
printf '%s\n' "$unit_contents"
exit 0
fi
printf 'Dienst: %s\nBenutzer: %s\nProjekt: %s\n' "$service_name" "$service_user" "$project_dir"
if ! $apply; then
printf '\nVorschau für %s:\n%s\n' "$unit_path" "$unit_contents"
printf '\nMit --apply installieren, aktivieren und starten.\n'
exit 0
fi
[[ -f "$code_dir/run.py" ]] || die 'run.py fehlt im Projektordner'
[[ -x "$code_dir/.venv/bin/python" ]] || die '.venv fehlt; zuerst SOFTWARE.md: Pi vorbereiten ausführen'
"$code_dir/.venv/bin/python" -c 'import fastapi, uvicorn, PIL' \
|| die 'Python-Abhängigkeiten fehlen; zuerst requirements.txt installieren'
command -v sudo >/dev/null || die 'sudo fehlt'
command -v systemctl >/dev/null || die 'systemctl fehlt'
if sudo test -e "$unit_path" &&
! sudo grep -qF '# Managed by Halloween project: tools/install_pi_service.sh' "$unit_path"; then
die "Vorhandene fremde Unit wird nicht überschrieben: $unit_path"
fi
temp_unit=$(mktemp)
trap 'rm -f -- "$temp_unit"' EXIT
printf '%s\n' "$unit_contents" > "$temp_unit"
sudo install -m 0644 -- "$temp_unit" "$unit_path"
sudo systemctl daemon-reload
sudo systemctl enable "$service_name"
sudo systemctl restart "$service_name"
if ! systemctl is-active --quiet "$service_name"; then
die "Dienst läuft nicht; Diagnose: sudo journalctl -u $service_name -n 80 --no-pager"
fi
printf 'Autostart aktiviert und Dienst gestartet. Status: systemctl status %s\n' "$service_name"