Zum Hauptinhalt springen
geprüft

Dieser Artikel ist Teil der Friends4-Enzyklopädie und wird nach Wiki-Prinzipien gepflegt, geprüft und versioniert.

Deployment / Update (Friends4)

Wenn du auf dem Server ein Update einspielst, nutze diese Reihenfolge: bash git pull npm install npm run preflight:dbenv npm run migrate:run npm run pm2:restart Für MultiInstanceRollouts ist die Reihenfolge verbindlich und deterministisch: 1. Neue Version deployen, aber noch keinen Traffic auf neue Instanzen leiten. 2. Genau einmal npm run migrate:run gegen die ZielDatenbank ausführen. 3. Erst danach AppProzesse/Instanzen mit der neuen Version starten (npm run pm2:restart oder Rolling Restart).

Kategorie: Friends4 DokumentationAutor: Friends4 Wiki-RedaktionVersion: 1Letzte Änderung:

Deployment / Update (Friends4)

Wenn du auf dem Server ein Update einspielst, nutze diese Reihenfolge:


git pull
npm install
npm run preflight:db-env
npm run migrate:run
npm run pm2:restart

Für **Multi-Instance-Rollouts** ist die Reihenfolge verbindlich und deterministisch:

1. Neue Version deployen, aber noch keinen Traffic auf neue Instanzen leiten.

2. Genau einmal `npm run migrate:run` gegen die Ziel-Datenbank ausführen.

3. Erst danach App-Prozesse/Instanzen mit der neuen Version starten (`npm run pm2:restart` oder Rolling Restart).

4. Danach Traffic umschalten.

So wird verhindert, dass alte und neue Instanzen gleichzeitig konkurrierende Runtime-DDL ausführen oder Requests gegen ein noch nicht migriertes Schema laufen.

Wenn `package.json` oder `knexfile.js` auf dem Server lokal verändert wurden, nutze stattdessen den Safe-Update-Befehl:


npm run update:safe

Dieser Befehl stasht lokale Änderungen an `package.json` und `knexfile.js` temporär, zieht dann `origin/main` per Fast-Forward und stellt die lokalen Dateien danach wieder her.

Zusätzlich führt er verpflichtend `npm run migrate:run` aus, damit neue Schema-Änderungen (z. B. `emergency_dispatch_state`) in allen Umgebungen vor dem Neustart eingespielt werden.

Vor Migrationen läuft zusätzlich ein produktionsnaher DB-Preflight (`npm run preflight:db-env`), der bei fehlenden Pflichtvariablen mit Exit-Code `1` stoppt.

> Wichtig: **Nicht** `npm install friends4` ausführen.

> Das würde versuchen, ein Paket namens `friends4` aus der npm-Registry zu laden (404).

Fehler beim `git pull` (lokale Änderung an `package.json` oder `knexfile.js`)

Wenn `git pull` mit dieser Meldung abbricht ...


error: Your local changes to the following files would be overwritten by merge:
  package.json
  knexfile.js

... dann wurde mindestens eine dieser Dateien lokal geändert (z. B. auf dem Server): `package.json` oder `knexfile.js`.

So kommst du sauber wieder auf den Stand aus Git:


git fetch origin
git restore --source=HEAD -- package.json knexfile.js
git pull --ff-only origin main

Wenn du die lokale Änderung doch behalten willst, sichere sie vorher:


git stash push -m "local package.json+knexfile" -- package.json knexfile.js
git pull --ff-only origin main

Wiki-Domain `wiki.friends4.de` in nginx aktivieren

Wenn `wiki.friends4.de` die nginx-Standardseite zeigt, fehlt meist der vHost-Symlink in `/etc/nginx/sites-enabled/`. Der vHost liegt im Repository unter `deploy/nginx/wiki.friends4.de.conf` und kann auf dem Server so installiert werden:


sudo LETSENCRYPT_EMAIL=admin@friends4.de npm run setup:wiki-nginx

Das Skript kopiert die Konfiguration nach `/etc/nginx/sites-available/wiki.friends4.de.conf`, setzt den Symlink `/etc/nginx/sites-enabled/wiki.friends4.de.conf`, entfernt bei Bedarf automatisch von Certbot in `/etc/nginx/sites-available/default` erzeugte `wiki.friends4.de`-`server`-Blöcke (inklusive Backup der Default-Datei), erstellt bei fehlendem Zertifikat ein Let's-Encrypt-Zertifikat per Webroot und lädt nginx nach erfolgreichem `nginx -t` neu.

Manuelle Alternative:


sudo certbot certonly --webroot -w /var/www/html -d wiki.friends4.de
sudo cp deploy/nginx/wiki.friends4.de.conf /etc/nginx/sites-available/wiki.friends4.de.conf
sudo ln -sfn /etc/nginx/sites-available/wiki.friends4.de.conf /etc/nginx/sites-enabled/wiki.friends4.de.conf
sudo nginx -t && sudo systemctl reload nginx

Prüfung nach dem Reload:


curl -I https://wiki.friends4.de/

PM2 einrichten (einmalig)


npm install -g pm2

Falls `npm install -g ...` mit `EACCES` fehlschlägt, fehlt dem User Schreibrecht auf `/usr/lib/node_modules`.

Dann **nicht** mit kaputten Workarounds wie `npm install /g ...` arbeiten (das führt zu `ENOENT` für `/g/package.json`), sondern eine der sauberen Varianten nutzen:


# Option A (empfohlen): Node über nvm pro User verwalten (kein sudo nötig)
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.nvm/nvm.sh
nvm install --lts
npm install -g pm2

# Option B: global mit sudo installieren (wenn bewusst systemweit gewünscht)
sudo npm install -g pm2

Korrekte Syntax für globales npm ist immer `-g` (mit Minus), **nicht** `/g`.

Produktionsbetrieb professioneller absichern

Für einen stabileren Betrieb sind diese Umgebungsvariablen jetzt sinnvoll:


MAX_ACTIVE_REQUESTS=2000
SOCKET_MAX_HTTP_BUFFER_BYTES=1000000
SOCKET_PING_TIMEOUT_MS=20000
SOCKET_PING_INTERVAL_MS=25000
HTTP_KEEP_ALIVE_TIMEOUT_MS=5000
HTTP_HEADERS_TIMEOUT_MS=60000
SHUTDOWN_TIMEOUT_MS=10000
FORCE_SECURE_COOKIES=true
FORCE_HTTPS=true
SOCKET_ADAPTER_MODE=redis
SOCKET_REDIS_URL=redis://127.0.0.1:6379/0
SECURITY_EVENT_WINDOW_MS=600000
SECURITY_EVENT_THRESHOLD=15
SECURITY_SIEM_WEBHOOK_URL=
SECURITY_SIEM_WEBHOOK_TOKEN=
HEALTHCHECK_CACHE_MS=15000
  • `MAX_ACTIVE_REQUESTS` aktiviert einfaches Load-Shedding: Bei Überlast liefert die App `503`, statt immer weiter Arbeit anzunehmen.
  • `SOCKET_MAX_HTTP_BUFFER_BYTES` begrenzt die maximale Größe von Socket.IO-Payloads.
  • `SOCKET_ADAPTER_MODE=redis` aktiviert den externen Socket-Adapter (kein In-Memory-Adapter in Produktion).
  • `SOCKET_REDIS_URL` (oder `SOCKET_REDIS_HOST` + `SOCKET_REDIS_PORT` etc.) verbindet Socket.IO mit Redis für stateless Mehrinstanzbetrieb.
  • `FORCE_SECURE_COOKIES=true` erzwingt sichere Cookies hinter HTTPS.
  • `FORCE_HTTPS=true` erzwingt HTTPS-Redirects in Produktion.
  • `HTTP_KEEP_ALIVE_TIMEOUT_MS` und `HTTP_HEADERS_TIMEOUT_MS` helfen, Ressourcen unter Last sauberer freizugeben.
  • `SECURITY_EVENT_*` steuert Security-Monitoring inkl. Schwellwerten.
  • `SECURITY_SIEM_WEBHOOK_*` aktiviert optionales Forwarding von Security-Events in SIEM/Incident-Pipelines.

Env-Dateien für Produktion

Die App lädt Env-Dateien jetzt in dieser Reihenfolge, falls vorhanden:


.env
.env.$NODE_ENV
.env.local
.env.$NODE_ENV.local

Für Produktion mit PM2 ist also z. B. eine dieser Varianten gültig:


NODE_ENV=production
cp .env.example .env.production.local

oder klassisch:


cp .env.example .env

Für den eingebauten MySQL-Migrationspfad (ohne Knex) zusätzlich setzen:


MIGRATIONS_USE_KNEX=false

Für lokale Uploads (ohne S3) sollte der Schalter explizit gesetzt sein, damit er in `.env` nicht fehlt:


S3_ENABLED=false

Wenn gar keine passende Env-Datei vorhanden ist, loggt die App beim Start ausdrücklich eine Warnung. Das ist besonders wichtig für den Mailversand.

Mindestwerte für Mailversand


APP_BASE_URL=https://friends4.de
EMAIL_VERIFICATION_REQUIRED=true
SMTP_HOST=smtp.your-server.de
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=info@friends4.de
SMTP_PASS=ECHTES_PASSWORT
SMTP_FROM=info@friends4.de
  • Port `587` braucht `SMTP_SECURE=false` und wird dann per STARTTLS aufgebaut.
  • Port `465` braucht `SMTP_SECURE=true`.
  • Port `25` sollte vermieden werden (bei Hetzner Cloud oft outbound geblockt/gedrosselt, um Spam zu verhindern).
  • Alternativ kann eine komplette URL wie `SMTP_URL=smtp://user:pass@smtp.example.com:587` verwendet werden.
  • Nach Änderungen an Env-Werten immer mit `pm2 restart friends4 --update-env` neu starten.

DNS: Google-Site-Verification (friends4.de)

Für die Google-Verifikation muss auf der Root-Domain (`@`) ein TXT-Record gesetzt sein:


Typ: TXT
Name/Host: @
Wert: google-site-verification=UK5HvPpRmkWM4_-oOxS1SJHU1gy7M7wtg07Ny6jZOdY
TTL: 300 (oder Provider-Standard)

Nach dem Setzen DNS-Propagation abwarten und dann in der Google Search Console verifizieren.

Health- und Readiness-Checks

Die App stellt jetzt zwei Betriebsendpunkte bereit:


curl http://127.0.0.1:3000/healthz
curl http://127.0.0.1:3000/readyz
  • `/healthz` zeigt, ob der Node-Prozess läuft, inklusive Uptime und aktiver Requests.
  • `/readyz` prüft zusätzlich Datenbank, Session-Store und den externen Socket-Adapter (in Produktion verpflichtend) und antwortet bei Problemen mit `503`.

Stateless Produktionsbetrieb (verpflichtend)

In `NODE_ENV=production` startet die App jetzt nur noch, wenn **beides** extern erreichbar ist:

  • Session-Store (MySQL) für `express-session`
  • Socket.IO-Adapter (`SOCKET_ADAPTER_MODE=redis`)

Wenn einer der Bausteine fehlt, bricht der Start bewusst mit Fehler ab – es gibt in Produktion keinen In-Memory-Fallback mehr.

Mehrinstanz-Betrieb mit Load-Balancer validieren

Für Socket.IO mit Long-Polling muss der Load-Balancer **Sticky Sessions** aktivieren, damit alle Polling-Requests einer Session auf derselben App-Instanz landen.

1) Instanz-ID sichtbar machen

Die App setzt automatisch den Header `X-Instance-Id` (Format: `hostname:pid`, optional via `INSTANCE_ID` überschreibbar). Darüber kann Affinity/Fallback gemessen werden.

2) Sticky-Session-Check (Socket.IO)


# optional: Zielsystem setzen
BASE_URL=https://friends4.de REQUESTS=10 npm run validate:multi-instance

Der Check führt einen Socket.IO-Polling-Handshake aus und wiederholt danach Polling-Requests mit demselben Cookie-Jar. Erwartung: **identische `X-Instance-Id`** über alle Requests.

3) Failover-Test

1. Erste Zielinstanz über Script-Ausgabe notieren (`X-Instance-Id`).

2. Diese Instanz gezielt aus dem Pool nehmen (Drain/Stop).

3. `npm run validate:multi-instance` erneut starten.

4. Erwartung:

- weiterhin `readyz=200`

- neue, stabile `X-Instance-Id`

- Socket.IO-Handshake/Polling erfolgreich

Nginx-Hinweis (Beispiel)


upstream friends4_backend {
    # Beispiel mit Hash-basiertem Sticky-Verhalten
    hash $cookie___Host_friends4_sid consistent;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3002 max_fails=3 fail_timeout=10s;
}

location /socket.io/ {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 60s;
    proxy_pass http://friends4_backend;
}

Wichtig: Je nach Ingress/LB (Nginx, HAProxy, Cloud-LB) unterscheiden sich Sticky-Mechanismen. Entscheidend ist immer, dass Socket.IO-Requests derselben Session nicht zwischen Instanzen springen.

Wiki-Subdomain aktivieren

Das integrierte Wiki wird vom bestehenden Friends4-Prozess ausgeliefert. Damit `https://wiki.friends4.de/` nicht auf die nginx-Default-Seite fällt, muss der dedizierte vHost aktiviert werden. Das Zertifikat wird vor dem Aktivieren des SSL-vHosts per Webroot erzeugt, damit `nginx -t` nicht an noch fehlenden Zertifikatsdateien scheitert:


sudo certbot certonly --webroot -w /var/www/html -d wiki.friends4.de
sudo cp /var/www/friends4/deploy/nginx/wiki.friends4.de.conf /etc/nginx/sites-available/wiki.friends4.de
sudo ln -sfn /etc/nginx/sites-available/wiki.friends4.de /etc/nginx/sites-enabled/wiki.friends4.de
sudo nginx -t && sudo systemctl reload nginx

Die Express-App erkennt den Host `wiki.friends4.de` und schreibt Requests, die noch nicht mit `/wiki` beginnen, intern auf `/wiki` um. Dadurch liefert `https://wiki.friends4.de/` direkt die Wiki-Startseite aus, während `/wiki`, `/wiki/...` und Asset-URLs nicht in Redirect-Schleifen geraten. Es darf kein separater PM2-Prozess für das Wiki gestartet werden; nach Änderungen reicht der bestehende Friends4-Prozess:


npm run pm2:restart
curl -I http://wiki.friends4.de/
curl -L https://wiki.friends4.de/ | grep -i "Friends4 Wiki"

App mit PM2 starten


npm run pm2:start

App nach Updates neu starten


npm run pm2:restart

Alternativ direkt:


pm2 restart friends4 --update-env

Falls PM2 noch keinen Prozess mit dem Namen `friends4` kennt, startet `npm run pm2:restart` automatisch neu.

Fehler `Access denied for user "root"@"localhost"`

Wenn im PM2-Log so etwas steht ...


ER_ACCESS_DENIED_NO_PASSWORD_ERROR
sqlMessage: "Access denied for user 'root'@'localhost'"

... läuft die App fast immer mit lokalen Fallback-Werten, weil die DB-Env-Variablen fehlen oder PM2 sie nicht neu eingelesen hat.

So prüfst du direkt auf dem Server:


pm2 env friends4 | egrep 'DB_|MYSQL'
npm run diagnose:db-env

Harter Preflight wie im Deployment-Skript:


npm run preflight:db-env

Dieser Check simuliert produktionsnahe Bedingungen (`--strict --as-production`) und bricht ab, wenn `DB_HOST`, `DB_USER`, `DB_PASSWORD` oder `DB_NAME` (inkl. unterstützter Alias-Varianten) fehlen.

Wichtig:

  • App akzeptiert jetzt neben `DB_*` auch Provider-Namen wie `MYSQLHOST`, `MYSQLPORT`, `MYSQLUSER`/`MYSQL_USERNAME`, `MYSQLPASSWORD`/`MYSQL_PASS`, `MYSQLDATABASE`/`MYSQL_DB` (zusätzlich auch `DATABASE_*`-Varianten wie `DATABASE_USERNAME`, `DATABASE_PASS`, `DATABASE_DB`).
  • Nach jeder Änderung an Env-Werten immer neu laden:

pm2 restart friends4 --update-env

In `NODE_ENV=production` startet die App absichtlich **nicht mehr**, wenn zwingende DB-Werte fehlen. Das verhindert unbeabsichtigte Verbindungen zu `root@localhost`.

Häufige Tippfehler

  • `9pm2` ❌
  • `pm2` ✅

Upload-Limits (Node + Reverse Proxy synchron halten)

Damit Frontend-Limits nicht höher sind als infra-seitig erlaubt, gelten aktuell diese Grenzen:

  • **Event-Cover Upload (`POST /events`, `multipart/form-data`, Feld `coverFile`)**: max. `5MB`.
  • **JSON/Data-URL Uploads (`POST /profile/media/:type`, `application/json`)**: max. `50mb` (Route-spezifisch).
  • **Multipart Video-Uploads (`POST /profile/media/:type`, `multipart/form-data`, Feld `media`)**: max. `1100MB`.
  • **Timeout pro Upload-Request**: `120s` Inaktivität.

Node/Express

  • Global:

- `REQUEST_BODY_LIMIT` (Fallback)

- `REQUEST_BODY_LIMIT_JSON`

- `REQUEST_BODY_LIMIT_URLENCODED`

  • Route-spezifisch für Profilmedien (in `src/routes/index.js`):

- JSON: `PROFILE_MEDIA_JSON_LIMIT`

- Multipart: `PROFILE_MEDIA_MULTIPART_LIMIT_BYTES`

Reverse Proxy (z. B. Nginx)

Setze den Proxy **mindestens** so hoch wie der größte Upload-Request, sonst blockt der Proxy vor der App (bei Events bereits >5MB relevant):


# Beispiel: etwas Puffer über App-Limit
client_max_body_size 1200m;
proxy_read_timeout 180s;
proxy_send_timeout 180s;

Empfehlung: Frontend-Maximum für Upload-UI <= kleinstes Limit aus:

1. Browser/Frontend-Validierung

2. Reverse-Proxy (`client_max_body_size`)

3. App-Limit (Route/Express)

Nur dann sind Fehlermeldungen konsistent reproduzierbar.

Nightly Date-Integrity-Check (Profil/Timeline/Gästebuch)

Um neue `500` durch ungültige Datumswerte zu verhindern, gibt es einen gezielten SQL-Check inkl. optionalem Auto-Fix:


npm run check:profile-dates
npm run fix:profile-dates
  • `check:profile-dates` prüft auf Zero-/Invalid-Dates in:

- `users.birthday`

- `profile_timeline_posts.created_at`

- `profile_guestbook_entries.created_at`

  • Exit-Code ist `1`, wenn invalides Datum gefunden wurde (praktisch für Monitoring/CI).
  • `fix:profile-dates` setzt ungültige Werte auf `NULL` (bei `birthday`) bzw. auf ein valides Fallback (`COALESCE(updated_at, CURRENT_TIMESTAMP)`) für `created_at`.

Beispiel für täglichen Cronjob (03:17 Uhr)


17 3 * * * cd /var/www/friends4 && /usr/bin/npm run fix:profile-dates >> /var/log/friends4-profile-date-check.log 2>&1

Damit läuft der Report nightly und bereinigt automatisch erkannte Invalid-Werte.

TLS / Zertifikat-Fehler (`NET::ERR_CERT_DATE_INVALID`)

Wenn Browser "Ihre Verbindung ist nicht privat" melden, liegt die Ursache fast immer **vor** der Node-App (Reverse-Proxy/LB/CDN-Zertifikat).

Schnellcheck vom Server aus


npm run check:tls-cert
# optional anderer Host/Port
bash scripts/check-tls-certificate.sh friends4.de 443

Der Check endet mit Exit-Code `1`, wenn das Zertifikat bereits abgelaufen ist oder innerhalb von `TLS_CERT_WARN_DAYS` (Default: `21`) ausläuft.

Sofortmaßnahme (typisch mit Certbot + Nginx)


sudo certbot renew --nginx
sudo nginx -t && sudo systemctl reload nginx

Monitoring-Empfehlung (täglich)


12 2 * * * cd /var/www/friends4 && /usr/bin/npm run -s check:tls-cert >> /var/log/friends4-tls-cert.log 2>&1

So fällt ein ablaufendes Zertifikat frühzeitig auf, bevor User einen TLS-Fehler sehen.

Hinweise

Nutze diesen Artikel als Orientierung. Bei Regeln gilt die aktuell freigegebene Version und bei Unsicherheit die Moderation.

Warnungen

Melde veraltete, missverständliche oder sicherheitsrelevante Informationen direkt über das Formular.

Diskussion

Die Diskussionsseite ist für konkrete Verbesserungsvorschläge gedacht: fehlende Abschnitte, unklare Formulierungen, Quellenhinweise oder Korrekturen. Sie ist keine Chatfunktion.

Bilder und Downloads

Optionale Medien, Grafiken oder Downloads können über strukturierte Daten und zukünftige Dateifreigaben ergänzt werden.