#!/usr/bin/env bash set -euo pipefail FEDEO_DIR=/opt/fedeo PRINT_UNIT=false SOURCE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" while [[ $# -gt 0 ]]; do case "$1" in --directory) FEDEO_DIR="${2:?Verzeichnis fehlt}"; shift 2 ;; --print-unit) PRINT_UNIT=true; shift ;; --help|-h) echo "sudo bash $0 [--directory /opt/fedeo] [--print-unit]"; exit 0 ;; *) echo "Unbekanntes Argument: $1" >&2; exit 1 ;; esac done render_unit() { python3 - "$SOURCE_DIR/fedeo-updater.service" "$FEDEO_DIR" <<'PY' import pathlib, sys directory = sys.argv[2] if not directory.startswith('/') or any(c in directory for c in '\n\r\0'): raise SystemExit('Ein gültiges absolutes Betriebsverzeichnis angeben.') quoted = '"' + directory.replace('\\', '\\\\').replace('"', '\\"').replace('%', '%%').replace('$', '$$') + '"' sys.stdout.write(pathlib.Path(sys.argv[1]).read_text().replace('@FEDEO_DIRECTORY@', quoted)) PY } if [[ "$PRINT_UNIT" == true ]]; then render_unit exit 0 fi [[ "$(id -u)" -eq 0 ]] || { echo "Als root ausführen." >&2; exit 1; } [[ -d /run/systemd/system ]] || { echo "Ein Linux-Host mit laufendem systemd wird benötigt." >&2; exit 1; } FEDEO_DIR="$(realpath -e -- "$FEDEO_DIR")" [[ -f "$FEDEO_DIR/.env" && -f "$FEDEO_DIR/docker-compose.yml" && ! -e "$FEDEO_DIR/.git" ]] || { echo "Ein bestehendes Selfhost-Betriebsverzeichnis ohne Git-Checkout angeben." >&2; exit 1; } python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else "Python ab 3.10 wird benötigt.")' for name in selfhost-update.py selfhost-update-service.py fedeo-updater.service; do [[ -f "$SOURCE_DIR/$name" ]] || { echo "Fehlende Installationsdatei: $name" >&2; exit 1; } done python3 - "$SOURCE_DIR" <<'PY' import importlib.util, inspect, pathlib, sys source = pathlib.Path(sys.argv[1]) spec = importlib.util.spec_from_file_location('fedeo_update_install_check', source / 'selfhost-update.py') engine = importlib.util.module_from_spec(spec) spec.loader.exec_module(engine) if not {'expected_fingerprint', 'request_id'}.issubset(inspect.signature(engine.Updater.execute).parameters): raise SystemExit('Updater und Dienst passen nicht zusammen. Alle Dateien aus demselben Stand bereitstellen.') compile((source / 'selfhost-update-service.py').read_text(), 'selfhost-update-service.py', 'exec') PY command -v flock >/dev/null command -v docker >/dev/null command -v curl >/dev/null docker compose version >/dev/null UNIT_CONTENT="$(render_unit)" # Drain an active service job before replacing either Python module. No containers are restarted here. if systemctl is-active --quiet fedeo-updater.service; then systemctl stop fedeo-updater.service fi [[ ! -L "$FEDEO_DIR/.updates" ]] || { echo ".updates darf kein symbolischer Link sein." >&2; exit 1; } install -d -m 0700 "$FEDEO_DIR/.updates" exec 9>"$FEDEO_DIR/.updates/lock" flock -n 9 || { echo "Ein CLI-Update läuft noch. Dienstinstallation später wiederholen." >&2; exit 1; } getent group fedeo-updater >/dev/null || groupadd --system fedeo-updater install -d -o root -g root -m 0700 /usr/local/lib/fedeo-updater /etc/fedeo install -o root -g root -m 0700 "$SOURCE_DIR/selfhost-update.py" /usr/local/lib/fedeo-updater/selfhost-update.py install -o root -g root -m 0700 "$SOURCE_DIR/selfhost-update-service.py" /usr/local/lib/fedeo-updater/selfhost-update-service.py if [[ ! -e /etc/fedeo/updater.env ]]; then (umask 077; printf '%s\n' '# Optional: FEDEO_RELEASE_TOKEN=... (nur Leserechte)' > /etc/fedeo/updater.env) fi chmod 0600 /etc/fedeo/updater.env UNIT_TMP="$(mktemp /etc/systemd/system/fedeo-updater.service.XXXXXX)" trap 'rm -f -- "$UNIT_TMP"' EXIT printf '%s\n' "$UNIT_CONTENT" > "$UNIT_TMP" chmod 0644 "$UNIT_TMP" mv -- "$UNIT_TMP" /etc/systemd/system/fedeo-updater.service flock -u 9 exec 9>&- systemctl daemon-reload systemctl enable --now fedeo-updater.service READY=false for attempt in {1..50}; do if curl --silent --fail --max-time 1 --unix-socket /run/fedeo-updater/control.sock http://localhost/v1/status >/dev/null; then READY=true break fi sleep 0.1 done systemctl --no-pager --full status fedeo-updater.service [[ "$READY" == true ]] || { echo "Dienstschnittstelle noch nicht erreichbar; journalctl -u fedeo-updater.service prüfen." >&2; exit 1; } echo "Lokale Schnittstelle: /run/fedeo-updater/control.sock"