Files
watermaps/deploy/scripts/deploy.sh
T
BuTzZ 2064570913
Test and publish container images / test (push) Successful in 2m43s
Test and publish container images / publish (push) Successful in 2m56s
Automate immutable production deployments
2026-07-29 13:00:50 +02:00

229 lines
7.1 KiB
Bash
Executable File

#!/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"
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_assert_data_mount
wm_use_images_env "$images_env_file"
install -d -m 0755 \
"$WATERMAPS_DATA_DIR/geofabrik" \
"$WATERMAPS_DATA_DIR/local" \
"$WATERMAPS_DATA_DIR/certbot/www" \
"$WATERMAPS_DATA_DIR/certbot/letsencrypt" \
"$WATERMAPS_RUNTIME_DIR/nginx/conf.d" \
"$WATERMAPS_RUNTIME_DIR/locks" \
"$WATERMAPS_RUNTIME_DIR/deployments"
if ! wm_acquire_route_lock; then
wm_log "Deployment wird später erneut versucht, weil gerade Routingdaten aktualisiert werden."
exit 75
fi
active_images_file="${WATERMAPS_ACTIVE_IMAGES_FILE:-${WATERMAPS_IMAGES_ENV_FILE:-$WM_DEPLOY_DIR/.env.images}}"
[[ "$active_images_file" == /* ]] ||
wm_die "WATERMAPS_ACTIVE_IMAGES_FILE muss ein absoluter Pfad sein."
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" &&
"${WATERMAPS_DISABLE_INTERNAL_ROLLBACK:-false}" != "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 postgres watermaps nginx
wm_wait_for_health postgres 240
wm_wait_for_health watermaps 240
wm_wait_for_health nginx 120
if wm_route_data_ready; then
wm_smoke_test_route
fi
if wm_marine_features_ready; then
wm_smoke_test_features
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
install -m 0644 \
"$WM_DEPLOY_DIR/nginx/bootstrap.conf" \
"$WATERMAPS_RUNTIME_DIR/nginx/conf.d/default.conf"
fi
revision="$(wm_env_value "$images_env_file" WATERMAPS_DEPLOY_REVISION)"
requested_app_image="$(wm_env_value "$images_env_file" WATERMAPS_APP_IMAGE)"
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 postgres 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 "Persistentes PostGIS wird vor App und Datenimport gestartet."
wm_compose up --detach --no-build postgres
wm_wait_for_health postgres 240
wm_log "Versionierte Datenbankmigrationen für Commit $revision werden angewendet."
wm_compose exec --no-TTY postgres \
psql \
--no-psqlrc \
--username seacompass \
--dbname seacompass \
--set ON_ERROR_STOP=1 \
--command '
CREATE TABLE IF NOT EXISTS watermaps_schema_migrations (
version text PRIMARY KEY,
applied_at timestamptz NOT NULL DEFAULT now()
);
'
shopt -s nullglob
migration_files=("$WM_ROOT_DIR"/database/migrations/*.sql)
shopt -u nullglob
[[ "${#migration_files[@]}" -gt 0 ]] ||
wm_die "Keine Datenbankmigrationen im Release gefunden."
for migration_file in "${migration_files[@]}"; do
migration_name="$(basename "$migration_file")"
[[ "$migration_name" =~ ^[0-9]{4}_[a-z0-9_]+\.sql$ ]] ||
wm_die "Ungültiger Migrationsdateiname: $migration_name"
migration_version="${migration_name%.sql}"
migration_applied="$(
wm_postgres_query "
SELECT EXISTS (
SELECT 1
FROM watermaps_schema_migrations
WHERE version = '$migration_version'
);
"
)"
migration_applied="${migration_applied//[[:space:]]/}"
if [[ "$migration_applied" == "t" ]]; then
continue
fi
wm_log "Wende Datenbankmigration $migration_version atomar an."
wm_compose exec --no-TTY postgres \
psql \
--no-psqlrc \
--username seacompass \
--dbname seacompass \
--set ON_ERROR_STOP=1 \
--single-transaction \
--file "/docker-entrypoint-initdb.d/migrations/$migration_name" \
--command "
INSERT INTO watermaps_schema_migrations (version)
VALUES ('$migration_version');
"
done
data_update_required=false
update_args=()
if [[ "${WATERMAPS_REBUILD_ROUTE_DATA:-false}" == "true" ]]; then
data_update_required=true
update_args+=(--force)
elif ! wm_route_data_ready; then
data_update_required=true
fi
if [[ "${WATERMAPS_REBUILD_MARINE_DATA:-false}" == "true" ]]; then
data_update_required=true
update_args+=(--force-marine)
elif ! wm_marine_features_ready; then
data_update_required=true
fi
if [[ "$data_update_required" == "true" ]]; then
wm_log "Deutschland-/Niederlande-Routingdaten und Marine-Features werden sicher vorbereitet."
WATERMAPS_ROUTE_LOCK_HELD=true \
"$WM_DEPLOY_DIR/scripts/update-route-data.sh" "${update_args[@]}"
fi
wm_assert_route_data
wm_assert_marine_features
wm_log "Commit $revision wird erst mit validierten Routing- und Ereignisdaten gestartet."
wm_compose up --detach --no-build --remove-orphans postgres watermaps nginx
wm_wait_for_health postgres 240
wm_wait_for_health watermaps 240
wm_wait_for_health nginx 120
wm_smoke_test_route
wm_smoke_test_features
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."