Der Admin-Benutzer wird immer in der Datenbank angelegt, bevor versucht wird, die generierten Zugangsdaten in ZUGANGSDATEN.md zu schreiben. Schlug dieser Schreibvorgang fehl (z. B. EACCES, weil die per Bind-Mount eingebundene Host-Datei root gehört und der App-Container als nicht-root läuft), flog bisher eine ungefangene Exception – das Skript brach ab, BEVOR das Passwort irgendwo protokolliert wurde. Der Admin existierte dann in der DB, aber niemand kannte sein Passwort mehr. appendFileSync() läuft jetzt in einem try/catch; schlägt der Dateizugriff fehl, werden E-Mail und Passwort stattdessen direkt ins Log geschrieben, und das Skript beendet sich weiterhin mit Exitcode 0 (die eigentlich kritische Operation – der DB-Insert – ist ja bereits erfolgreich). Lokal verifiziert: Skript bricht bei einer nicht beschreibbaren ZUGANGSDATEN.md nicht mehr ab, Zugangsdaten landen im Log, zweiter Lauf bleibt idempotent. DEPLOYMENT.md: Schritt 2 ergänzt um "chmod 666 ZUGANGSDATEN.md" (der eigentliche Auslöser), neue Fehlerbehebung-Einträge für ECONNREFUSED-Startreihenfolge und diesen EACCES-Fall inkl. Recovery-Anleitung für einen bereits verwaisten Admin-Account.
242 lines
8.7 KiB
Markdown
242 lines
8.7 KiB
Markdown
# Deployment auf einem neuen Server
|
||
|
||
Schritt-für-Schritt-Anleitung, um Canteen Menu auf einem frischen
|
||
(Linux-)Server produktiv zu betreiben. Die App läuft vollständig selbst
|
||
gehostet (PostgreSQL + eigene Auth), einzige optionale externe
|
||
Abhängigkeit ist DeepL für automatische Übersetzungen.
|
||
|
||
## Voraussetzungen
|
||
|
||
- Ein Server (VM oder Root-Server) mit Docker und Docker Compose v2
|
||
(`docker compose version` sollte funktionieren; auf den meisten
|
||
aktuellen Distributionen per `apt install docker.io
|
||
docker-compose-v2` bzw. dem [offiziellen Docker-Install-Skript](https://docs.docker.com/engine/install/)
|
||
installierbar).
|
||
- Eine Domain, deren DNS-A/AAAA-Eintrag auf die Server-IP zeigt (z. B.
|
||
`menu.example.de`). Ohne eigene Domain funktioniert die App auch
|
||
unter der reinen Server-IP, dann aber ohne gültiges HTTPS-Zertifikat
|
||
und ohne funktionierende Passkeys (siehe unten).
|
||
- Mindestens 1 GB RAM frei (App + PostgreSQL sind genügsam, für ~250
|
||
Mitarbeiter reicht die kleinste übliche VM-Größe).
|
||
|
||
## 1. Repository auf den Server holen
|
||
|
||
```bash
|
||
git clone https://github.com/<dein-fork-oder-repo>/canteen-menu.git
|
||
cd canteen-menu
|
||
```
|
||
|
||
(Oder per `git pull`/`scp`, falls kein direkter Git-Zugriff vom Server
|
||
aus gewünscht ist.)
|
||
|
||
## 2. Konfiguration anlegen
|
||
|
||
```bash
|
||
cp .env.example .env.local
|
||
```
|
||
|
||
`.env.local` mit einem Editor öffnen und ausfüllen:
|
||
|
||
| Variable | Was rein muss |
|
||
| --- | --- |
|
||
| `LOCAL_PG_PASSWORD` | Ein neues, zufälliges Passwort **nur aus Buchstaben/Zahlen** (z. B. `openssl rand -hex 24`) – wird beim ersten Start zum Datenbank-Passwort des mitgelieferten PostgreSQL-Containers. Zeichen wie `#`, `$`, Leerzeichen oder Anführungszeichen können beim Einlesen der `.env`-Datei zu Problemen führen (`#` beginnt z. B. einen Kommentar) |
|
||
| `APP_URL` | Die öffentliche URL mit Schema, z. B. `https://menu.example.de` (auch wenn HTTPS erst in Schritt 5 eingerichtet wird – schon jetzt korrekt eintragen, da daraus u. a. die Passkey-Domain abgeleitet wird) |
|
||
| `SITE_TITLE` | Optional, Titel im Browser-Tab/Überschrift ohne Logo |
|
||
| `ADMIN_EMAIL` | Optional, E-Mail des automatisch angelegten Admin-Erstnutzers. Ohne Angabe: `admin@<Hostname aus APP_URL>` |
|
||
| `DEEPL_API_KEY` | Optional, nur für automatische Übersetzung |
|
||
|
||
`DATABASE_URL` in `.env.local` **nicht** anpassen – die wird nur für
|
||
den Betrieb ohne Docker gebraucht (siehe README) und beim
|
||
Docker-Compose-Start ignoriert.
|
||
|
||
Zusätzlich eine leere Datei für die automatisch generierten
|
||
Admin-Zugangsdaten anlegen, da sie per Bind-Mount in den Container
|
||
eingebunden wird. Der App-Container läuft als nicht-root-Nutzer –
|
||
damit er die Datei beschreiben darf, muss sie für alle schreibbar sein:
|
||
|
||
```bash
|
||
touch ZUGANGSDATEN.md
|
||
chmod 666 ZUGANGSDATEN.md
|
||
```
|
||
|
||
## 3. App starten
|
||
|
||
**Wichtig:** `.env.local` muss vollständig ausgefüllt sein, *bevor*
|
||
dieser Befehl zum ersten Mal läuft – und danach immer mit
|
||
`--env-file .env.local` aufgerufen werden. PostgreSQL setzt sein
|
||
Passwort nur beim allerersten Start auf einem leeren Datenverzeichnis;
|
||
läuft der Befehl vorher schon einmal ohne (oder mit einem anderen)
|
||
`--env-file`, landet ein falsches Passwort dauerhaft im Datenbank-Volume
|
||
(Symptom: `password authentication failed for user "postgres"` in den
|
||
App-Logs, siehe [Fehlerbehebung](#fehlerbehebung)).
|
||
|
||
```bash
|
||
docker compose --env-file .env.local up -d --build
|
||
```
|
||
|
||
Das baut das Docker-Image, startet PostgreSQL und die App, wendet beim
|
||
ersten Start automatisch das Datenbankschema an und legt – da die
|
||
Datenbank leer ist – einen einzigen Admin-Benutzer mit zufällig
|
||
generiertem Passwort an.
|
||
|
||
Kurz prüfen, ob beides läuft:
|
||
|
||
```bash
|
||
docker compose ps
|
||
docker compose logs app --tail 30
|
||
```
|
||
|
||
In den Logs sollte u. a. `bootstrap-admin – Admin angelegt: ...`
|
||
stehen.
|
||
|
||
## 4. Admin-Zugangsdaten abholen
|
||
|
||
```bash
|
||
cat ZUGANGSDATEN.md
|
||
```
|
||
|
||
Enthält E-Mail-Adresse und Passwort des automatisch angelegten
|
||
Admin-Kontos. Direkt nach dem ersten Login unter „Mein Konto“ das
|
||
Passwort ändern und optional einen Passkey registrieren.
|
||
|
||
Die App ist jetzt unter `http://<server-ip>:3000` erreichbar – für den
|
||
produktiven Betrieb fehlt noch HTTPS/die Domain (nächster Schritt).
|
||
|
||
## 5. Reverse-Proxy + HTTPS einrichten
|
||
|
||
Die App selbst spricht nur HTTP auf Port 3000; TLS/HTTPS übernimmt ein
|
||
Reverse-Proxy davor. Am einfachsten mit [Caddy](https://caddyserver.com/)
|
||
(automatisches Let’s-Encrypt-Zertifikat, keine manuelle Zertifikats-
|
||
verwaltung nötig):
|
||
|
||
```bash
|
||
sudo apt install caddy # oder: siehe caddyserver.com/docs/install
|
||
```
|
||
|
||
`/etc/caddy/Caddyfile`:
|
||
|
||
```
|
||
menu.example.de {
|
||
reverse_proxy localhost:3000
|
||
}
|
||
```
|
||
|
||
```bash
|
||
sudo systemctl reload caddy
|
||
```
|
||
|
||
Caddy holt beim ersten Aufruf automatisch ein Zertifikat und leitet
|
||
danach HTTP→HTTPS um. Alternative: nginx + certbot, falls bereits ein
|
||
nginx-Setup existiert (Standard-`proxy_pass`-Konfiguration auf
|
||
`http://127.0.0.1:3000`, Zertifikat separat per `certbot --nginx`).
|
||
|
||
Danach in `.env.local` sicherstellen, dass `APP_URL=https://menu.example.de`
|
||
gesetzt ist (Schema `https`, keine Portangabe), und die App neu
|
||
starten, falls diese Variable nach Schritt 2 noch geändert wurde:
|
||
|
||
```bash
|
||
docker compose --env-file .env.local up -d
|
||
```
|
||
|
||
## 6. Danach
|
||
|
||
- Passkeys funktionieren erst mit korrekt gesetzter `APP_URL` (RP-Domain
|
||
wird direkt daraus abgeleitet) und über HTTPS (Passkeys verlangen eine
|
||
sichere Origin, `localhost` ausgenommen).
|
||
- Weitere Kantinenmitarbeiter/Admins unter „Benutzer“ (nur als Admin
|
||
sichtbar) anlegen.
|
||
- Essenszeiten, Gerichte, Allergene/Zusatzstoffe sind zu Beginn leer und
|
||
werden im Kantinen-Bereich gepflegt.
|
||
- Logo/Favicon/Farben unter „Design“ (nur Admin) anpassen.
|
||
|
||
## Fehlerbehebung
|
||
|
||
### `password authentication failed for user "postgres"` in den App-Logs
|
||
|
||
Ursache: Das Docker-Volume der Datenbank wurde bereits mit einem
|
||
anderen Passwort initialisiert, als aktuell in `.env.local` steht (z. B.
|
||
weil `docker compose up` einmal ohne `--env-file .env.local` lief, bevor
|
||
das Passwort gesetzt war, oder `LOCAL_PG_PASSWORD` nachträglich geändert
|
||
wurde – PostgreSQL übernimmt das Passwort nur beim allerersten Start).
|
||
|
||
Fix, solange noch keine echten Daten in der Datenbank sind (bei einem
|
||
frischen Deployment immer der Fall):
|
||
|
||
```bash
|
||
docker compose --env-file .env.local down
|
||
docker volume rm canteen-menu_pgdata
|
||
docker compose --env-file .env.local up -d --build
|
||
docker compose logs app --tail 30
|
||
```
|
||
|
||
Existieren bereits echte Daten, stattdessen das Passwort direkt im
|
||
laufenden Datenbank-Container an `.env.local` angleichen, statt das
|
||
Volume zu löschen:
|
||
|
||
```bash
|
||
docker compose exec db psql -U postgres -c "ALTER USER postgres WITH PASSWORD '<Wert aus LOCAL_PG_PASSWORD>';"
|
||
docker compose --env-file .env.local up -d
|
||
```
|
||
|
||
### `Error: connect ECONNREFUSED ...:5432` beim Start
|
||
|
||
Normal beim allerersten Start: Der App-Container versucht sich zu
|
||
verbinden, bevor PostgreSQL im `db`-Container fertig hochgefahren ist.
|
||
Dank `restart: unless-stopped` startet der App-Container automatisch
|
||
neu und verbindet sich, sobald die Datenbank bereit ist (kann ein paar
|
||
Sekunden bis wenige Minuten dauern). Mit `docker compose logs app -f`
|
||
verfolgen – taucht der Fehler dauerhaft (mehrere Minuten) weiter auf,
|
||
`docker compose ps` prüfen, ob der `db`-Container tatsächlich dauerhaft
|
||
läuft.
|
||
|
||
### `EACCES: permission denied, open '/app/ZUGANGSDATEN.md'`
|
||
|
||
Die Datei wurde ohne Schreibrecht für alle angelegt (Schritt 2 oben,
|
||
`chmod 666` fehlte). Der Admin-Benutzer wurde in diesem Fall trotzdem
|
||
in der Datenbank angelegt, nur die Zugangsdaten konnten nicht in die
|
||
Datei geschrieben werden – sie stehen dann stattdessen im Log
|
||
(`docker compose logs app` nach `bootstrap-admin – Admin-Passwort:`
|
||
durchsuchen). Für zukünftige Deployments zusätzlich die Berechtigung
|
||
korrigieren:
|
||
|
||
```bash
|
||
chmod 666 ZUGANGSDATEN.md
|
||
```
|
||
|
||
Falls das Passwort auch im Log nicht mehr auffindbar ist (z. B. weil
|
||
Logs schon rotiert wurden), Admin-Konto neu anlegen lassen:
|
||
|
||
```bash
|
||
docker compose exec db psql -U postgres -d canteen_menu -c "delete from users;"
|
||
chmod 666 ZUGANGSDATEN.md
|
||
docker compose restart app
|
||
docker compose logs app --tail 20
|
||
cat ZUGANGSDATEN.md
|
||
```
|
||
|
||
## Updates einspielen
|
||
|
||
```bash
|
||
git pull
|
||
docker compose --env-file .env.local up -d --build
|
||
```
|
||
|
||
Neue Datenbank-Migrationen (`db/migrations/*.sql`) werden beim Start
|
||
automatisch und idempotent angewendet, bestehende Daten bleiben
|
||
erhalten.
|
||
|
||
## Backup
|
||
|
||
Zwei Docker-Volumes enthalten alle persistenten Daten:
|
||
|
||
- `canteen-menu_pgdata` – die komplette PostgreSQL-Datenbank
|
||
- `canteen-menu_branding_data` – hochgeladenes Logo/Favicon
|
||
|
||
```bash
|
||
docker run --rm -v canteen-menu_pgdata:/data -v "$PWD":/backup alpine \
|
||
tar czf /backup/pgdata-backup.tar.gz -C /data .
|
||
```
|
||
|
||
Für ein reines Datenbank-Backup eignet sich auch `docker compose exec db
|
||
pg_dump -U postgres canteen_menu > backup.sql`.
|