Ops: add Proxmox migration tooling and runbook
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.
This commit is contained in:
@@ -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=<BCRYPT_HASH_HERE>
|
||||||
|
TZ=Europe/Berlin
|
||||||
|
GOTIFY_URL=
|
||||||
|
GOTIFY_APP_TOKEN=
|
||||||
|
OPENROUTER_API_KEY=
|
||||||
|
|
||||||
|
# Optional Build Metadata
|
||||||
|
# APP_VERSION=v1.0.0
|
||||||
@@ -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.
|
||||||
Executable
+39
@@ -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"
|
||||||
Executable
+73
@@ -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."
|
||||||
Executable
+70
@@ -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."
|
||||||
Executable
+34
@@ -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."
|
||||||
Executable
+81
@@ -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."
|
||||||
Executable
+72
@@ -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."
|
||||||
Executable
+23
@@ -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)."
|
||||||
Executable
+65
@@ -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
|
||||||
Reference in New Issue
Block a user