#!/usr/bin/env bash set -Eeuo pipefail WM_DEPLOY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" WM_ROOT_DIR="$(cd "$WM_DEPLOY_DIR/.." && pwd)" # shellcheck source=remote-common.sh source "$WM_DEPLOY_DIR/scripts/remote-common.sh" server_ipv4="" identity_file="" run_go_live=false usage() { cat <<'USAGE' Verwendung: ./deploy/scripts/upload-and-deploy.sh [Optionen] Optionen: --host IPV4 Server-IP statt `tofu output -raw server_ipv4` --identity DATEI Privater SSH-Key (alternativ WATERMAPS_SSH_KEY) --user BENUTZER SSH-Benutzer; Standard: deploy --go-live Nach dem Deployment sofort den SSL-Livegang versuchen -h, --help Hilfe anzeigen Die Datei deploy/.env.production muss lokal vorhanden sein. Sie wird separat mit Dateimodus 0600 übertragen. Es wird ausschließlich der Deployment-Ordner ü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 } while [[ "$#" -gt 0 ]]; do case "$1" in --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 ;; --go-live) run_go_live=true shift ;; -h|--help) usage exit 0 ;; *) wm_local_die "Unbekannte Option: $1" ;; esac done 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" [[ -f "$local_env" ]] || 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." origin_main_revision() { local main_revision main_revision="$( git -C "$WM_ROOT_DIR" \ ls-remote --exit-code origin refs/heads/main | awk 'NR == 1 { print $1 }' )" [[ "$main_revision" =~ ^[0-9a-f]{40}$ ]] || return 1 printf '%s\n' "$main_revision" } published_revision="$(origin_main_revision)" || wm_local_die "origin/main konnte nicht revisionsgenau gelesen werden." [[ "$published_revision" == "$revision" ]] || wm_local_die "Lokaler HEAD $revision ist nicht der aktuelle origin/main-Commit $published_revision." 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 wm_local_resolve_ssh "$server_ipv4" "$identity_file" wm_local_wait_for_ssh remote_prefix="" rsync_path="rsync" if [[ -n "$WM_REMOTE_SUDO" ]]; then remote_prefix="$WM_REMOTE_SUDO " rsync_path="$WM_REMOTE_SUDO rsync" fi wait_for_remote_release() { local expected_revision="$1" local timeout_seconds="${WATERMAPS_RELEASE_WAIT_SECONDS:-43200}" local poll_seconds="${WATERMAPS_RELEASE_POLL_SECONDS:-15}" local deadline local attempts=0 local current_main_revision local remote_check [[ "$timeout_seconds" =~ ^[1-9][0-9]*$ ]] && ((timeout_seconds >= 60 && timeout_seconds <= 46800)) || wm_local_die "WATERMAPS_RELEASE_WAIT_SECONDS muss zwischen 60 und 46800 liegen." [[ "$poll_seconds" =~ ^[1-9][0-9]*$ ]] && ((poll_seconds >= 5 && poll_seconds <= 60)) || wm_local_die "WATERMAPS_RELEASE_POLL_SECONDS muss zwischen 5 und 60 liegen." deadline=$((SECONDS + timeout_seconds)) remote_check="${remote_prefix}test -L /opt/watermaps/current" remote_check+=" && ${remote_prefix}grep -Fxq" remote_check+=" 'WATERMAPS_DEPLOY_REVISION=$expected_revision'" remote_check+=" /opt/watermaps/deploy/.env.images" remote_check+=" && ${remote_prefix}readlink --canonicalize /opt/watermaps/current" remote_check+=" | grep -Eq '/$expected_revision$'" while ((SECONDS < deadline)); do if ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" "$remote_check" \ >/dev/null 2>&1; then printf '[watermaps] Commit %s ist auf dem Server aktiv.\n' \ "$expected_revision" return 0 fi attempts=$((attempts + 1)) if ((attempts == 1 || attempts % 4 == 0)); then if current_main_revision="$(origin_main_revision 2>/dev/null)" && [[ "$current_main_revision" != "$expected_revision" ]]; then wm_local_die "origin/main wurde während des Erstdeployments von $expected_revision auf $current_main_revision verschoben; starte den Livegang aus dem aktuellen main-Stand erneut." fi printf '[watermaps] Das revisionsgenaue Erstdeployment läuft unter systemd; warte auf Commit %s …\n' \ "$expected_revision" fi sleep "$poll_seconds" done ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \ "${remote_prefix}systemctl --no-pager --full status watermaps-auto-deploy.service" \ || true wm_local_die "Commit $expected_revision wurde nicht innerhalb von ${timeout_seconds}s aktiviert." } printf '[watermaps] Warte auf Cloud-init und das persistente Hetzner-Volume.\n' ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \ "cloud-init status --wait && ${remote_prefix}systemctl start watermaps-volume-setup.service && mountpoint --quiet /srv/watermaps-data" printf '[watermaps] Übertrage Bootstrap-Dateien nach %s:/opt/watermaps\n' "$WM_SSH_TARGET" ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \ "${remote_prefix}install -d -m 0755 /opt/watermaps /opt/watermaps/deploy" rsync \ --archive \ --compress \ --delete-delay \ --human-readable \ --rsync-path="$rsync_path" \ --exclude='.env.production' \ --exclude='.env.images*' \ --exclude='.gitea-token' \ --exclude='.gitea-*-token' \ -e "ssh ${WM_SSH_OPTIONS[*]@Q}" \ "$WM_DEPLOY_DIR/" \ "$WM_SSH_TARGET:/opt/watermaps/deploy/" rsync \ --archive \ --chmod=F600 \ --rsync-path="$rsync_path" \ -e "ssh ${WM_SSH_OPTIONS[*]@Q}" \ "$local_env" \ "$WM_SSH_TARGET:/opt/watermaps/deploy/.env.production" 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}/opt/watermaps/deploy/scripts/bootstrap-server.sh" remote_command+=" && ${remote_prefix}systemctl start --no-block watermaps-auto-deploy.service" ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" "$remote_command" wait_for_remote_release "$revision" if [[ "$run_go_live" == "true" ]]; then ssh "${WM_SSH_OPTIONS[@]}" "$WM_SSH_TARGET" \ "${remote_prefix}WATERMAPS_ENV_FILE=/opt/watermaps/deploy/.env.production WATERMAPS_IMAGES_ENV_FILE=/opt/watermaps/deploy/.env.images /opt/watermaps/current/deploy/scripts/go-live.sh '$WM_SERVER_IPV4'" else 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" fi