#!/bin/bash set -euo pipefail # Deployment Script für Harheimer TC Test-Instanz # Verwendet separate Backup- und Data-Verzeichnisse # Immer im Repo-Verzeichnis arbeiten (wichtig für Backup/Restore mit relativen Pfaden) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" echo "=== Harheimer TC Test-Instanz Deployment ===" echo "" echo "Working directory: $(pwd)" echo "" if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo "ERROR: Dieses Script muss im Git-Repository ausgeführt werden (kein .git gefunden)." exit 1 fi # Separate Verzeichnisse für Test-Instanz DEPLOY_HOME="${DEPLOY_HOME:-${HOME:-/tmp}}" DATA_ROOT="${DATA_ROOT:-$DEPLOY_HOME/harheimertc.test-data}" BACKUP_ROOT="${BACKUP_ROOT:-$DEPLOY_HOME/harheimertc.test-backups}" mkdir -p "$DATA_ROOT" "$BACKUP_ROOT" ensure_symlink_dir() { local src="$1" # z.B. server/data local target="$2" # z.B. /var/lib/harheimertc.test/server-data mkdir -p "$(dirname "$src")" mkdir -p "$target" if [ -L "$src" ]; then return 0 fi if [ -d "$src" ]; then echo " Moving $src -> $target (first-time migration)" # Merge existing content into target cp -a "$src/." "$target/" || true rm -rf "$src" fi ln -s "$target" "$src" echo " Linked $src -> $target" } has_tracked_files_under() { local prefix="$1" # e.g. public/data # If any file is tracked under this path, symlinking the directory will break git operations git ls-files "$prefix" | head -n 1 | grep -q . } echo "0. Ensuring persistent data directories (recommended)..." # IMPORTANT: Only symlink server/data if it's not tracked by git. if has_tracked_files_under "server/data"; then echo " Skipping symlink for server/data (tracked files detected in git)." echo " Recommendation: remove server/data/** from git history and keep them only as production data." else ensure_symlink_dir "server/data" "$DATA_ROOT/server-data" fi # IMPORTANT: Only symlink public/data if it's not tracked by git. # Otherwise git will error with "path is beyond a symbolic link". if has_tracked_files_under "public/data"; then echo " Skipping symlink for public/data (tracked files detected in git)." echo " Recommendation: remove public/data/*.csv from git history and keep them only as production data." else ensure_symlink_dir "public/data" "$DATA_ROOT/public-data" fi ensure_symlink_dir "public/uploads" "$DATA_ROOT/public-uploads" echo "" # 1. BACKUP: Laufende Produktivdaten VOR allen Git-Operationen sichern echo "1. Backing up current production data (pre-git)..." # Human readable timestamp (lokal) BACKUP_TS="$(date +"%Y-%m-%d_%H-%M-%S")" BACKUP_DIR="$BACKUP_ROOT/backup_$BACKUP_TS" mkdir -p "$BACKUP_DIR" echo " Backup directory: $BACKUP_DIR" # Backup server data (JSON) und CSVs immer vom Dateisystem, nicht aus 'stash' if [ -d server/data ]; then cp -a server/data "$BACKUP_DIR/server-data" echo " Backed up server/data -> $BACKUP_DIR/server-data" else echo "ERROR: server/data existiert nicht. Abbruch, damit wir keine Repo-Defaults ausrollen." exit 1 fi if ls public/data/*.csv >/dev/null 2>&1; then mkdir -p "$BACKUP_DIR/public-data" cp -a public/data/*.csv "$BACKUP_DIR/public-data/" echo " Backed up public/data/*.csv -> $BACKUP_DIR/public-data/" else echo " No public CSVs to backup (public/data/*.csv not found)" fi # 2. Handle local changes and Git Pull echo "2. Handling local changes and pulling latest from git..." # Check if there are merge conflicts first if [ -n "$(git status --porcelain | grep '^UU\|^AA\|^DD')" ]; then echo " Resolving existing merge conflicts..." git reset --hard HEAD fi # Ensure git operations can run even if we have untracked local artifacts (e.g. backups/) # We avoid `git stash` (breaks with symlinked tracked paths). Instead, hard-reset and clean safely. echo " Resetting working tree to HEAD (production data will be restored from backup)..." git reset --hard HEAD echo " Cleaning untracked files (excluding data dirs/backups/.env)..." git clean -fd \ -e server/data \ -e public/data \ -e public/uploads \ -e backups \ -e .env || true # Pull latest changes echo " Pulling latest changes..." git pull # Reset any accidental changes from stash restore (should be none now) git reset --hard HEAD >/dev/null 2>&1 # WICHTIG: Entferne public/data Dateien aus Git-Index, falls sie getrackt sind # (Sie sollten in .gitignore sein, aber falls sie historisch getrackt wurden) if git ls-files --error-unmatch public/data/*.csv >/dev/null 2>&1; then echo " WARNING: public/data/*.csv Dateien sind im Git getrackt!" echo " Entferne sie aus dem Git-Index (Dateien bleiben erhalten)..." git rm --cached public/data/*.csv 2>/dev/null || true fi # 3. Install dependencies echo "" echo "3. Installing dependencies..." npm install # 4. Remove old build (but keep data!) echo "" echo "4. Cleaning build artifacts..." # Sicherstellen, dass .output vollständig gelöscht wird if [ -d ".output" ]; then echo " Removing .output directory..." rm -rf .output # Prüfen, ob wirklich gelöscht wurde if [ -d ".output" ]; then echo "WARNING: .output konnte nicht vollständig gelöscht werden. Versuche erneut..." sleep 2 rm -rf .output if [ -d ".output" ]; then echo "ERROR: .output konnte auch nach erneutem Versuch nicht gelöscht werden!" echo "Bitte manuell prüfen und löschen: rm -rf .output" exit 1 fi fi echo " ✓ .output gelöscht" fi # Auch .nuxt Cache löschen für sauberen Build if [ -d ".nuxt" ]; then echo " Removing .nuxt cache..." rm -rf .nuxt echo " ✓ .nuxt gelöscht" fi # Prüfe, ob node_modules vorhanden ist (für npm run build) if [ ! -d "node_modules" ]; then echo "" echo "WARNING: node_modules fehlt. Installiere Dependencies..." npm install fi # 5. Build echo "" echo "5. Building application..." echo " Running: npm run build" echo " (This may take a few minutes...)" # Clean-Build: Stelle sicher, dass node_modules aktuell ist echo " Checking dependencies..." if [ ! -f "node_modules/.package-lock.json" ] && [ ! -f "package-lock.json" ]; then echo " WARNING: package-lock.json fehlt. Führe npm install aus..." npm install fi # Build mit expliziter Fehlerbehandlung und Output-Capture BUILD_OUTPUT=$(npm run build 2>&1) BUILD_EXIT_CODE=$? # Zeige Build-Output echo "$BUILD_OUTPUT" if [ "$BUILD_EXIT_CODE" -ne 0 ]; then echo "" echo "ERROR: Build fehlgeschlagen mit Exit-Code $BUILD_EXIT_CODE" echo "Bitte prüfen Sie die Build-Ausgabe oben auf Fehler." exit 1 fi # Prüfe auf Warnungen im Build-Output, die auf Probleme hinweisen if echo "$BUILD_OUTPUT" | grep -qi "error\|failed\|missing"; then echo "" echo "WARNING: Build-Output enthält möglicherweise Fehler oder Warnungen." echo "Bitte prüfen Sie die Ausgabe oben." fi # Prüfe, ob der Build erfolgreich war - mehrere Checks echo "" echo " Verifying build output..." BUILD_FAILED=0 # Check 1: _nuxt Verzeichnis if [ ! -d ".output/public/_nuxt" ]; then echo "ERROR: .output/public/_nuxt Verzeichnis fehlt!" BUILD_FAILED=1 else NUXT_FILES=$(find .output/public/_nuxt -type f 2>/dev/null | wc -l) echo " ✓ .output/public/_nuxt vorhanden ($NUXT_FILES Dateien)" if [ "$NUXT_FILES" -eq 0 ]; then echo "ERROR: .output/public/_nuxt ist leer!" BUILD_FAILED=1 else # Prüfe, ob wichtige Dateien vorhanden sind JS_FILES=$(find .output/public/_nuxt -name "*.js" 2>/dev/null | wc -l) CSS_FILES=$(find .output/public/_nuxt -name "*.css" 2>/dev/null | wc -l) echo " ✓ JS-Dateien: $JS_FILES, CSS-Dateien: $CSS_FILES" if [ "$JS_FILES" -eq 0 ]; then echo "ERROR: Keine JS-Dateien in .output/public/_nuxt gefunden!" BUILD_FAILED=1 fi fi fi # Check 1b: _nuxt/builds Verzeichnis (für Meta-Dateien) if [ -d ".output/public/_nuxt/builds" ]; then BUILD_META_FILES=$(find .output/public/_nuxt/builds -type f 2>/dev/null | wc -l) echo " ✓ .output/public/_nuxt/builds vorhanden ($BUILD_META_FILES Meta-Dateien)" else echo "WARNING: .output/public/_nuxt/builds fehlt (kann in manchen Nuxt-Versionen normal sein)" fi # Check 2: Server index.mjs if [ ! -f ".output/server/index.mjs" ]; then echo "ERROR: .output/server/index.mjs fehlt!" BUILD_FAILED=1 else echo " ✓ .output/server/index.mjs vorhanden" fi # Check 3: Public Verzeichnis if [ ! -d ".output/public" ]; then echo "ERROR: .output/public Verzeichnis fehlt!" BUILD_FAILED=1 else echo " ✓ .output/public vorhanden" fi # Check 4: Server Verzeichnis if [ ! -d ".output/server" ]; then echo "ERROR: .output/server Verzeichnis fehlt!" BUILD_FAILED=1 else echo " ✓ .output/server vorhanden" fi if [ "$BUILD_FAILED" -eq 1 ]; then echo "" echo "ERROR: Build-Verifikation fehlgeschlagen!" echo "Bitte führen Sie manuell aus:" echo " rm -rf .output .nuxt" echo " npm run build" exit 1 fi echo " ✓ Build erfolgreich verifiziert" # 6. Restore Production Data (überschreibe Repo-Defaults mit Backup) echo "" echo "6. Restoring production data..." # Stelle server/data vollständig wieder her (inkl. config.json, users.json, news.json, sessions.json, members.json, membership-applications) if [ ! -d "$BACKUP_DIR/server-data" ]; then echo "ERROR: Backup-Verzeichnis $BACKUP_DIR/server-data fehlt. Abbruch." exit 1 fi # WICHTIG: Prüfe, ob server/data ein Symlink ist (z.B. auf Backup-Verzeichnis) # Falls ja, entferne den Symlink, damit wir in das echte Verzeichnis kopieren können if [ -L "server/data" ]; then echo " WARNING: server/data ist ein Symlink. Entferne Symlink..." rm "server/data" fi # Stelle sicher, dass server/data ein echtes Verzeichnis ist if [ ! -d "server/data" ]; then mkdir -p server/data fi # Kopiere Daten vom Backup (verwende -L, um Symlinks zu folgen, falls nötig) cp -aL "$BACKUP_DIR/server-data/." server/data/ echo " Restored server/data from backup ($BACKUP_DIR/server-data)." # Stelle alle CSVs wieder her if ls "$BACKUP_DIR/public-data"/*.csv >/dev/null 2>&1; then mkdir -p public/data # WICHTIG: Überschreibe auch Dateien, die aus dem Git-Repository kommen # Verwende cp mit -f (force) um sicherzustellen, dass Backup-Dateien Vorrang haben for csv_file in "$BACKUP_DIR/public-data"/*.csv; do filename=$(basename "$csv_file") # Überschreibe explizit, auch wenn Datei bereits existiert cp -f "$csv_file" "public/data/$filename" # Stelle sicher, dass die Datei wirklich überschrieben wurde if [ -f "public/data/$filename" ]; then # Prüfe, ob die Datei wirklich vom Backup kommt (Größenvergleich) backup_size=$(stat -f%z "$csv_file" 2>/dev/null || stat -c%s "$csv_file" 2>/dev/null || echo "0") restored_size=$(stat -f%z "public/data/$filename" 2>/dev/null || stat -c%s "public/data/$filename" 2>/dev/null || echo "0") if [ "$backup_size" = "$restored_size" ] && [ "$backup_size" != "0" ]; then echo " ✓ Restored public/data/$filename from backup ($backup_size bytes)" else echo " ⚠ WARNING: public/data/$filename Größe stimmt nicht überein (Backup: $backup_size, Restored: $restored_size)" fi else echo " ❌ ERROR: Konnte public/data/$filename nicht wiederherstellen!" fi done echo " ✓ All public/data/*.csv files restored from backup ($BACKUP_DIR/public-data)." # Zusätzliche Sicherheit: Entferne public/data Dateien aus Git-Index, falls sie getrackt sind # (nach dem Restore, damit sie nicht beim nächsten git reset überschrieben werden) if git ls-files --error-unmatch public/data/*.csv >/dev/null 2>&1; then echo " WARNING: public/data/*.csv Dateien sind noch im Git getrackt!" echo " Entferne sie aus dem Git-Index (Dateien bleiben erhalten)..." git rm --cached public/data/*.csv 2>/dev/null || true echo " ✓ public/data/*.csv aus Git-Index entfernt" fi else echo " No public CSVs to restore" fi # Sanity Check: users.json muss existieren und darf nicht leer sein if [ ! -s server/data/users.json ]; then echo "ERROR: server/data/users.json fehlt oder ist leer nach Restore. Abbruch." exit 1 fi # 7. Cleanup (Backups werden bewusst behalten) echo "" echo "7. Keeping backups in $BACKUP_ROOT (no git stash used)." # 8. Restart PM2 echo "" echo "8. Restarting PM2..." # Prüfe, ob PM2 installiert ist if ! command -v pm2 &> /dev/null; then echo "ERROR: PM2 ist nicht installiert oder nicht im PATH!" echo "Bitte installieren Sie PM2: npm install -g pm2" exit 1 fi # Funktion zum Starten/Neustarten einer PM2-Instanz restart_pm2_instance() { local instance_name=$1 local config_file="$SCRIPT_DIR/harheimertc.test.config.cjs" # Prüfe, ob die Konfigurationsdatei existiert if [ ! -f "$config_file" ]; then echo " ERROR: PM2-Konfigurationsdatei nicht gefunden: $config_file" return 1 fi if ! pm2 describe "$instance_name" &> /dev/null; then echo " WARNING: PM2-Prozess '$instance_name' existiert nicht." echo " Versuche, den Prozess zu starten..." echo " Verwende Konfiguration: $config_file" if pm2 start "$config_file" --update-env; then echo " ✓ PM2-Prozess '$instance_name' gestartet" return 0 else echo " ERROR: Konnte PM2-Prozess '$instance_name' nicht starten." echo " Bitte manuell prüfen: pm2 logs $instance_name" return 1 fi else echo " Restarting $instance_name with --update-env..." if pm2 restart "$instance_name" --update-env; then echo " ✓ PM2-Prozess '$instance_name' neu gestartet" return 0 else echo " ERROR: PM2-Restart für '$instance_name' fehlgeschlagen!" return 1 fi fi } # Starte/Neustarte Test-Instanz if ! restart_pm2_instance "harheimertc.test"; then echo "" echo "WARNING: PM2-Instanz konnte nicht gestartet werden. Bitte manuell prüfen:" echo " pm2 status" echo " pm2 logs harheimertc.test" fi # Prüfe, ob der Prozess läuft sleep 2 echo "" echo " Checking PM2 instance status..." if pm2 describe harheimertc.test | grep -q "online"; then echo " ✓ PM2-Prozess 'harheimertc.test' läuft (online)" else echo " WARNING: PM2-Prozess 'harheimertc.test' ist nicht online. Prüfe Logs: pm2 logs harheimertc.test" fi echo "" echo "=== Test-Instanz Deployment completed successfully! ===" echo "The test application is now running with the latest code and your test data preserved." echo "" echo "Useful commands:" echo " pm2 logs harheimertc.test # View logs" echo " pm2 status # View status" echo " pm2 restart harheimertc.test # Restart manually"