feat(selfhost): add persistent host update service
This commit is contained in:
93
scripts/selfhost-update-service-install.sh
Normal file
93
scripts/selfhost-update-service-install.sh
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/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"
|
||||
Reference in New Issue
Block a user