Files
FEDEO/scripts/selfhost-update-service-install.sh
flfeders 7164989da9
All checks were successful
Build and Release FEDEO / verify (push) Successful in 47s
Build and Release FEDEO / build-and-release (push) Successful in 5m53s
feat(selfhost): add persistent host update service
2026-09-23 18:09:04 +02:00

94 lines
4.2 KiB
Bash

#!/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"