diff --git a/.gitignore b/.gitignore index ac35ba4..ff8aab3 100644 --- a/.gitignore +++ b/.gitignore @@ -149,11 +149,8 @@ gitleaks gitleaks.tar.gz osv-scanner -# Sensitive data (DO NOT commit production sessions!) -# server/data/sessions.json - uncomment for production -public/uploads -public/uploads/* - -# Server data files (all data files to prevent overwriting) -server/data/ +# Sensitive / production data (DO NOT commit!) +server/data/** !server/data/.gitkeep +public/data/** +public/uploads/** diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index ae148a2..7778c60 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -119,7 +119,7 @@ pm2 save ## Notizen - **`.output/` ist NICHT im Git** (steht in `.gitignore`) -- **Produktivdaten SIND im Git** (werden versioniert) +- **Produktivdaten SOLLEN NICHT im Git sein** (werden per `.gitignore` ausgeschlossen und in Produktion außerhalb des Repos persistiert, z.B. unter `/var/lib/harheimertc`) - **Bei Deployment:** Immer Backup → Build → Restore - **Bei Problemen:** Script verwenden oder manuell Daten sichern diff --git a/deploy-production.sh b/deploy-production.sh index ebe5e8c..7bdc713 100755 --- a/deploy-production.sh +++ b/deploy-production.sh @@ -1,10 +1,55 @@ #!/bin/bash +set -euo pipefail + +# Immer im Repo-Verzeichnis arbeiten (wichtig für Backup/Restore mit relativen Pfaden) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" # Deployment Script für Harheimer TC Website # Sichert Produktivdaten vor dem Build und stellt sie danach wieder her echo "=== Harheimer TC 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 + +# Optional (empfohlen): Persistente Daten außerhalb des Git-Repos halten und per Symlink einbinden. +# Das verhindert zuverlässig, dass Git jemals Produktivdaten überschreibt. +DATA_ROOT="${DATA_ROOT:-/var/lib/harheimertc}" +mkdir -p "$DATA_ROOT" + +ensure_symlink_dir() { + local src="$1" # z.B. server/data + local target="$2" # z.B. /var/lib/harheimertc/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" +} + +echo "0. Ensuring persistent data directories (recommended)..." +ensure_symlink_dir "server/data" "$DATA_ROOT/server-data" +ensure_symlink_dir "public/data" "$DATA_ROOT/public-data" +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)..." @@ -14,9 +59,21 @@ rm -rf .backup mkdir -p .backup # Backup server data (JSON) und CSVs immer vom Dateisystem, nicht aus 'stash' -cp -a server/data .backup/data_backup 2>/dev/null || echo " No server/data to backup" +if [ -d server/data ]; then + cp -a server/data .backup/data_backup + echo " Backed up server/data -> .backup/data_backup" +else + echo "ERROR: server/data existiert nicht. Abbruch, damit wir keine Repo-Defaults ausrollen." + exit 1 +fi + mkdir -p .backup/public_data -cp -a public/data/*.csv .backup/public_data/ 2>/dev/null || echo " No public CSVs to backup" +if ls public/data/*.csv >/dev/null 2>&1; then + cp -a public/data/*.csv .backup/public_data/ + echo " Backed up public/data/*.csv -> .backup/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..." @@ -29,15 +86,11 @@ fi # Stash any local changes (including production data) echo " Stashing local changes..." -git stash push -m "Production deployment stash $(date)" +git stash push -m "Production deployment stash $(date)" || true # Pull latest changes echo " Pulling latest changes..." git pull -if [ $? -ne 0 ]; then - echo "ERROR: Git pull failed!" - exit 1 -fi # Reset any accidental changes from stash restore (should be none now) git reset --hard HEAD >/dev/null 2>&1 @@ -46,10 +99,6 @@ git reset --hard HEAD >/dev/null 2>&1 echo "" echo "3. Installing dependencies..." npm install -if [ $? -ne 0 ]; then - echo "ERROR: npm install failed!" - exit 1 -fi # 4. Remove old build (but keep data!) echo "" @@ -60,31 +109,36 @@ rm -rf .output echo "" echo "5. Building application..." npm run build -if [ $? -ne 0 ]; then - echo "ERROR: Build failed!" - exit 1 -fi # 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/data_backup ]; then - mkdir -p server/data - cp -a .backup/data_backup/. server/data/ -else - echo "No server/data to restore" +if [ ! -d .backup/data_backup ]; then + echo "ERROR: Backup-Verzeichnis .backup/data_backup fehlt. Abbruch." + exit 1 fi +mkdir -p server/data +cp -a .backup/data_backup/. server/data/ +echo " Restored server/data from backup." + # Stelle alle CSVs wieder her if ls .backup/public_data/*.csv >/dev/null 2>&1; then mkdir -p public/data cp -a .backup/public_data/*.csv public/data/ + echo " Restored public/data/*.csv from backup." 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 backup and stash echo "" echo "7. Cleaning up backup and stash..." diff --git a/harheimertc.config.cjs b/harheimertc.config.cjs index 98c13da..8785ae1 100644 --- a/harheimertc.config.cjs +++ b/harheimertc.config.cjs @@ -1,3 +1,11 @@ +// Load environment variables from .env (production secrets) +try { + // eslint-disable-next-line global-require + require('dotenv').config({ path: '/var/www/harheimertc/.env' }) +} catch (_e) { + // If dotenv isn't available or .env missing, continue (process.env may be set elsewhere) +} + module.exports = { apps: [{ name: 'harheimertc', @@ -10,7 +18,32 @@ module.exports = { max_memory_restart: '1G', env: { NODE_ENV: 'production', - PORT: 3100 + PORT: 3100, + // Secrets/Config (loaded from .env above, if present) + ENCRYPTION_KEY: process.env.ENCRYPTION_KEY, + JWT_SECRET: process.env.JWT_SECRET, + SMTP_HOST: process.env.SMTP_HOST, + SMTP_PORT: process.env.SMTP_PORT, + SMTP_USER: process.env.SMTP_USER, + SMTP_PASS: process.env.SMTP_PASS, + SMTP_FROM: process.env.SMTP_FROM, + SMTP_ADMIN: process.env.SMTP_ADMIN, + NUXT_PUBLIC_BASE_URL: process.env.NUXT_PUBLIC_BASE_URL, + COOKIE_SECURE: process.env.COOKIE_SECURE, + COOKIE_SAMESITE: process.env.COOKIE_SAMESITE, + CSP_ENABLED: process.env.CSP_ENABLED, + CSP_REPORT_ONLY: process.env.CSP_REPORT_ONLY, + CSP_VALUE: process.env.CSP_VALUE, + HIBP_ENABLED: process.env.HIBP_ENABLED, + HIBP_USER_AGENT: process.env.HIBP_USER_AGENT, + HIBP_TIMEOUT_MS: process.env.HIBP_TIMEOUT_MS, + HIBP_CACHE_TTL_MS: process.env.HIBP_CACHE_TTL_MS, + HIBP_FAIL_CLOSED: process.env.HIBP_FAIL_CLOSED, + AUDIT_LOG_ENABLED: process.env.AUDIT_LOG_ENABLED, + WEBAUTHN_ORIGIN: process.env.WEBAUTHN_ORIGIN, + WEBAUTHN_RP_ID: process.env.WEBAUTHN_RP_ID, + WEBAUTHN_RP_NAME: process.env.WEBAUTHN_RP_NAME, + WEBAUTHN_REQUIRE_UV: process.env.WEBAUTHN_REQUIRE_UV }, error_file: '/var/log/pm2/harheimertc-error.log', out_file: '/var/log/pm2/harheimertc-out.log', diff --git a/harheimertc.simple.cjs b/harheimertc.simple.cjs index 47b7663..c0f3c90 100644 --- a/harheimertc.simple.cjs +++ b/harheimertc.simple.cjs @@ -1,3 +1,11 @@ +// Load environment variables from .env (production secrets) +try { + // eslint-disable-next-line global-require + require('dotenv').config({ path: '/var/www/harheimertc/.env' }) +} catch (_e) { + // ignore +} + module.exports = { apps: [{ name: 'harheimertc', @@ -9,7 +17,9 @@ module.exports = { max_memory_restart: '1G', env: { NODE_ENV: 'production', - PORT: 3100 + PORT: 3100, + ENCRYPTION_KEY: process.env.ENCRYPTION_KEY, + JWT_SECRET: process.env.JWT_SECRET } }] } diff --git a/package-lock.json b/package-lock.json index afea33f..b9e429d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11374,9 +11374,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": {