216 lines
8.5 KiB
Markdown
216 lines
8.5 KiB
Markdown
# Watermaps-Produktion
|
|
|
|
Dieser Stack hostet die Watermaps-App, die lokalen Fahrrouten sowie eine
|
|
persistente PostGIS-Datenbank für Häfen, Schleusen, Brücken und Kontaktdaten in
|
|
Deutschland und den Niederlanden. Die sichtbaren Kartenkacheln bleiben externe
|
|
Dienste. Öffentlich gebunden werden ausschließlich TCP 80 und 443; App und
|
|
PostGIS sind nur im internen Docker-Netz erreichbar.
|
|
|
|
## Konfiguration
|
|
|
|
```bash
|
|
cp deploy/.env.production.example deploy/.env.production
|
|
editor deploy/.env.production
|
|
```
|
|
|
|
Mindestens `WATERMAPS_ACME_EMAIL` und `WATERMAPS_POSTGRES_PASSWORD` müssen
|
|
angepasst werden. Für das Datenbankpasswort eignet sich ein URL-unabhängiges
|
|
Hex-Secret:
|
|
|
|
```bash
|
|
openssl rand -hex 32
|
|
```
|
|
|
|
Außerdem müssen `WATERMAPS_REGISTRY` und `WATERMAPS_REGISTRY_OWNER` auf die
|
|
Gitea Container Registry zeigen. Der Hetzner-API-Token gehört **nicht** in
|
|
diese Datei. Er bleibt lokal in der ignorierten Datei
|
|
`infra/opentofu/terraform.tfvars` (alternativ kann der Provider
|
|
`TF_VAR_hcloud_token` lesen).
|
|
|
|
Die private SSH-Keydatei wird ebenfalls nicht gespeichert. Sie kann beim
|
|
Deployment mit `--identity` oder über `WATERMAPS_SSH_KEY` angegeben werden.
|
|
Falls OpenTofu keinen Output `ssh_private_key_path` bereitstellt und der Key
|
|
nicht bereits über den SSH-Agenten verfügbar ist, ist eine dieser beiden
|
|
Angaben erforderlich. Der vorbereitete SSH-Benutzer heißt standardmäßig
|
|
`deploy`; die privilegierten Installationsschritte laufen über dessen
|
|
passwortloses `sudo`.
|
|
|
|
## Gitea Actions und Container Registry
|
|
|
|
Der Workflow `.gitea/workflows/container-images.yml` führt Typechecks und Tests
|
|
aus. Nach einem erfolgreichen Push auf `main` baut und veröffentlicht er zwei
|
|
OCI-Images unter der vollständigen Git-Commit-SHA:
|
|
|
|
```text
|
|
gitea.incoso.eu/kevin_janssen/watermaps:<commit-sha>
|
|
gitea.incoso.eu/kevin_janssen/watermaps-route-data:<commit-sha>
|
|
```
|
|
|
|
In Gitea müssen Repository Actions aktiviert und ein Docker-fähiger
|
|
`ubuntu-latest`-Runner registriert sein. Unter
|
|
`Repository → Settings → Actions → Secrets` werden benötigt:
|
|
|
|
- `REGISTRY_USERNAME`: Gitea-Benutzer, dem die Packages gehören
|
|
- `REGISTRY_TOKEN`: Personal Access Token mit `package: Read and Write`
|
|
|
|
Der separate PAT ist derzeit nötig, weil Giteas eingebauter Job-Token
|
|
OCI-Pakete noch nicht zuverlässig veröffentlichen kann. Für Pull Requests
|
|
werden nur Tests ausgeführt; Registry-Secrets werden dabei nicht verwendet.
|
|
|
|
## Deployment
|
|
|
|
Nach einem erfolgreichen Image-Build liest das Skript standardmäßig den
|
|
OpenTofu-Output `server_ipv4`. Es erlaubt ausschließlich einen sauberen,
|
|
vollständig committeten Git-Stand und überträgt nur den kleinen Ordner
|
|
`deploy/` sowie das kanonische `database/schema.sql`, nicht den
|
|
Anwendungsquellcode. Die Image-Tags entsprechen exakt `git rev-parse HEAD`.
|
|
|
|
Ist die Registry privat, werden einmalig beziehungsweise nach Tokenwechsel
|
|
lokale Pull-Zugangsdaten mitgegeben:
|
|
|
|
```bash
|
|
WATERMAPS_REGISTRY_USERNAME=kevin_janssen \
|
|
WATERMAPS_REGISTRY_TOKEN='<package-read-token>' \
|
|
./deploy/scripts/upload-and-deploy.sh \
|
|
--identity ~/.ssh/watermaps_hetzner_ed25519
|
|
```
|
|
|
|
Der Token benötigt auf dem Produktionsserver nur `package: Read`. Er wird per
|
|
SSH an `docker login --password-stdin` übergeben, nicht in
|
|
`.env.production`, einem Container oder dem Image gespeichert. Docker legt die
|
|
Anmeldung root-lesbar in seiner lokalen Client-Konfiguration ab. Bei späteren
|
|
Deployments muss der Token nicht erneut angegeben werden, solange die
|
|
Anmeldung gültig ist:
|
|
|
|
```bash
|
|
./deploy/scripts/upload-and-deploy.sh \
|
|
--identity ~/.ssh/watermaps_hetzner_ed25519
|
|
```
|
|
|
|
Das Serverskript lädt App, den kombinierten Routing-/Feature-Daten-Builder,
|
|
PostGIS, Nginx und Certbot mit `docker compose pull`. Die beiden commitgenauen
|
|
Gitea-Tags werden anschließend in ihre unveränderlichen Registry-Digests
|
|
aufgelöst. Erst danach werden die Container mit `--no-build` gestartet. Der
|
|
Produktionsserver benötigt deshalb weder Git noch Node/npm oder den Quellcode.
|
|
|
|
Der erste Datenaufbau lädt die Geofabrik-Extrakte für Deutschland und die
|
|
Niederlande, baut den Fahrroutenindex und importiert daraus die
|
|
`marine_features`. Je nach Serverleistung kann dies längere Zeit dauern.
|
|
App und Nginx werden beim ersten Release erst gestartet, wenn sowohl der
|
|
Routingindex als auch ein nicht leerer, zur PBF-Prüfsumme passender Bestand an
|
|
Häfen, Schleusen und Brücken geprüft wurde. Ein HTTP-Healthcheck allein kann
|
|
damit keine leere Ereignisdatenbank freigeben.
|
|
|
|
Die produktiven Daten liegen auf dem persistenten Servervolume:
|
|
|
|
```text
|
|
/srv/watermaps-data/local/germany-netherlands-fairways.json
|
|
/srv/watermaps-data/local/.marine-features.ready
|
|
/srv/watermaps-data/postgres/
|
|
/srv/watermaps-data/tmp/
|
|
```
|
|
|
|
PostGIS veröffentlicht keinen Host-Port. Die App verbindet sich im privaten
|
|
Compose-Netz; das Passwort wird separat über `PGPASSWORD` übergeben und muss
|
|
nicht URL-kodiert in `DATABASE_URL` dupliziert werden. Auch die temporären,
|
|
potenziell mehrere Gigabyte großen Filter- und GeoJSON-Dateien des Imports
|
|
liegen unter `tmp/` auf diesem Volume und nicht im Docker-Overlay der
|
|
Rootdisk; nach jedem Importlauf werden sie entfernt.
|
|
|
|
Der Upload wartet zuerst auf SSH und den Abschluss von Cloud-init. Anschließend
|
|
startet und prüft er `watermaps-volume-setup.service`. Ohne tatsächlich unter
|
|
`/srv/watermaps-data` eingehängtes Volume wird kein Download gestartet, damit
|
|
die großen PBF-Dateien nicht versehentlich auf dem Root-Dateisystem landen.
|
|
Für den kurzzeitigen Speicherpeak beim kombinierten Indexaufbau richtet der
|
|
Bootstrap zusätzlich die über `WATERMAPS_SWAP_SIZE_GB` konfigurierte,
|
|
persistente Swap-Reserve ein.
|
|
|
|
Vor dem Livegang antwortet Nginx nur für ACME-Challenges. Alle anderen
|
|
HTTP-Anfragen erhalten 404.
|
|
|
|
## Rollback
|
|
|
|
Jedes erfolgreiche Release wird mit Commit-SHA und den aufgelösten
|
|
Image-Digests unter `/srv/watermaps-runtime/deployments/` gespeichert. Scheitert
|
|
ein Deployment nach dem Containerwechsel, startet `deploy.sh` automatisch das
|
|
vorherige Release und führt die Health-, Routen- und Featuretests erneut aus.
|
|
Ein Rollback wechselt nur die unveränderlichen App-/Builder-Images. Die
|
|
persistente PostGIS-Datenbank bleibt erhalten; das Schema und der Import sind
|
|
aufwärtskompatibel und idempotent.
|
|
|
|
Das unmittelbar vorherige Release lässt sich auch manuell aktivieren:
|
|
|
|
```bash
|
|
./deploy/scripts/remote-rollback.sh \
|
|
--identity ~/.ssh/watermaps_hetzner_ed25519
|
|
```
|
|
|
|
Oder ein bestimmter, bereits erfolgreich deployter Commit:
|
|
|
|
```bash
|
|
./deploy/scripts/remote-rollback.sh \
|
|
--revision 0123456789abcdef0123456789abcdef01234567 \
|
|
--identity ~/.ssh/watermaps_hetzner_ed25519
|
|
```
|
|
|
|
Der Rollback verwendet gespeicherte Digests, nicht einen beweglichen Tag.
|
|
|
|
## DNS und SSL-Livegang
|
|
|
|
Sobald der OpenTofu-Output bekannt ist, kann folgender DNS-Eintrag gesetzt
|
|
werden:
|
|
|
|
```text
|
|
A watermaps.incoso.eu <server_ipv4>
|
|
```
|
|
|
|
Nach der DNS-Propagation wird der Livegang lokal ausgelöst:
|
|
|
|
```bash
|
|
./deploy/scripts/remote-go-live.sh --identity ~/.ssh/watermaps_hetzner_ed25519
|
|
```
|
|
|
|
Das Serverskript prüft, dass sämtliche A-Records ausschließlich auf die
|
|
erwartete IPv4 zeigen, testet den Routingindex mit je einer Route in
|
|
Deutschland und den Niederlanden, prüft den ACME-Webroot, fordert das
|
|
Zertifikat an und aktiviert erst anschließend HTTPS. HTTP leitet danach auf
|
|
HTTPS um.
|
|
|
|
## Automatik
|
|
|
|
`bootstrap-server.sh` installiert zwei systemd-Timer:
|
|
|
|
- `watermaps-route-update.timer`: täglich neue Deutschland- und
|
|
Niederlande-Daten. Der Fahrroutenindex wird atomar aktiviert. Der
|
|
Feature-Importer aktualisiert beide Länder per Upsert und entfernt
|
|
verschwundene Datensätze erst, nachdem beide Importe vollständig waren;
|
|
dabei werden ausschließlich OSM-Zeilen bereinigt. EuRIS- und
|
|
Website-Anreicherungen bleiben erhalten.
|
|
- `watermaps-certbot-renew.timer`: zweimal täglich Certbot-Prüfung mit
|
|
anschließendem Nginx-Reload.
|
|
|
|
Ein vollständiger manueller Feature-Neuimport lässt sich über die
|
|
Option `--force-marine` erzwingen:
|
|
|
|
```bash
|
|
/opt/watermaps/deploy/scripts/update-route-data.sh --force-marine
|
|
```
|
|
|
|
`WATERMAPS_REBUILD_MARINE_DATA=true` in `.env.production` erzwingt den
|
|
Neuimport stattdessen beim nächsten regulären Deployment. Der systemd-Dienst
|
|
hat bewusst kein Startzeitlimit, da Download, Indexbau und Import auf kleineren
|
|
Servern deutlich länger als 90 Sekunden dauern können.
|
|
|
|
Status und Logs:
|
|
|
|
```bash
|
|
systemctl list-timers 'watermaps-*'
|
|
journalctl -u watermaps-route-update.service
|
|
journalctl -u watermaps-certbot-renew.service
|
|
docker compose \
|
|
--project-directory /opt/watermaps \
|
|
--env-file /opt/watermaps/deploy/.env.production \
|
|
--env-file /opt/watermaps/deploy/.env.images \
|
|
-f /opt/watermaps/deploy/compose.production.yml ps
|
|
```
|