From 1c7bfdf421c3df29d0c4e7dc8c908335dc69ba55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rdle=20Bot?= Date: Sat, 25 Apr 2026 09:46:52 +0000 Subject: [PATCH] Ops: add Proxmox migration tooling and runbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add end-to-end migration scripts for inventory, precopy, cutover, smoke tests, rollback, and post-migration checks. Include an operational runbook and Proxmox env template to move Hördle behind Nginx Proxy Manager while preserving persistent volumes safely. --- .env.proxmox.example | 29 +++++++ docs/PROXMOX_MIGRATION_RUNBOOK.md | 121 ++++++++++++++++++++++++++++ scripts/backup-persistence.sh | 39 +++++++++ scripts/migration-cutover.sh | 73 +++++++++++++++++ scripts/migration-inventory.sh | 70 ++++++++++++++++ scripts/migration-postcheck.sh | 34 ++++++++ scripts/migration-precopy.sh | 81 +++++++++++++++++++ scripts/migration-prepare-target.sh | 72 +++++++++++++++++ scripts/migration-rollback.sh | 23 ++++++ scripts/migration-smoke-test.sh | 65 +++++++++++++++ 10 files changed, 607 insertions(+) create mode 100644 .env.proxmox.example create mode 100644 docs/PROXMOX_MIGRATION_RUNBOOK.md create mode 100755 scripts/backup-persistence.sh create mode 100755 scripts/migration-cutover.sh create mode 100755 scripts/migration-inventory.sh create mode 100755 scripts/migration-postcheck.sh create mode 100755 scripts/migration-precopy.sh create mode 100755 scripts/migration-prepare-target.sh create mode 100755 scripts/migration-rollback.sh create mode 100755 scripts/migration-smoke-test.sh diff --git a/.env.proxmox.example b/.env.proxmox.example new file mode 100644 index 0000000..83b1471 --- /dev/null +++ b/.env.proxmox.example @@ -0,0 +1,29 @@ +# ============================================ +# Hördle Environment (Proxmox + NPM) +# ============================================ +# Kopiere nach .env und passe alle Werte an. + +# Build-Time +NEXT_PUBLIC_APP_NAME=Hördle +NEXT_PUBLIC_APP_DESCRIPTION=Daily music guessing game +NEXT_PUBLIC_DOMAIN=hoerdle.de +NEXT_PUBLIC_TWITTER_HANDLE=@hoerdle +NEXT_PUBLIC_PLAUSIBLE_DOMAIN=hoerdle.de +NEXT_PUBLIC_PLAUSIBLE_SCRIPT_SRC=https://plausible.example.com/js/script.js +NEXT_PUBLIC_THEME_COLOR=#000000 +NEXT_PUBLIC_BACKGROUND_COLOR=#ffffff +NEXT_PUBLIC_CREDITS_ENABLED=true +NEXT_PUBLIC_CREDITS_TEXT=Vibe coded with coffee +NEXT_PUBLIC_CREDITS_LINK_TEXT=@yourhandle@server.social +NEXT_PUBLIC_CREDITS_LINK_URL=https://server.social/@yourhandle + +# Runtime +DATABASE_URL=file:/app/data/prod.db +ADMIN_PASSWORD= +TZ=Europe/Berlin +GOTIFY_URL= +GOTIFY_APP_TOKEN= +OPENROUTER_API_KEY= + +# Optional Build Metadata +# APP_VERSION=v1.0.0 diff --git a/docs/PROXMOX_MIGRATION_RUNBOOK.md b/docs/PROXMOX_MIGRATION_RUNBOOK.md new file mode 100644 index 0000000..d99d22e --- /dev/null +++ b/docs/PROXMOX_MIGRATION_RUNBOOK.md @@ -0,0 +1,121 @@ +# Hördle Migration Runbook (Proxmox-LXC + Nginx Proxy Manager) + +Dieses Runbook setzt den Migrationsplan operativ um und priorisiert Datensicherheit +fuer `data/prod.db` und `public/uploads`. + +## 0) Voraussetzungen + +- Quelle (alt): VPS mit laufendem Hördle-Compose-Stack. +- Ziel (neu): Proxmox-LXC unter `root@10.0.0.19`. +- DNS ist bereits vorbereitet. +- Ziel-LXC ist im internen Netz vom NPM-LXC erreichbar. + +## 1) Inventur auf der Quelle + +```bash +cd /home/docker/hoerdle +./scripts/migration-inventory.sh +``` + +Erwartung: +- Persistenzpfade vorhanden: `data`, `data/prod.db`, `public/uploads` +- Runtime-Variablen in `docker-compose.yml` referenzieren `.env` + +## 2) Ziel-LXC vorbereiten + +Auf dem Ziel-LXC (`root@10.0.0.19`): + +```bash +mkdir -p /opt/hoerdle +# Repo nach /opt/hoerdle bringen (git clone oder rsync) +cd /opt/hoerdle +./scripts/migration-prepare-target.sh +``` + +Danach: +- `.env` in `/opt/hoerdle/.env` produktiv ausfuellen +- `ADMIN_PASSWORD` als bcrypt hash setzen +- Caddy-Stack nicht verwenden (nur `docker-compose.yml`) + +## 3) Vorabkopie der Daten (Quelle -> Ziel) + +Auf der Quelle: + +```bash +cd /home/docker/hoerdle +TARGET_HOST=root@10.0.0.19 TARGET_APP_DIR=/opt/hoerdle ./scripts/migration-precopy.sh +``` + +Das Skript synchronisiert: +- `data/` +- `public/uploads/` + +und zeigt Dateizaehlung/Groessen als Plausibilitaetscheck. + +## 4) Cutover im Wartungsfenster + +Auf der Quelle: + +```bash +cd /home/docker/hoerdle +TARGET_HOST=root@10.0.0.19 TARGET_APP_DIR=/opt/hoerdle ./scripts/migration-cutover.sh +``` + +Das Skript: +1. stoppt die Quell-App, +2. fuehrt Final-Sync aus, +3. verifiziert `prod.db` per SHA256, +4. startet die Ziel-App mit `docker compose up -d --build`. + +## 5) NPM umschalten + Smoke-Test + +Im Nginx Proxy Manager: +- Proxy Host auf `10.0.0.19:3010` setzen +- Scheme `http`, Websocket Support aktiv +- SSL aktivieren (Let's Encrypt), Force SSL einschalten +- Header-Weiterleitung aktiv lassen (`X-Forwarded-*`) + +Smoke-Test: + +```bash +./scripts/migration-smoke-test.sh https://hoerdle.de +``` + +Zusatzpruefungen manuell: +- Admin-Login +- Upload eines Testfiles +- Abruf des Uploads im Frontend + +## 6) Post-Migration (24h) + +- `docker compose logs --since=24h` auf Ziel auswerten +- NPM Access/Error Logs pruefen +- taegliches Backup sicherstellen: + - `data/prod.db` + - `public/uploads/` +- Alte Instanz erst nach stabiler Beobachtungszeit stilllegen + +Automatisierter Postcheck: + +```bash +cd /opt/hoerdle +DOMAIN_URL=https://hoerdle.de ./scripts/migration-postcheck.sh +``` + +Beispiel fuer taegliches Backup (cron auf Ziel-LXC): + +```bash +crontab -e +# taeglich 03:30 +30 3 * * * cd /opt/hoerdle && ./scripts/backup-persistence.sh >> /opt/hoerdle/logs/backup-persistence.log 2>&1 +``` + +## 7) Rollback + +Wenn Tests fehlschlagen: + +```bash +TARGET_HOST=root@10.0.0.19 TARGET_APP_DIR=/opt/hoerdle ./scripts/migration-rollback.sh +``` + +Danach sofort im NPM auf alte Instanz zurueckschalten. diff --git a/scripts/backup-persistence.sh b/scripts/backup-persistence.sh new file mode 100755 index 0000000..14beba7 --- /dev/null +++ b/scripts/backup-persistence.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Erstellt ein lokales Backup der Persistenzdaten. +# Ziel: data/prod.db + public/uploads + +APP_DIR="${APP_DIR:-$(pwd)}" +BACKUP_DIR="${BACKUP_DIR:-$APP_DIR/backups/persistence}" +TIMESTAMP="$(date +%Y%m%d_%H%M%S)" +OUT_DIR="$BACKUP_DIR/$TIMESTAMP" + +cd "$APP_DIR" + +mkdir -p "$OUT_DIR" + +echo "== Hördle Persistenz-Backup ==" +echo "Quelle: $APP_DIR" +echo "Ziel: $OUT_DIR" + +if [ -f "data/prod.db" ]; then + if command -v sqlite3 >/dev/null 2>&1; then + sqlite3 "data/prod.db" ".timeout 5000" ".backup '$OUT_DIR/prod.db'" + else + cp "data/prod.db" "$OUT_DIR/prod.db" + fi + sha256sum "$OUT_DIR/prod.db" > "$OUT_DIR/prod.db.sha256" +else + echo "Warnung: data/prod.db fehlt." +fi + +if [ -d "public/uploads" ]; then + tar -C "public" -czf "$OUT_DIR/uploads.tar.gz" "uploads" + sha256sum "$OUT_DIR/uploads.tar.gz" > "$OUT_DIR/uploads.tar.gz.sha256" +else + echo "Warnung: public/uploads fehlt." +fi + +echo "$TIMESTAMP" > "$BACKUP_DIR/latest" +echo "Backup abgeschlossen: $OUT_DIR" diff --git a/scripts/migration-cutover.sh b/scripts/migration-cutover.sh new file mode 100755 index 0000000..c306b92 --- /dev/null +++ b/scripts/migration-cutover.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Cutover-Skript fuer kurzes Wartungsfenster. +# Auf QUELLSYSTEM ausfuehren: +# 1) stoppt lokale Hördle-App +# 2) finaler Delta-Sync data/ + uploads/ +# 3) startet Ziel-App im Proxmox-LXC + +TARGET_HOST="${TARGET_HOST:-root@10.0.0.19}" +TARGET_APP_DIR="${TARGET_APP_DIR:-/opt/hoerdle}" +SOURCE_APP_DIR="${SOURCE_APP_DIR:-$(pwd)}" +SOURCE_COMPOSE_FILE="${SOURCE_COMPOSE_FILE:-docker-compose.yml}" +TARGET_COMPOSE_FILE="${TARGET_COMPOSE_FILE:-docker-compose.yml}" +SSH_OPTS="${SSH_OPTS:-}" + +cd "$SOURCE_APP_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Fehlend: $1" + exit 1 + fi +} + +require_cmd docker +require_cmd rsync +require_cmd ssh +require_cmd sha256sum + +echo "== Hördle Cutover ==" +echo "Quelle: $(pwd)" +echo "Ziel: $TARGET_HOST:$TARGET_APP_DIR" +echo + +echo "Wartungsfenster starten. Enter zum Fortfahren, Ctrl+C zum Abbruch." +read -r + +echo "-- Quelle stoppen --" +docker compose -f "$SOURCE_COMPOSE_FILE" down +echo + +echo "-- Finaler Delta-Sync data/ --" +rsync -aHAX --numeric-ids --delete \ + -e "ssh $SSH_OPTS" \ + "./data/" "$TARGET_HOST:$TARGET_APP_DIR/data/" +echo + +echo "-- Finaler Delta-Sync public/uploads/ --" +rsync -aHAX --numeric-ids --delete \ + -e "ssh $SSH_OPTS" \ + "./public/uploads/" "$TARGET_HOST:$TARGET_APP_DIR/public/uploads/" +echo + +if [ -f "./data/prod.db" ]; then + echo "-- Konsistenzcheck DB --" + LOCAL_HASH="$(sha256sum ./data/prod.db | awk '{print $1}')" + REMOTE_HASH="$(ssh $SSH_OPTS "$TARGET_HOST" "sha256sum '$TARGET_APP_DIR/data/prod.db' | awk '{print \$1}'")" + echo "local prod.db sha256: $LOCAL_HASH" + echo "remote prod.db sha256: $REMOTE_HASH" + + if [ "$LOCAL_HASH" != "$REMOTE_HASH" ]; then + echo "DB Hash mismatch. Abbruch vor Start auf Ziel." + exit 1 + fi +fi +echo + +echo "-- Ziel-App starten --" +ssh $SSH_OPTS "$TARGET_HOST" "cd '$TARGET_APP_DIR' && docker compose -f '$TARGET_COMPOSE_FILE' up -d --build" +echo + +echo "Cutover abgeschlossen. Bitte jetzt scripts/migration-smoke-test.sh ausfuehren." diff --git a/scripts/migration-inventory.sh b/scripts/migration-inventory.sh new file mode 100755 index 0000000..b5ed49a --- /dev/null +++ b/scripts/migration-inventory.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Inventarisiert Persistenz und laufzeitrelevante Variablen für die Migration. +# Das Skript verändert keine Daten. + +ROOT_DIR="${1:-$(pwd)}" +cd "$ROOT_DIR" + +count_files_recursive() { + local dir="$1" + find "$dir" -type f 2>/dev/null | wc -l +} + +echo "== Hördle Migrations-Inventur ==" +echo "Projektpfad: $(pwd)" +echo + +required_paths=( + "data" + "data/prod.db" + "public/uploads" + "docker-compose.yml" + ".env" +) + +echo "-- Existenzcheck --" +for path in "${required_paths[@]}"; do + if [ -e "$path" ]; then + echo "[OK] $path" + else + echo "[FEHLT] $path" + fi +done +echo + +echo "-- Groesse und Dateizaehlung --" +if [ -d "data" ]; then + du -sh "data" + DATA_FILE_COUNT="$(count_files_recursive "data")" + echo "Dateien in data/: $DATA_FILE_COUNT" +fi + +if [ -d "public/uploads" ]; then + du -sh "public/uploads" + UPLOAD_FILE_COUNT="$(count_files_recursive "public/uploads")" + echo "Dateien in public/uploads/: $UPLOAD_FILE_COUNT" +fi +echo + +echo "-- DB Hash (Integritaetsreferenz) --" +if [ -f "data/prod.db" ]; then + sha256sum "data/prod.db" +else + echo "data/prod.db nicht gefunden." +fi +echo + +echo "-- Compose Persistenz-Mounts --" +grep -E "^[[:space:]]*-[[:space:]]+\./(data|public/uploads):" "docker-compose.yml" || true +echo + +echo "-- Runtime Variablen in Compose --" +grep -E "^[[:space:]]*-[[:space:]]+(DATABASE_URL|ADMIN_PASSWORD|TZ|GOTIFY_URL|GOTIFY_APP_TOKEN|OPENROUTER_API_KEY)=" "docker-compose.yml" || true +echo + +echo "-- Hinweise --" +echo "1) Stelle sicher, dass .env nicht aus Versehen in Transfers/Backups an Dritte gelangt." +echo "2) Fuer den Cutover muss die Quell-App vor Final-Sync gestoppt werden." +echo "3) Nutze danach scripts/migration-precopy.sh und scripts/migration-cutover.sh." diff --git a/scripts/migration-postcheck.sh b/scripts/migration-postcheck.sh new file mode 100755 index 0000000..223946d --- /dev/null +++ b/scripts/migration-postcheck.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Kontrollskript fuer die ersten 24h nach Migration. + +APP_DIR="${APP_DIR:-$(pwd)}" +DOMAIN_URL="${DOMAIN_URL:-https://hoerdle.de}" +CONTAINER_NAME="${CONTAINER_NAME:-hoerdle}" + +cd "$APP_DIR" + +echo "== Hördle Post-Migration Check ==" +echo "App dir: $APP_DIR" +echo "Domain: $DOMAIN_URL" +echo + +echo "-- Container Status --" +docker compose ps +echo + +echo "-- Health Endpoint --" +curl -fsS "$DOMAIN_URL/api/daily" >/dev/null +echo "OK: /api/daily erreichbar" +echo + +echo "-- Fehlerlogs (24h) --" +docker compose logs --since=24h "$CONTAINER_NAME" 2>&1 | grep -Ei "(error|exception|fatal|panic)" || true +echo + +echo "-- Backup Testlauf --" +./scripts/backup-persistence.sh +echo + +echo "Postcheck abgeschlossen." diff --git a/scripts/migration-precopy.sh b/scripts/migration-precopy.sh new file mode 100755 index 0000000..655a488 --- /dev/null +++ b/scripts/migration-precopy.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Vorabkopie der persistenten Daten von Quelle -> Ziel (App bleibt online). +# Auf dem Quellsystem ausfuehren. + +TARGET_HOST="${TARGET_HOST:-root@10.0.0.19}" +TARGET_APP_DIR="${TARGET_APP_DIR:-/opt/hoerdle}" +SOURCE_APP_DIR="${SOURCE_APP_DIR:-$(pwd)}" +SSH_OPTS="${SSH_OPTS:-}" + +cd "$SOURCE_APP_DIR" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Fehlend: $1" + exit 1 + fi +} + +require_cmd rsync +require_cmd ssh +require_cmd sha256sum +require_cmd find + +echo "== Hördle Precopy ==" +echo "Quelle: $(pwd)" +echo "Ziel: $TARGET_HOST:$TARGET_APP_DIR" +echo + +for d in data public/uploads; do + if [ ! -d "$d" ]; then + echo "Pfad fehlt: $d" + exit 1 + fi +done + +echo "-- Zielordner sicherstellen --" +ssh $SSH_OPTS "$TARGET_HOST" "mkdir -p '$TARGET_APP_DIR/data' '$TARGET_APP_DIR/public/uploads'" +echo + +echo "-- Vorabkopie data/ --" +rsync -aHAX --numeric-ids --delete \ + -e "ssh $SSH_OPTS" \ + "./data/" "$TARGET_HOST:$TARGET_APP_DIR/data/" +echo + +echo "-- Vorabkopie public/uploads/ --" +rsync -aHAX --numeric-ids --delete \ + -e "ssh $SSH_OPTS" \ + "./public/uploads/" "$TARGET_HOST:$TARGET_APP_DIR/public/uploads/" +echo + +echo "-- Verifikation (Dateien/Groesse) --" +LOCAL_DATA_COUNT="$(find data -type f 2>/dev/null | wc -l)" +REMOTE_DATA_COUNT="$(ssh $SSH_OPTS "$TARGET_HOST" "cd '$TARGET_APP_DIR' && find data -type f 2>/dev/null | wc -l")" +echo "data files local=$LOCAL_DATA_COUNT remote=$REMOTE_DATA_COUNT" + +LOCAL_UPLOAD_COUNT="$(find public/uploads -type f 2>/dev/null | wc -l)" +REMOTE_UPLOAD_COUNT="$(ssh $SSH_OPTS "$TARGET_HOST" "cd '$TARGET_APP_DIR' && find public/uploads -type f 2>/dev/null | wc -l")" +echo "uploads files local=$LOCAL_UPLOAD_COUNT remote=$REMOTE_UPLOAD_COUNT" + +LOCAL_DATA_SIZE="$(du -sb data | awk '{print $1}')" +REMOTE_DATA_SIZE="$(ssh $SSH_OPTS "$TARGET_HOST" "du -sb '$TARGET_APP_DIR/data' | awk '{print \$1}'")" +echo "data bytes local=$LOCAL_DATA_SIZE remote=$REMOTE_DATA_SIZE" + +LOCAL_UPLOAD_SIZE="$(du -sb public/uploads | awk '{print $1}')" +REMOTE_UPLOAD_SIZE="$(ssh $SSH_OPTS "$TARGET_HOST" "du -sb '$TARGET_APP_DIR/public/uploads' | awk '{print \$1}'")" +echo "uploads bytes local=$LOCAL_UPLOAD_SIZE remote=$REMOTE_UPLOAD_SIZE" +echo + +if [ -f "data/prod.db" ]; then + echo "-- DB Hash Vergleich (Precopy) --" + LOCAL_HASH="$(sha256sum data/prod.db | awk '{print $1}')" + REMOTE_HASH="$(ssh $SSH_OPTS "$TARGET_HOST" "sha256sum '$TARGET_APP_DIR/data/prod.db' | awk '{print \$1}'")" + echo "prod.db sha256 local=$LOCAL_HASH" + echo "prod.db sha256 remote=$REMOTE_HASH" +fi + +echo +echo "Precopy abgeschlossen. Fuer konsistente Enddaten jetzt Cutover ausfuehren." diff --git a/scripts/migration-prepare-target.sh b/scripts/migration-prepare-target.sh new file mode 100755 index 0000000..9a78888 --- /dev/null +++ b/scripts/migration-prepare-target.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Dieses Skript auf dem ZIEL-LXC ausfuehren. +# Es richtet die Ordnerstruktur und Preflight-Checks fuer den Hördle-Betrieb ein. + +APP_DIR="${APP_DIR:-/opt/hoerdle}" +APP_USER="${APP_USER:-docker}" +APP_GROUP="${APP_GROUP:-docker}" + +echo "== Prepare target LXC for Hördle ==" +echo "APP_DIR=$APP_DIR" +echo "APP_USER=$APP_USER" +echo + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Fehlend: $1" + exit 1 + fi +} + +require_cmd docker + +if ! docker compose version >/dev/null 2>&1; then + echo "docker compose Plugin fehlt." + exit 1 +fi + +echo "-- Docker Version --" +docker --version +docker compose version +echo + +echo "-- Verzeichnisse vorbereiten --" +mkdir -p "$APP_DIR"/{data,public/uploads,logs,backups} +chown -R "$APP_USER:$APP_GROUP" "$APP_DIR" +chmod 755 "$APP_DIR" +chmod 755 "$APP_DIR/data" "$APP_DIR/public" "$APP_DIR/public/uploads" +echo "Ordner vorbereitet." +echo + +echo "-- Speichercheck --" +df -h "$APP_DIR" +echo + +echo "-- Netzwerkcheck --" +if ! docker network inspect hoerdle_default >/dev/null 2>&1; then + echo "Docker-Netzwerk hoerdle_default fehlt, wird erstellt." + docker network create hoerdle_default >/dev/null +fi +echo "Netzwerk hoerdle_default bereit." +echo + +echo "-- .env Vorlage --" +if [ ! -f "$APP_DIR/.env" ]; then + if [ -f "$APP_DIR/.env.proxmox.example" ]; then + cp "$APP_DIR/.env.proxmox.example" "$APP_DIR/.env" + echo ".env aus .env.proxmox.example erstellt." + elif [ -f "$APP_DIR/.env.example" ]; then + cp "$APP_DIR/.env.example" "$APP_DIR/.env" + echo ".env aus .env.example erstellt." + else + echo "Keine Env-Vorlage gefunden. Bitte .env manuell erstellen." + fi +else + echo ".env existiert bereits, kein Override." +fi +echo + +echo "-- Abschluss --" +echo "Target-LXC vorbereitet. Nächster Schritt: Daten-Vorabkopie aus der Quelle." diff --git a/scripts/migration-rollback.sh b/scripts/migration-rollback.sh new file mode 100755 index 0000000..51d0312 --- /dev/null +++ b/scripts/migration-rollback.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Rollback-Hilfsskript: stoppt Ziel-Container und zeigt klare Operator-Hinweise. +# Die eigentliche Rückleitung des Traffics erfolgt im Nginx Proxy Manager. + +TARGET_HOST="${TARGET_HOST:-root@10.0.0.19}" +TARGET_APP_DIR="${TARGET_APP_DIR:-/opt/hoerdle}" +TARGET_COMPOSE_FILE="${TARGET_COMPOSE_FILE:-docker-compose.yml}" +SSH_OPTS="${SSH_OPTS:-}" + +echo "== Hördle Rollback-Hilfe ==" +echo "Zielhost: $TARGET_HOST" +echo + +echo "-- Ziel-App stoppen --" +ssh $SSH_OPTS "$TARGET_HOST" "cd '$TARGET_APP_DIR' && docker compose -f '$TARGET_COMPOSE_FILE' down" +echo + +echo "Naechste Schritte:" +echo "1) NPM Proxy Host sofort wieder auf alte VPS-Instanz umstellen." +echo "2) Externen Smoke-Test auf alter Instanz durchfuehren." +echo "3) Fehleranalyse im Ziel-LXC (docker compose logs)." diff --git a/scripts/migration-smoke-test.sh b/scripts/migration-smoke-test.sh new file mode 100755 index 0000000..874bf98 --- /dev/null +++ b/scripts/migration-smoke-test.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Smoke-Tests nach dem Umschalten auf Proxmox/NPM. +# Kann lokal oder auf beliebigem Host mit curl ausgefuehrt werden. + +BASE_URL="${1:-https://hoerdle.de}" +EXPECTED_STATUS="${EXPECTED_STATUS:-200}" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Fehlend: $1" + exit 1 + fi +} + +require_cmd curl +require_cmd grep + +echo "== Hördle Smoke-Test ==" +echo "BASE_URL=$BASE_URL" +echo + +check_http() { + local name="$1" + local url="$2" + local code + + code="$(curl -sS -o /dev/null -w "%{http_code}" "$url" || true)" + if [ "$code" = "$EXPECTED_STATUS" ]; then + echo "[OK] $name -> $code" + else + echo "[FAIL] $name -> $code (erwartet: $EXPECTED_STATUS)" + return 1 + fi +} + +check_body_contains() { + local name="$1" + local url="$2" + local needle="$3" + + if curl -fsS "$url" | grep -q "$needle"; then + echo "[OK] $name enthält '$needle'" + else + echo "[FAIL] $name enthält '$needle' nicht" + return 1 + fi +} + +FAILED=0 + +check_http "Homepage" "$BASE_URL/" || FAILED=1 +check_http "Daily API" "$BASE_URL/api/daily" || FAILED=1 + +# Prüft, ob Daily API als JSON zurückkommt. +check_body_contains "Daily API" "$BASE_URL/api/daily" "{" || FAILED=1 + +echo +if [ "$FAILED" -eq 0 ]; then + echo "Smoke-Test erfolgreich." +else + echo "Smoke-Test fehlgeschlagen." + exit 1 +fi