Reorganised deployment scripts and added rollback functionality. Updated documentation and workflow for container image builds.
Test and publish container images / test (push) Successful in 2m50s
Test and publish container images / publish (push) Failing after 54s

This commit is contained in:
BuTzZ
2026-07-25 12:36:35 +02:00
parent 12eee8d211
commit c9a20aacd7
17 changed files with 642 additions and 73 deletions
+89
View File
@@ -0,0 +1,89 @@
name: Test and publish container images
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
packages: none
env:
REGISTRY: gitea.incoso.eu
REGISTRY_OWNER: kevin_janssen
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:22-bookworm
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Install test dependencies
run: |
apt-get update
apt-get install --yes --no-install-recommends jq python3 python3-pip util-linux
rm -rf /var/lib/apt/lists/*
python3 -m pip install \
--disable-pip-version-check \
--target .tools/python \
"osmium==4.3.1"
- name: Install Node dependencies
run: npm ci
- name: Run type checks and tests
run: |
npm run typecheck
npm test
publish:
if: ${{ gitea.event_name == 'push' }}
needs:
- test
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Log in to the Gitea Container Registry
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
test -n "$REGISTRY_USERNAME"
test -n "$REGISTRY_TOKEN"
printf '%s\n' "$REGISTRY_TOKEN" |
docker login "$REGISTRY" --username "$REGISTRY_USERNAME" --password-stdin
- name: Build images for the triggering commit
env:
REVISION: ${{ gitea.sha }}
run: |
test "$(printf '%s' "$REVISION" | wc -c)" -eq 40
docker build \
--label "org.opencontainers.image.revision=$REVISION" \
--label "org.opencontainers.image.source=${{ gitea.server_url }}/${{ gitea.repository }}" \
--tag "$REGISTRY/$REGISTRY_OWNER/watermaps:$REVISION" \
.
docker build \
--file deploy/route-data.Dockerfile \
--label "org.opencontainers.image.revision=$REVISION" \
--label "org.opencontainers.image.source=${{ gitea.server_url }}/${{ gitea.repository }}" \
--tag "$REGISTRY/$REGISTRY_OWNER/watermaps-route-data:$REVISION" \
.
- name: Push immutable commit images
env:
REVISION: ${{ gitea.sha }}
run: |
docker push "$REGISTRY/$REGISTRY_OWNER/watermaps:$REVISION"
docker push "$REGISTRY/$REGISTRY_OWNER/watermaps-route-data:$REVISION"
- name: Log out from the registry
if: always()
run: docker logout "$REGISTRY" || true
+23 -8
View File
@@ -68,9 +68,17 @@ Die produktive Infrastruktur besteht aus einem Hetzner-Server, einer festen
IPv4, Firewall und einem persistenten Volume. Der Docker-Stack enthält nur die IPv4, Firewall und einem persistenten Volume. Der Docker-Stack enthält nur die
App, Nginx/Certbot und den Wartungscontainer für Deutschland- und App, Nginx/Certbot und den Wartungscontainer für Deutschland- und
Niederlande-Routendaten; Kartenkacheln werden nicht selbst gehostet. Niederlande-Routendaten; Kartenkacheln werden nicht selbst gehostet.
Gitea Actions testet jeden Stand und veröffentlicht App und
Routingdaten-Builder für Commits auf `main` als Container-Images. Der
Produktionsserver klont keinen Quellcode und baut keine Anwendung.
Die beiden lokalen, von Git ignorierten Konfigurationsdateien sind bereits Die beiden lokalen, von Git ignorierten Konfigurationsdateien werden aus ihren
angelegt: Vorlagen angelegt:
```bash
cp infra/opentofu/terraform.tfvars.example infra/opentofu/terraform.tfvars
cp deploy/.env.production.example deploy/.env.production
```
- `infra/opentofu/terraform.tfvars`: hier den Hetzner-Cloud-Read/Write-Token - `infra/opentofu/terraform.tfvars`: hier den Hetzner-Cloud-Read/Write-Token
eintragen und `admin_cidrs` bei Bedarf auf die aktuelle öffentliche IP eintragen und `admin_cidrs` bei Bedarf auf die aktuelle öffentliche IP
@@ -89,19 +97,26 @@ tofu output server_ipv4
cd ../.. cd ../..
``` ```
Sobald `server_ipv4` ausgegeben wurde, kann der manuelle DNS-A-Record Vor dem ersten Deployment müssen Gitea Actions, ein Docker-fähiger Runner sowie
die Action-Secrets `REGISTRY_USERNAME` und `REGISTRY_TOKEN` eingerichtet sein.
Der Token benötigt Schreibzugriff auf Packages. Sobald `server_ipv4` ausgegeben
wurde, kann der manuelle DNS-A-Record
`watermaps.incoso.eu` auf diese IPv4 gesetzt werden. Das erste Deployment darf `watermaps.incoso.eu` auf diese IPv4 gesetzt werden. Das erste Deployment darf
bereits vor der DNS-Propagation laufen: bereits vor der DNS-Propagation laufen, sobald die Action die Images des
aktuellen Commits veröffentlicht hat:
```bash ```bash
WATERMAPS_REGISTRY_USERNAME=kevin_janssen \
WATERMAPS_REGISTRY_TOKEN='<read-only-package-token>' \
./deploy/scripts/upload-and-deploy.sh \ ./deploy/scripts/upload-and-deploy.sh \
--identity ~/.ssh/watermaps_hetzner_ed25519 --identity ~/.ssh/watermaps_hetzner_ed25519
``` ```
Dabei werden die vollständigen Geofabrik-Extrakte für Deutschland und die Der Registry-Token wird nur an `docker login --password-stdin` übertragen und
Niederlande auf dem persistenten Server-Volume geladen und der lokale nicht Teil der Projektkonfiguration. Das Skript überträgt ausschließlich die
Routingindex erstellt. Vor dem SSL-Livegang liefert Port 80 außer Deployment-Dateien. Der Server zieht die commitgenauen Images, fixiert deren
ACME-Challenges nur 404. Digests und lädt beim ersten Start die Geofabrik-Extrakte auf das persistente
Volume. Vor dem SSL-Livegang liefert Port 80 außer ACME-Challenges nur 404.
Erst wenn der DNS-A-Record propagiert ist, wird HTTPS mit dem finalen manuellen Erst wenn der DNS-A-Record propagiert ist, wird HTTPS mit dem finalen manuellen
Befehl aktiviert: Befehl aktiviert:
+5 -2
View File
@@ -17,10 +17,13 @@ WATERMAPS_RUNTIME_DIR=/srv/watermaps-runtime
# Verhindert Speicherabbrüche beim vollständigen DE/NL-Indexaufbau auf 4-GB-Servern. # Verhindert Speicherabbrüche beim vollständigen DE/NL-Indexaufbau auf 4-GB-Servern.
WATERMAPS_SWAP_SIZE_GB=4 WATERMAPS_SWAP_SIZE_GB=4
WATERMAPS_APP_IMAGE=watermaps:production # Gitea Container Registry. Die commitgenauen App-Referenzen werden beim
# Deployment separat erzeugt und niemals hier von Hand auf `latest` gesetzt.
WATERMAPS_REGISTRY=gitea.incoso.eu
WATERMAPS_REGISTRY_OWNER=kevin_janssen
WATERMAPS_NGINX_IMAGE=nginx:1.30.4-alpine WATERMAPS_NGINX_IMAGE=nginx:1.30.4-alpine
WATERMAPS_CERTBOT_IMAGE=certbot/certbot:v5.7.0 WATERMAPS_CERTBOT_IMAGE=certbot/certbot:v5.7.0
WATERMAPS_ROUTE_DATA_IMAGE=watermaps-route-data:production
# Nur auf true setzen, wenn bei jedem Deployment Deutschland und die # Nur auf true setzen, wenn bei jedem Deployment Deutschland und die
# Niederlande erneut geprüft und der Routingindex neu gebaut werden sollen. # Niederlande erneut geprüft und der Routingindex neu gebaut werden sollen.
+2
View File
@@ -1,2 +1,4 @@
.env.production .env.production
.env.images
.env.images.candidate
!.env.production.example !.env.production.example
+78 -13
View File
@@ -12,11 +12,11 @@ cp deploy/.env.production.example deploy/.env.production
editor deploy/.env.production editor deploy/.env.production
``` ```
Mindestens `WATERMAPS_ACME_EMAIL` muss angepasst werden. Der Hetzner-API-Token Mindestens `WATERMAPS_ACME_EMAIL` muss angepasst werden. Außerdem müssen
gehört **nicht** in diese Datei. Er bleibt lokal in der ignorierten Datei `WATERMAPS_REGISTRY` und `WATERMAPS_REGISTRY_OWNER` auf die Gitea Container
`infra/opentofu/terraform.tfvars` (alternativ kann der Provider Registry zeigen. Der Hetzner-API-Token gehört **nicht** in diese Datei. Er
`TF_VAR_hcloud_token` lesen). Der Upload schließt Terraform-Variablen, Pläne, bleibt lokal in der ignorierten Datei `infra/opentofu/terraform.tfvars`
State, lokale Env-Dateien und `.terraform` in jeder Verzeichnistiefe aus. (alternativ kann der Provider `TF_VAR_hcloud_token` lesen).
Die private SSH-Keydatei wird ebenfalls nicht gespeichert. Sie kann beim Die private SSH-Keydatei wird ebenfalls nicht gespeichert. Sie kann beim
Deployment mit `--identity` oder über `WATERMAPS_SSH_KEY` angegeben werden. Deployment mit `--identity` oder über `WATERMAPS_SSH_KEY` angegeben werden.
@@ -26,24 +26,64 @@ Angaben erforderlich. Der vorbereitete SSH-Benutzer heißt standardmäßig
`deploy`; die privilegierten Installationsschritte laufen über dessen `deploy`; die privilegierten Installationsschritte laufen über dessen
passwortloses `sudo`. 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 ## Deployment
Nach `tofu apply` liest dieses Skript standardmäßig den Output `server_ipv4`, Nach einem erfolgreichen Image-Build liest das Skript standardmäßig den
überträgt das Projekt ohne State, lokale Daten oder Node-Module und startet den OpenTofu-Output `server_ipv4`. Es erlaubt ausschließlich einen sauberen,
Serverbootstrap sowie den Docker-Stack: vollständig committeten Git-Stand und überträgt nur den kleinen Ordner
`deploy/`, 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 ```bash
./deploy/scripts/upload-and-deploy.sh --identity ~/.ssh/watermaps_hetzner_ed25519 WATERMAPS_REGISTRY_USERNAME=kevin_janssen \
WATERMAPS_REGISTRY_TOKEN='<package-read-token>' \
./deploy/scripts/upload-and-deploy.sh \
--identity ~/.ssh/watermaps_hetzner_ed25519
``` ```
Alternativ: 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 ```bash
WATERMAPS_SERVER_IPV4=203.0.113.10 \ ./deploy/scripts/upload-and-deploy.sh \
WATERMAPS_SSH_KEY=~/.ssh/watermaps_hetzner_ed25519 \ --identity ~/.ssh/watermaps_hetzner_ed25519
./deploy/scripts/upload-and-deploy.sh
``` ```
Das Serverskript lädt App, Routingdaten-Builder, 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 Der erste Datenaufbau lädt die Geofabrik-Extrakte für Deutschland und die
Niederlande herunter und kann entsprechend der Serverleistung längere Zeit Niederlande herunter und kann entsprechend der Serverleistung längere Zeit
dauern. Der produktive Index liegt auf dem Server unter: dauern. Der produktive Index liegt auf dem Server unter:
@@ -63,6 +103,30 @@ persistente Swap-Reserve ein.
Vor dem Livegang antwortet Nginx nur für ACME-Challenges. Alle anderen Vor dem Livegang antwortet Nginx nur für ACME-Challenges. Alle anderen
HTTP-Anfragen erhalten 404. 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- und Routentests erneut aus.
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 ## DNS und SSL-Livegang
Sobald der OpenTofu-Output bekannt ist, kann folgender DNS-Eintrag gesetzt Sobald der OpenTofu-Output bekannt ist, kann folgender DNS-Eintrag gesetzt
@@ -103,5 +167,6 @@ journalctl -u watermaps-certbot-renew.service
docker compose \ docker compose \
--project-directory /opt/watermaps \ --project-directory /opt/watermaps \
--env-file /opt/watermaps/deploy/.env.production \ --env-file /opt/watermaps/deploy/.env.production \
--env-file /opt/watermaps/deploy/.env.images \
-f /opt/watermaps/deploy/compose.production.yml ps -f /opt/watermaps/deploy/compose.production.yml ps
``` ```
+2 -8
View File
@@ -2,10 +2,7 @@ name: watermaps-production
services: services:
watermaps: watermaps:
image: ${WATERMAPS_APP_IMAGE:-watermaps:production} image: ${WATERMAPS_APP_IMAGE:?WATERMAPS_APP_IMAGE fehlt}
build:
context: .
dockerfile: Dockerfile
environment: environment:
NODE_ENV: production NODE_ENV: production
HOST: 0.0.0.0 HOST: 0.0.0.0
@@ -85,10 +82,7 @@ services:
target: /etc/letsencrypt target: /etc/letsencrypt
route-data: route-data:
image: ${WATERMAPS_ROUTE_DATA_IMAGE:-watermaps-route-data:production} image: ${WATERMAPS_ROUTE_DATA_IMAGE:?WATERMAPS_ROUTE_DATA_IMAGE fehlt}
build:
context: .
dockerfile: deploy/route-data.Dockerfile
profiles: ["maintenance"] profiles: ["maintenance"]
environment: environment:
WATERMAPS_GEOFABRIK_DIR: /workspace/data/geofabrik WATERMAPS_GEOFABRIK_DIR: /workspace/data/geofabrik
+100
View File
@@ -5,6 +5,7 @@ set -Eeuo pipefail
WM_DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" WM_DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WM_ROOT_DIR="$(cd "$WM_DEPLOY_DIR/.." && pwd)" WM_ROOT_DIR="$(cd "$WM_DEPLOY_DIR/.." && pwd)"
WM_ENV_FILE="${WATERMAPS_ENV_FILE:-$WM_DEPLOY_DIR/.env.production}" WM_ENV_FILE="${WATERMAPS_ENV_FILE:-$WM_DEPLOY_DIR/.env.production}"
WM_IMAGES_ENV_FILE="${WATERMAPS_IMAGES_ENV_FILE:-$WM_DEPLOY_DIR/.env.images}"
WM_COMPOSE_FILE="$WM_DEPLOY_DIR/compose.production.yml" WM_COMPOSE_FILE="$WM_DEPLOY_DIR/compose.production.yml"
wm_die() { wm_die() {
@@ -47,13 +48,112 @@ wm_assert_data_mount() {
} }
wm_compose() { wm_compose() {
wm_validate_images_env "$WM_IMAGES_ENV_FILE"
docker compose \ docker compose \
--project-directory "$WM_ROOT_DIR" \ --project-directory "$WM_ROOT_DIR" \
--env-file "$WM_ENV_FILE" \ --env-file "$WM_ENV_FILE" \
--env-file "$WM_IMAGES_ENV_FILE" \
--file "$WM_COMPOSE_FILE" \ --file "$WM_COMPOSE_FILE" \
"$@" "$@"
} }
wm_use_images_env() {
local images_env_file="$1"
wm_validate_images_env "$images_env_file"
WM_IMAGES_ENV_FILE="$images_env_file"
export WATERMAPS_IMAGES_ENV_FILE="$images_env_file"
}
wm_validate_images_env() {
local images_env_file="$1"
local revision app_image route_data_image
[[ -f "$images_env_file" ]] ||
wm_die "Image-Konfiguration fehlt: $images_env_file"
revision="$(wm_env_value "$images_env_file" WATERMAPS_DEPLOY_REVISION)"
app_image="$(wm_env_value "$images_env_file" WATERMAPS_APP_IMAGE)"
route_data_image="$(wm_env_value "$images_env_file" WATERMAPS_ROUTE_DATA_IMAGE)"
[[ "$revision" =~ ^[0-9a-f]{40}$ ]] ||
wm_die "WATERMAPS_DEPLOY_REVISION muss eine vollständige Git-Commit-SHA sein."
wm_validate_image_reference "$app_image" WATERMAPS_APP_IMAGE
wm_validate_image_reference "$route_data_image" WATERMAPS_ROUTE_DATA_IMAGE
wm_validate_release_image "$app_image" "$revision" WATERMAPS_APP_IMAGE
wm_validate_release_image \
"$route_data_image" \
"$revision" \
WATERMAPS_ROUTE_DATA_IMAGE
}
wm_env_value() {
local env_file="$1"
local requested_key="$2"
local value
value="$(
awk -F= -v requested_key="$requested_key" '
$1 == requested_key {
print substr($0, length($1) + 2)
matches += 1
}
END {
if (matches != 1) {
exit 1
}
}
' "$env_file"
)" || wm_die "$requested_key fehlt oder ist mehrfach in $env_file vorhanden."
printf '%s\n' "$value"
}
wm_validate_image_reference() {
local image_reference="$1"
local variable_name="$2"
[[ -n "$image_reference" &&
"$image_reference" != *[[:space:]]* &&
"$image_reference" == */* &&
"$image_reference" != *REPLACE* ]] ||
wm_die "$variable_name enthält keine gültige Registry-Image-Referenz."
}
wm_validate_release_image() {
local image_reference="$1"
local revision="$2"
local variable_name="$3"
[[ "$image_reference" == *":$revision" ||
"$image_reference" =~ @sha256:[0-9a-f]{64}$ ]] ||
wm_die "$variable_name muss auf den Commit-Tag $revision oder einen SHA256-Digest zeigen."
}
wm_resolve_image_digest() {
local image_reference="$1"
local requested_repository last_component resolved_reference
requested_repository="${image_reference%%@*}"
last_component="${requested_repository##*/}"
if [[ "$last_component" == *:* ]]; then
requested_repository="${requested_repository%:*}"
fi
resolved_reference="$(
docker image inspect \
--format '{{range .RepoDigests}}{{println .}}{{end}}' \
"$image_reference" |
awk -v requested_repository="$requested_repository" '
index($0, requested_repository "@sha256:") == 1 {
print
exit
}
'
)"
[[ "$resolved_reference" =~ @sha256:[0-9a-f]{64}$ ]] ||
wm_die "Für $image_reference konnte kein lokaler Registry-Digest ermittelt werden."
printf '%s\n' "$resolved_reference"
}
wm_route_file() { wm_route_file() {
printf '%s/local/germany-netherlands-fairways.json\n' "$WATERMAPS_DATA_DIR" printf '%s/local/germany-netherlands-fairways.json\n' "$WATERMAPS_DATA_DIR"
} }
+103 -9
View File
@@ -5,12 +5,35 @@ set -Eeuo pipefail
WM_DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" WM_DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=common.sh # shellcheck source=common.sh
source "$WM_DEPLOY_DIR/scripts/common.sh" source "$WM_DEPLOY_DIR/scripts/common.sh"
images_env_file="$WM_IMAGES_ENV_FILE"
usage() {
printf 'Verwendung: %s [--images-file DATEI]\n' "$0"
}
while [[ "$#" -gt 0 ]]; do
case "$1" in
--images-file)
[[ "$#" -ge 2 ]] || wm_die "Wert für --images-file fehlt."
images_env_file="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
wm_die "Unbekannte Option: $1"
;;
esac
done
[[ "$(id -u)" -eq 0 ]] ||
wm_die "Dieses Skript muss als root ausgeführt werden."
wm_load_env wm_load_env
wm_assert_data_mount wm_assert_data_mount
wm_use_images_env "$images_env_file"
if [[ "$(id -u)" -ne 0 ]]; then
wm_die "Dieses Skript muss als root ausgeführt werden."
fi
install -d -m 0755 \ install -d -m 0755 \
"$WATERMAPS_DATA_DIR/geofabrik" \ "$WATERMAPS_DATA_DIR/geofabrik" \
@@ -18,20 +41,81 @@ install -d -m 0755 \
"$WATERMAPS_DATA_DIR/certbot/www" \ "$WATERMAPS_DATA_DIR/certbot/www" \
"$WATERMAPS_DATA_DIR/certbot/letsencrypt" \ "$WATERMAPS_DATA_DIR/certbot/letsencrypt" \
"$WATERMAPS_RUNTIME_DIR/nginx/conf.d" \ "$WATERMAPS_RUNTIME_DIR/nginx/conf.d" \
"$WATERMAPS_RUNTIME_DIR/locks" "$WATERMAPS_RUNTIME_DIR/locks" \
"$WATERMAPS_RUNTIME_DIR/deployments"
wm_acquire_route_lock || wm_acquire_route_lock ||
wm_die "Deployment abgebrochen, weil gerade Routingdaten aktualisiert werden." wm_die "Deployment abgebrochen, weil gerade Routingdaten aktualisiert werden."
active_images_file="$WM_DEPLOY_DIR/.env.images"
rollback_images_file=""
resolved_images_file=""
deployment_complete=false
if [[ -f "$active_images_file" ]]; then
wm_validate_images_env "$active_images_file"
rollback_images_file="$(
mktemp "$WATERMAPS_RUNTIME_DIR/deployments/.rollback-images.XXXXXX"
)"
install -m 0644 "$active_images_file" "$rollback_images_file"
fi
wm_finish_deployment() {
local status=$?
trap - EXIT HUP INT TERM
set +e
if [[ "$status" -ne 0 && "$deployment_complete" != "true" ]]; then
if [[ -n "$rollback_images_file" && -f "$rollback_images_file" ]]; then
wm_log "Deployment fehlgeschlagen; vorheriges Container-Release wird wiederhergestellt."
wm_use_images_env "$rollback_images_file"
wm_compose up --detach --no-build --remove-orphans watermaps nginx
wm_wait_for_health watermaps 240
wm_wait_for_health nginx 120
if wm_route_data_ready; then
wm_smoke_test_route
fi
else
wm_log "Deployment fehlgeschlagen; für das erste Release existiert noch kein Rollback."
fi
fi
[[ -z "$resolved_images_file" ]] || rm -f "$resolved_images_file"
[[ -z "$rollback_images_file" ]] || rm -f "$rollback_images_file"
exit "$status"
}
trap wm_finish_deployment EXIT
trap 'exit 130' HUP INT TERM
if [[ ! -f "$WATERMAPS_RUNTIME_DIR/nginx/conf.d/default.conf" ]]; then if [[ ! -f "$WATERMAPS_RUNTIME_DIR/nginx/conf.d/default.conf" ]]; then
install -m 0644 \ install -m 0644 \
"$WM_DEPLOY_DIR/nginx/bootstrap.conf" \ "$WM_DEPLOY_DIR/nginx/bootstrap.conf" \
"$WATERMAPS_RUNTIME_DIR/nginx/conf.d/default.conf" "$WATERMAPS_RUNTIME_DIR/nginx/conf.d/default.conf"
fi fi
wm_log "Anwendungsimage wird gebaut." revision="$(wm_env_value "$images_env_file" WATERMAPS_DEPLOY_REVISION)"
wm_compose build watermaps requested_app_image="$(wm_env_value "$images_env_file" WATERMAPS_APP_IMAGE)"
wm_compose up --detach --remove-orphans watermaps nginx requested_route_data_image="$(wm_env_value "$images_env_file" WATERMAPS_ROUTE_DATA_IMAGE)"
wm_log "Container-Images für Commit $revision werden aus den Registries geladen."
wm_compose --profile maintenance pull watermaps nginx certbot route-data
resolved_app_image="$(wm_resolve_image_digest "$requested_app_image")"
resolved_route_data_image="$(wm_resolve_image_digest "$requested_route_data_image")"
resolved_images_file="$(
mktemp "$WATERMAPS_RUNTIME_DIR/deployments/.resolved-images.XXXXXX"
)"
{
printf 'WATERMAPS_DEPLOY_REVISION=%s\n' "$revision"
printf 'WATERMAPS_APP_IMAGE=%s\n' "$resolved_app_image"
printf 'WATERMAPS_ROUTE_DATA_IMAGE=%s\n' "$resolved_route_data_image"
} >"$resolved_images_file"
chmod 0644 "$resolved_images_file"
wm_use_images_env "$resolved_images_file"
wm_log "Commit $revision wird mit unveränderlichen Image-Digests gestartet."
wm_compose up --detach --no-build --remove-orphans watermaps nginx
wm_wait_for_health watermaps 240 wm_wait_for_health watermaps 240
wm_wait_for_health nginx 120 wm_wait_for_health nginx 120
@@ -55,4 +139,14 @@ wm_assert_route_data
wm_wait_for_health watermaps 240 wm_wait_for_health watermaps 240
wm_smoke_test_route wm_smoke_test_route
wm_log "Deployment ist bereit. Vor dem Livegang liefert Port 80 nur ACME-Challenges und ansonsten HTTP 404." release_images_file="$WATERMAPS_RUNTIME_DIR/deployments/$revision.env"
install -m 0644 "$resolved_images_file" "$release_images_file"
if [[ -n "$rollback_images_file" ]]; then
install -m 0644 \
"$rollback_images_file" \
"$WATERMAPS_RUNTIME_DIR/deployments/previous.env"
fi
install -m 0644 "$resolved_images_file" "$active_images_file"
deployment_complete=true
wm_log "Deployment von Commit $revision ist bereit. Vor dem Livegang liefert Port 80 nur ACME-Challenges und ansonsten HTTP 404."
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -Eeuo pipefail
WM_DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=remote-common.sh
source "$WM_DEPLOY_DIR/scripts/remote-common.sh"
server_ipv4=""
identity_file=""
revision="previous"
usage() {
printf 'Verwendung: %s [--revision SHA|previous] [--host IPV4] [--identity DATEI] [--user BENUTZER]\n' "$0"
}
while [[ "$#" -gt 0 ]]; do
case "$1" in
--revision)
[[ "$#" -ge 2 ]] || wm_local_die "Wert für --revision fehlt."
revision="$2"
shift 2
;;
--host)
[[ "$#" -ge 2 ]] || wm_local_die "Wert für --host fehlt."
server_ipv4="$2"
shift 2
;;
--identity)
[[ "$#" -ge 2 ]] || wm_local_die "Wert für --identity fehlt."
identity_file="$2"
shift 2
;;
--user)
[[ "$#" -ge 2 ]] || wm_local_die "Wert für --user fehlt."
WATERMAPS_SSH_USER="$2"
export WATERMAPS_SSH_USER
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
wm_local_die "Unbekannte Option: $1"
;;
esac
done
[[ "$revision" == "previous" || "$revision" =~ ^[0-9a-f]{40}$ ]] ||
wm_local_die "--revision muss 'previous' oder eine vollständige Git-Commit-SHA sein."
wm_local_resolve_ssh "$server_ipv4" "$identity_file"
wm_local_wait_for_ssh
remote_prefix=""
if [[ -n "$WM_REMOTE_SUDO" ]]; then
remote_prefix="$WM_REMOTE_SUDO "
fi
ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \
"${remote_prefix}/opt/watermaps/deploy/scripts/rollback.sh '$revision'"
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -Eeuo pipefail
WM_DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=common.sh
source "$WM_DEPLOY_DIR/scripts/common.sh"
if [[ "$(id -u)" -ne 0 ]]; then
wm_die "Dieses Skript muss als root ausgeführt werden."
fi
wm_load_env
revision="${1:-previous}"
deployments_dir="$WATERMAPS_RUNTIME_DIR/deployments"
if [[ "$revision" == "previous" ]]; then
images_file="$deployments_dir/previous.env"
else
[[ "$revision" =~ ^[0-9a-f]{40}$ ]] ||
wm_die "Rollback-Ziel muss 'previous' oder eine vollständige Git-Commit-SHA sein."
images_file="$deployments_dir/$revision.env"
fi
[[ -f "$images_file" ]] ||
wm_die "Gespeichertes Release nicht gefunden: $images_file"
exec "$WM_DEPLOY_DIR/scripts/deploy.sh" --images-file "$images_file"
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
TEST_DIR="$(mktemp -d)"
trap 'rm -rf "$TEST_DIR"' EXIT
# shellcheck source=../common.sh
source "$ROOT_DIR/deploy/scripts/common.sh"
revision="0123456789abcdef0123456789abcdef01234567"
valid_env="$TEST_DIR/valid.env"
cat >"$valid_env" <<EOF
WATERMAPS_DEPLOY_REVISION=$revision
WATERMAPS_APP_IMAGE=registry.example:5000/team/watermaps:$revision
WATERMAPS_ROUTE_DATA_IMAGE=registry.example:5000/team/watermaps-route-data:$revision
EOF
wm_validate_images_env "$valid_env"
invalid_env="$TEST_DIR/invalid.env"
cat >"$invalid_env" <<EOF
WATERMAPS_DEPLOY_REVISION=$revision
WATERMAPS_APP_IMAGE=registry.example/team/watermaps:latest
WATERMAPS_ROUTE_DATA_IMAGE=registry.example/team/watermaps-route-data:$revision
EOF
if (wm_validate_images_env "$invalid_env" >/dev/null 2>&1); then
printf 'Beweglicher latest-Tag wurde als Release akzeptiert.\n' >&2
exit 1
fi
fake_bin="$TEST_DIR/bin"
mkdir -p "$fake_bin"
cat >"$fake_bin/docker" <<'SH'
#!/usr/bin/env bash
set -Eeuo pipefail
if [[ "${1:-}" == "image" && "${2:-}" == "inspect" ]]; then
printf '%s\n' \
'mirror.example/team/watermaps@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \
'registry.example:5000/team/watermaps@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
exit 0
fi
exit 1
SH
chmod +x "$fake_bin/docker"
PATH="$fake_bin:$PATH"
resolved="$(
wm_resolve_image_digest \
"registry.example:5000/team/watermaps:$revision"
)"
[[ "$resolved" == \
"registry.example:5000/team/watermaps@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ]]
printf 'Commit-Tag- und Digest-Prüfungen: OK\n'
@@ -10,6 +10,7 @@ FAKE_BIN="$TEST_DIR/bin"
DATA_DIR="$TEST_DIR/data" DATA_DIR="$TEST_DIR/data"
RUNTIME_DIR="$TEST_DIR/runtime" RUNTIME_DIR="$TEST_DIR/runtime"
ENV_FILE="$TEST_DIR/production.env" ENV_FILE="$TEST_DIR/production.env"
IMAGES_ENV_FILE="$TEST_DIR/images.env"
mkdir -p "$FAKE_BIN" "$DATA_DIR/local" "$RUNTIME_DIR" mkdir -p "$FAKE_BIN" "$DATA_DIR/local" "$RUNTIME_DIR"
cat >"$ENV_FILE" <<EOF cat >"$ENV_FILE" <<EOF
@@ -17,6 +18,12 @@ WATERMAPS_DATA_DIR=$DATA_DIR
WATERMAPS_RUNTIME_DIR=$RUNTIME_DIR WATERMAPS_RUNTIME_DIR=$RUNTIME_DIR
EOF EOF
cat >"$IMAGES_ENV_FILE" <<'EOF'
WATERMAPS_DEPLOY_REVISION=0123456789abcdef0123456789abcdef01234567
WATERMAPS_APP_IMAGE=registry.example/watermaps:0123456789abcdef0123456789abcdef01234567
WATERMAPS_ROUTE_DATA_IMAGE=registry.example/watermaps-route-data:0123456789abcdef0123456789abcdef01234567
EOF
cat >"$FAKE_BIN/id" <<'SH' cat >"$FAKE_BIN/id" <<'SH'
#!/usr/bin/env bash #!/usr/bin/env bash
if [[ "${1:-}" == "-u" ]]; then if [[ "${1:-}" == "-u" ]]; then
@@ -95,6 +102,7 @@ chmod +x "$FAKE_BIN/id" "$FAKE_BIN/mountpoint" "$FAKE_BIN/docker"
export PATH="$FAKE_BIN:$PATH" export PATH="$FAKE_BIN:$PATH"
export WATERMAPS_ENV_FILE="$ENV_FILE" export WATERMAPS_ENV_FILE="$ENV_FILE"
export WATERMAPS_IMAGES_ENV_FILE="$IMAGES_ENV_FILE"
export WM_TEST_ROOT="$ROOT_DIR" export WM_TEST_ROOT="$ROOT_DIR"
# A first successful activation creates an active index and a validated marker. # A first successful activation creates an active index and a validated marker.
+1 -1
View File
@@ -145,7 +145,7 @@ if wm_route_data_ready; then
fi fi
wm_log "Deutschland-/Niederlande-Routingdaten werden in einer Stagingdatei gebaut." wm_log "Deutschland-/Niederlande-Routingdaten werden in einer Stagingdatei gebaut."
if ! wm_compose --profile maintenance run --rm --build \ if ! wm_compose --profile maintenance run --rm \
--env "WATERMAPS_LOCAL_FAIRWAYS_PATH=$container_candidate" \ --env "WATERMAPS_LOCAL_FAIRWAYS_PATH=$container_candidate" \
--env "WATERMAPS_ROUTE_MARKER_PATH=$container_candidate_marker" \ --env "WATERMAPS_ROUTE_MARKER_PATH=$container_candidate_marker" \
route-data; then route-data; then
+70 -22
View File
@@ -24,8 +24,13 @@ Optionen:
-h, --help Hilfe anzeigen -h, --help Hilfe anzeigen
Die Datei deploy/.env.production muss lokal vorhanden sein. Sie wird separat Die Datei deploy/.env.production muss lokal vorhanden sein. Sie wird separat
mit Dateimodus 0600 übertragen. Terraform-State, .terraform, data, mit Dateimodus 0600 übertragen. Es wird ausschließlich der Deployment-Ordner
node_modules, lokale Env-Dateien und Git-Metadaten werden ausgeschlossen. übertragen; App und Routingdaten-Builder werden als commitgenaue Images aus der
in .env.production konfigurierten Gitea Registry geladen.
Für eine private Registry können WATERMAPS_REGISTRY_USERNAME und
WATERMAPS_REGISTRY_TOKEN lokal gesetzt werden. Der Token wird nur über SSH an
`docker login --password-stdin` übergeben und nicht auf den Server hochgeladen.
USAGE USAGE
} }
@@ -62,10 +67,49 @@ while [[ "$#" -gt 0 ]]; do
done done
command -v rsync >/dev/null 2>&1 || wm_local_die "rsync ist lokal nicht installiert." command -v rsync >/dev/null 2>&1 || wm_local_die "rsync ist lokal nicht installiert."
command -v git >/dev/null 2>&1 || wm_local_die "git ist lokal nicht installiert."
local_env="$WM_DEPLOY_DIR/.env.production" local_env="$WM_DEPLOY_DIR/.env.production"
[[ -f "$local_env" ]] || [[ -f "$local_env" ]] ||
wm_local_die "Bitte zuerst deploy/.env.production aus der Vorlage erstellen und konfigurieren." wm_local_die "Bitte zuerst deploy/.env.production aus der Vorlage erstellen und konfigurieren."
if [[ -n "$(git -C "$WM_ROOT_DIR" status --porcelain --untracked-files=normal)" ]]; then
wm_local_die "Deployment nur aus einem vollständig committeten, sauberen Git-Stand erlaubt."
fi
revision="$(git -C "$WM_ROOT_DIR" rev-parse HEAD)"
[[ "$revision" =~ ^[0-9a-f]{40}$ ]] ||
wm_local_die "Aktueller Git-Commit konnte nicht als vollständige SHA ermittelt werden."
set -a
# shellcheck disable=SC1090
source "$local_env"
set +a
registry="${WATERMAPS_REGISTRY:-}"
registry_owner="${WATERMAPS_REGISTRY_OWNER:-}"
[[ "$registry" =~ ^[a-z0-9][a-z0-9.-]*(:[0-9]{1,5})?$ ]] ||
wm_local_die "WATERMAPS_REGISTRY muss ein Registry-Hostname ohne URL-Schema sein."
[[ "$registry_owner" =~ ^[A-Za-z0-9_.-]+$ ]] ||
wm_local_die "WATERMAPS_REGISTRY_OWNER enthält keinen gültigen Gitea-Besitzer."
registry_username="${WATERMAPS_REGISTRY_USERNAME:-}"
registry_token="${WATERMAPS_REGISTRY_TOKEN:-}"
if [[ -n "$registry_username" || -n "$registry_token" ]]; then
[[ -n "$registry_username" && -n "$registry_token" ]] ||
wm_local_die "WATERMAPS_REGISTRY_USERNAME und WATERMAPS_REGISTRY_TOKEN müssen gemeinsam gesetzt werden."
[[ "$registry_username" =~ ^[A-Za-z0-9_.-]+$ ]] ||
wm_local_die "WATERMAPS_REGISTRY_USERNAME enthält ungültige Zeichen."
fi
images_env="$(mktemp)"
trap 'rm -f "$images_env"' EXIT
chmod 0600 "$images_env"
{
printf 'WATERMAPS_DEPLOY_REVISION=%s\n' "$revision"
printf 'WATERMAPS_APP_IMAGE=%s/%s/watermaps:%s\n' \
"$registry" "$registry_owner" "$revision"
printf 'WATERMAPS_ROUTE_DATA_IMAGE=%s/%s/watermaps-route-data:%s\n' \
"$registry" "$registry_owner" "$revision"
} >"$images_env"
wm_local_resolve_ssh "$server_ipv4" "$identity_file" wm_local_resolve_ssh "$server_ipv4" "$identity_file"
wm_local_wait_for_ssh wm_local_wait_for_ssh
@@ -80,7 +124,7 @@ printf '[watermaps] Warte auf Cloud-init und das persistente Hetzner-Volume.\n'
ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \ ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \
"cloud-init status --wait && ${remote_prefix}systemctl start watermaps-volume-setup.service && mountpoint --quiet /srv/watermaps-data" "cloud-init status --wait && ${remote_prefix}systemctl start watermaps-volume-setup.service && mountpoint --quiet /srv/watermaps-data"
printf '[watermaps] Übertrage Projekt nach %s:/opt/watermaps\n' "$WM_SSH_TARGET" printf '[watermaps] Übertrage Deployment-Dateien nach %s:/opt/watermaps/deploy\n' "$WM_SSH_TARGET"
ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \ ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \
"${remote_prefix}install -d -m 0755 /opt/watermaps /opt/watermaps/deploy" "${remote_prefix}install -d -m 0755 /opt/watermaps /opt/watermaps/deploy"
@@ -90,24 +134,11 @@ rsync \
--delete-delay \ --delete-delay \
--human-readable \ --human-readable \
--rsync-path="$rsync_path" \ --rsync-path="$rsync_path" \
--exclude='.git/' \ --exclude='.env.production' \
--exclude='.terraform/' \ --exclude='.env.images*' \
--exclude='.terraform.tfstate.lock.info' \
--exclude='*.tfvars' \
--exclude='*.tfvars.json' \
--exclude='*.tfplan' \
--exclude='*.tfstate*' \
--exclude='crash.log' \
--exclude='crash.*.log' \
--exclude='/data/' \
--exclude='/.tools/' \
--exclude='node_modules/' \
--exclude='**/.env' \
--exclude='**/.env.*' \
--exclude='/deploy/.env.production' \
-e "ssh ${WM_SSH_OPTIONS[*]@Q}" \ -e "ssh ${WM_SSH_OPTIONS[*]@Q}" \
"$WM_ROOT_DIR/" \ "$WM_DEPLOY_DIR/" \
"$WM_SSH_TARGET:/opt/watermaps/" "$WM_SSH_TARGET:/opt/watermaps/deploy/"
rsync \ rsync \
--archive \ --archive \
@@ -117,15 +148,32 @@ rsync \
"$local_env" \ "$local_env" \
"$WM_SSH_TARGET:/opt/watermaps/deploy/.env.production" "$WM_SSH_TARGET:/opt/watermaps/deploy/.env.production"
rsync \
--archive \
--chmod=F600 \
--rsync-path="$rsync_path" \
-e "ssh ${WM_SSH_OPTIONS[*]@Q}" \
"$images_env" \
"$WM_SSH_TARGET:/opt/watermaps/deploy/.env.images.candidate"
if [[ -n "$registry_token" ]]; then
printf '[watermaps] Aktualisiere den privaten Registry-Login auf dem Server.\n'
printf '%s\n' "$registry_token" |
ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \
"${remote_prefix}docker login '$registry' --username '$registry_username' --password-stdin"
fi
remote_command="${remote_prefix}chmod +x /opt/watermaps/deploy/scripts/*.sh" remote_command="${remote_prefix}chmod +x /opt/watermaps/deploy/scripts/*.sh"
remote_command+=" && ${remote_prefix}/opt/watermaps/deploy/scripts/bootstrap-server.sh" remote_command+=" && ${remote_prefix}/opt/watermaps/deploy/scripts/bootstrap-server.sh"
remote_command+=" && ${remote_prefix}/opt/watermaps/deploy/scripts/deploy.sh" remote_command+=" && ${remote_prefix}/opt/watermaps/deploy/scripts/deploy.sh --images-file /opt/watermaps/deploy/.env.images.candidate"
ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" "$remote_command" ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" "$remote_command"
ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \
"${remote_prefix}rm -f /opt/watermaps/deploy/.env.images.candidate"
if [[ "$run_go_live" == "true" ]]; then if [[ "$run_go_live" == "true" ]]; then
ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \ ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \
"${remote_prefix}/opt/watermaps/deploy/scripts/go-live.sh '$WM_SERVER_IPV4'" "${remote_prefix}/opt/watermaps/deploy/scripts/go-live.sh '$WM_SERVER_IPV4'"
else else
printf '[watermaps] Deployment abgeschlossen. Nach dem DNS-Eintrag:\n' printf '[watermaps] Commit %s wurde aus der Registry deployt. Nach dem DNS-Eintrag:\n' "$revision"
printf ' ./deploy/scripts/remote-go-live.sh --host %s\n' "$WM_SERVER_IPV4" printf ' ./deploy/scripts/remote-go-live.sh --host %s\n' "$WM_SERVER_IPV4"
fi fi
+13 -6
View File
@@ -3,14 +3,20 @@
Diese OpenTofu-Konfiguration erstellt einen einzelnen Ubuntu-24.04-Server mit Diese OpenTofu-Konfiguration erstellt einen einzelnen Ubuntu-24.04-Server mit
fester IPv4-Adresse, vorgeschalteter Hetzner-Firewall und einem persistenten fester IPv4-Adresse, vorgeschalteter Hetzner-Firewall und einem persistenten
ext4-Volume für die Routingdaten von Deutschland und den Niederlanden. ext4-Volume für die Routingdaten von Deutschland und den Niederlanden.
Kartenkacheln, DNS-Einträge und Anwendungsdeployment sind bewusst nicht Teil Kartenkacheln, DNS-Einträge und konkrete Anwendungsversionen sind bewusst nicht
dieses Infrastrukturmoduls. Teil dieses Infrastrukturmoduls. Die Anwendung wird anschließend als
commitgenaues Container-Image aus der Gitea Registry deployt.
## Konfiguration ## Konfiguration
Die lokale Datei `terraform.tfvars` ist bereits angelegt und wird durch die Die lokale Datei `terraform.tfvars` wird aus der versionierten Vorlage angelegt
lokale `.gitignore` ausgeschlossen. Vor dem ersten Plan müssen dort mindestens und durch die lokale `.gitignore` ausgeschlossen:
folgende Werte ersetzt werden:
```sh
cp terraform.tfvars.example terraform.tfvars
```
Vor dem ersten Plan müssen dort mindestens folgende Werte ersetzt werden:
- `hcloud_token`: Read/Write-API-Token des richtigen Hetzner-Cloud-Projekts - `hcloud_token`: Read/Write-API-Token des richtigen Hetzner-Cloud-Projekts
- `ssh_public_key`: vollständiger Inhalt des öffentlichen SSH-Schlüssels - `ssh_public_key`: vollständiger Inhalt des öffentlichen SSH-Schlüssels
@@ -58,7 +64,8 @@ bereitstehen.
## Auf dem Server ## Auf dem Server
Cloud-init installiert Docker einschließlich Compose v2, `curl`, `jq` und Cloud-init installiert Docker einschließlich Compose v2, `curl`, `jq` und
`rsync`. Der Benutzer `deploy` erhält Zugriff per SSH und auf Docker. Das `rsync`. Der Benutzer `deploy` erhält Zugriff per SSH und auf Docker. Es wird
kein Git-Repository auf den Server geklont. Das
persistente Volume wird nach dem Anhängen unter `/srv/watermaps-data` gemountet persistente Volume wird nach dem Anhängen unter `/srv/watermaps-data` gemountet
und enthält die vom Produktions-Stack verwendeten Verzeichnisse `geofabrik`, und enthält die vom Produktions-Stack verwendeten Verzeichnisse `geofabrik`,
`local` und `certbot`. Ein systemd-Drop-in lässt Docker bei jedem Start auf den `local` und `certbot`. Ein systemd-Drop-in lässt Docker bei jedem Start auf den
+1 -2
View File
@@ -127,8 +127,7 @@ write_files:
runcmd: runcmd:
- [install, -d, -m, "0755", -o, ${deploy_user}, -g, ${deploy_user}, /opt/watermaps] - [install, -d, -m, "0755", -o, ${deploy_user}, -g, ${deploy_user}, /opt/watermaps]
- [install, -d, -m, "0755", -o, ${deploy_user}, -g, ${deploy_user}, /opt/watermaps/releases] - [install, -d, -m, "0755", -o, ${deploy_user}, -g, ${deploy_user}, /opt/watermaps/deploy]
- [install, -d, -m, "0755", -o, ${deploy_user}, -g, ${deploy_user}, /opt/watermaps/shared]
- [install, -d, -m, "0755", -o, ${deploy_user}, -g, ${deploy_user}, /var/www/certbot] - [install, -d, -m, "0755", -o, ${deploy_user}, -g, ${deploy_user}, /var/www/certbot]
- [systemctl, daemon-reload] - [systemctl, daemon-reload]
- [systemctl, enable, --now, watermaps-volume-setup.service] - [systemctl, enable, --now, watermaps-volume-setup.service]
+1 -1
View File
@@ -22,7 +22,7 @@
"setup:local-routing": "./scripts/setup-local-routing.sh", "setup:local-routing": "./scripts/setup-local-routing.sh",
"sync:euris-locks": "node scripts/sync-euris-locks.mjs", "sync:euris-locks": "node scripts/sync-euris-locks.mjs",
"test": "npm run build --workspace @watermaps/shared && npm run test --workspace @watermaps/shared && npm run test --workspace @watermaps/api && npm run test --workspace @watermaps/web && npm run test:local-routing && npm run test:deployment", "test": "npm run build --workspace @watermaps/shared && npm run test --workspace @watermaps/shared && npm run test --workspace @watermaps/api && npm run test --workspace @watermaps/web && npm run test:local-routing && npm run test:deployment",
"test:deployment": "bash deploy/scripts/tests/route-data-helpers.test.sh && bash deploy/scripts/tests/update-route-data.test.sh", "test:deployment": "bash deploy/scripts/tests/image-references.test.sh && bash deploy/scripts/tests/route-data-helpers.test.sh && bash deploy/scripts/tests/update-route-data.test.sh",
"test:e2e": "npm run test:e2e --workspace @watermaps/web", "test:e2e": "npm run test:e2e --workspace @watermaps/web",
"test:local-routing": "PYTHONPATH=.tools/python python3 -m unittest discover -s scripts/tests -p 'test_*.py'", "test:local-routing": "PYTHONPATH=.tools/python python3 -m unittest discover -s scripts/tests -p 'test_*.py'",
"typecheck": "npm run build --workspace @watermaps/shared && npm run typecheck --workspace @watermaps/shared && npm run typecheck --workspace @watermaps/api && npm run typecheck --workspace @watermaps/web" "typecheck": "npm run build --workspace @watermaps/shared && npm run typecheck --workspace @watermaps/shared && npm run typecheck --workspace @watermaps/api && npm run typecheck --workspace @watermaps/web"