feat: establish secure update foundation
Container-Image veröffentlichen / container (push) v1.4.0 wurde manuell veröffentlicht und verifiziert

This commit is contained in:
admin committed 2026-08-29 11:30:43 +02:00
1 parent f02ec9977c
commit bb60c06c0e
28 files changed
+1403 -280

No files matched your search

+23 -40
View File
@@ -23,54 +23,35 @@ Beim ersten Start werden Datenbank und Verschlüsselungsschlüssel automatisch i
## Das Compose
```yaml
x-domain-control-image: &domain-control-image
image: git.j-mahler.de/admin/domain-control-webinterface-prod:v1.3.0
services:
dyndns:
<<: *domain-control-image
image: &image git.j-mahler.de/admin/domain-control-webinterface-prod:v1.4.0
restart: unless-stopped
ports:
- "9090:8080"
volumes:
- dyndns-data:/data
- dyndns-secrets:/secrets
- dyndns-update-control:/run/domain-control-update
labels:
de.j-mahler.domain-control.role: app
stop_grace_period: 20s
ports: ["9090:8080"]
volumes: ["dyndns-data:/data", "dyndns-secrets:/secrets", "dyndns-update-control:/run/domain-control-update"]
labels: {de.j-mahler.domain-control.role: app}
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs: ["/tmp:rw,noexec,nosuid,nodev,size=16m,mode=1777"]
security_opt: [no-new-privileges:true]
cap_drop: [ALL]
update-agent:
<<: *domain-control-image
image: *image
restart: unless-stopped
user: "0:10001"
entrypoint: [/usr/local/bin/update-agent]
healthcheck:
disable: true
command: [update-agent]
healthcheck: {disable: true}
network_mode: none
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- dyndns-update-control:/run/domain-control-update
volumes: ["/var/run/docker.sock:/var/run/docker.sock", "dyndns-update-control:/run/domain-control-update"]
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=4m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
tmpfs: ["/tmp:rw,noexec,nosuid,nodev,size=4m,mode=1777"]
security_opt: [no-new-privileges:true]
cap_drop: [ALL]
pids_limit: 32
mem_limit: 64m
volumes:
dyndns-data:
dyndns-secrets:
dyndns-update-control:
volumes: {dyndns-data: {}, dyndns-secrets: {}, dyndns-update-control: {}}
```
Soll ein anderer Port verwendet werden, muss nur die linke Zahl geändert werden.
@@ -80,18 +61,20 @@ Soll ein anderer Port verwendet werden, muss nur die linke Zahl geändert werden
Eine vollständige Schritt-für-Schritt-Anleitung für WebUI, Docker Compose,
Portainer/Dockge und Rollbacks steht in [`UPDATE.md`](UPDATE.md).
Administratoren können im Menü **Update** nach stabilen Releases suchen und eine neue Version direkt installieren. Der Gitea-Benutzername und Token werden einmalig in der Weboberfläche hinterlegt; der Token wird mit dem Master-Key verschlüsselt gespeichert und weder an Templates noch an den Browser zurückgegeben.
Administratoren können im Menü **Update** nach stabilen Releases suchen und eine neue Version direkt installieren. Dabei dürfen auch mehrere Versionen bis hin zu einem kompatiblen Major-Release übersprungen werden. Vor dem Neustart prüft der Agent Versionsgrenze, Agent-Protokoll, Datenbankvertrag, Laufzeitkonfiguration und Healthcheck des Ziel-Images. Der Gitea-Benutzername und Token werden einmalig in der Weboberfläche hinterlegt; der Token wird mit dem Master-Key verschlüsselt gespeichert und weder an Templates noch an den Browser zurückgegeben.
`v1.4.0` ist die sichere Basis für diesen Update-Weg. Eine ältere Installation wird einmalig mit Docker Compose auf `v1.4.0` gebracht; danach kann der Agent auch spätere kompatible Releases direkt installieren. Web-App und Agent dürfen dabei unterschiedliche Versionsnummern haben: Maßgeblich ist das ausdrücklich ausgewiesene Protokoll. Verlangt ein Ziel-Image einen neueren Vertrag, bleibt der laufende Container unverändert und die Oberfläche fordert ein Compose-Update an.
Nur der separate, netzwerklose `update-agent` erhält Zugriff auf den Docker-Socket. Er kann ausschließlich das fest konfigurierte Anwendungs-Image ersetzen: Das neue Image wird vollständig geladen, bevor der laufende Container beendet wird, und erst nach erfolgreichem Healthcheck übernommen. Bei einem Fehler wird die vorherige Instanz automatisch wiederhergestellt. Ist Gitea oder die Registry nicht erreichbar, läuft der DNS-Updater unabhängig weiter und die zuletzt bekannte Update-Benachrichtigung bleibt erhalten.
Alternativ lässt sich die Version weiterhin vollständig über Docker Compose aktualisieren. Dafür nur die eine gepinnte Image-Zeile unter `x-domain-control-image` ändern; Web-App und Update-Agent verwenden dadurch garantiert dieselbe Version. Anschließend:
Alternativ lässt sich die Version weiterhin vollständig über Docker Compose aktualisieren. Dafür nur den konkret gepinnten Tag in der mit `&image` markierten Image-Zeile ändern; Web-App und Update-Agent verwenden dadurch garantiert dieselbe Version. Anschließend:
```bash
docker compose pull
docker compose up -d
docker compose up -d --force-recreate
```
Compose ersetzt geänderte Container und bindet die vorhandenen benannten Volumes wieder ein. `docker compose down -v` darf nicht verwendet werden, weil `-v` die Daten- und Secret-Volumes löschen würde. Der Ein-Klick-Updater verändert weder `compose.yml` noch Volumes; wer später wieder per Compose aktualisiert, setzt dort einfach die gewünschte konkrete Version und führt die beiden Befehle aus.
Compose ersetzt beide Container und bindet die vorhandenen benannten Volumes wieder ein. `docker compose down -v` darf nicht verwendet werden, weil `-v` die Daten- und Secret-Volumes löschen würde. Der Ein-Klick-Updater verändert weder `compose.yml` noch Volumes. Vor einem späteren manuellen `docker compose up` muss der dort gepinnte Tag deshalb auf die gewünschte Version gesetzt werden. Meldet die WebUI **Compose aktualisieren**, unterstützt der vorhandene Agent den benötigten Vertrag nicht und beide Container werden auf diesem Weg gemeinsam aktualisiert.
Benutzer, Domains und Einstellungen bleiben in `dyndns-data`. Der Schlüssel für gespeicherte API-Tokens liegt in `dyndns-secrets`. Für ein vollständiges Backup müssen beide Volumes gesichert werden.
@@ -108,7 +91,7 @@ Benutzer, Domains und Einstellungen bleiben in `dyndns-data`. Der Schlüssel fü
- eine Pi-hole-v6-Verbindung mit verschlüsseltem Anwendungspasswort und geschützter Anzeige nach erneuter Admin-Passwortprüfung
- lokale Pi-hole-DNS-Einträge für IPv4 und IPv6 anlegen, aktivieren/deaktivieren, bearbeiten, aus der WebUI trennen, wiederherstellen und vollständig löschen
Der Container muss die verbundene Pi-hole-Adresse direkt im internen Netz erreichen können. Pi-hole-Sitzungen werden nur für die jeweilige Aktion erstellt und danach aktiv beendet; dauerhaft gespeichert wird ausschließlich das mit dem Master-Key verschlüsselte Anwendungspasswort. Der Master-Key liegt getrennt mit Dateimodus `0600`; Datenverzeichnis und SQLite-Datei werden auf `0700` beziehungsweise `0600` gehärtet. Das Compose-Setup verwendet außerdem ein schreibgeschütztes Root-Dateisystem und ein begrenztes `/tmp`-Tmpfs. HTTPS mit gültigem Zertifikat ist empfohlen.
Der Container muss die verbundene Pi-hole-Adresse direkt im internen Netz erreichen können. Pi-hole-Sitzungen werden nur für die jeweilige Aktion erstellt und danach aktiv beendet; dauerhaft gespeichert wird ausschließlich das mit dem Master-Key verschlüsselte Anwendungspasswort. Secret- und Datenverzeichnis werden auf `0700`, Master-Key und SQLite-Datei auf `0600` gehärtet. Das Compose-Setup verwendet außerdem ein schreibgeschütztes Root-Dateisystem und ein begrenztes `/tmp`-Tmpfs. HTTPS mit gültigem Zertifikat ist empfohlen.
Damit ein Pi-hole-Anwendungspasswort lokale DNS-Einträge ändern darf, muss am Pi-hole `webserver.api.app_sudo=true` gesetzt sein; Pi-hole verwendet hierfür standardmäßig `false`. Zusätzlich darf der globale Schalter `misc.readOnly` nicht aktiviert sein. Bei einem Pi-hole-Docker-Container entspricht das beispielsweise `FTLCONF_webserver_api_app_sudo: "true"` und `FTLCONF_misc_readOnly: "false"`.