From 6478e87fb3e24a48d4e184cc1ec0ab06ccf270e6 Mon Sep 17 00:00:00 2001
From: Pascal Scholz
Date: Mon, 21 Sep 2026 22:00:02 +0200
Subject: [PATCH] Restructure deploy guide: generate admin credentials from the
built image
The ad-hoc `npm install bcryptjs` in a throwaway container hit npm's known
"Tracker 'idealTree' already exists" bug. Reorder Teil B so the admin
service is built first (a proper `npm ci`, the same path already used for
the real deployment), then generate the password hash and session secret
via `docker compose run --rm admin ...` against that image instead.
---
DEPLOYMENT_ADMIN.md | 88 +++++++++++++++++++++++++++------------------
1 file changed, 53 insertions(+), 35 deletions(-)
diff --git a/DEPLOYMENT_ADMIN.md b/DEPLOYMENT_ADMIN.md
index 9e1215a..0b14520 100644
--- a/DEPLOYMENT_ADMIN.md
+++ b/DEPLOYMENT_ADMIN.md
@@ -72,34 +72,15 @@ cd ~/sites/larpaso-website
git pull
```
-### B2. Login-Zugangsdaten erzeugen
-Der Server selbst braucht dafür kein Node/npm installiert zu haben – das läuft in einem
-kurzlebigen Docker-Container (Node steckt nur in den Containern, nicht auf dem Host):
+### B2. Platzhalter für `~/stack/admin.env` anlegen (NICHT ins Git-Repo!)
+Erstmal nur eine leere Datei, damit Docker Compose in B3/B5 nicht meckert, dass sie fehlt.
+Die echten Werte kommen in B6 rein.
```bash
-docker run --rm -e PW='dein-neues-passwort' node:22-alpine sh -c \
- "npm install --no-save bcryptjs >/dev/null 2>&1 && \
- node -e \"console.log(require('bcryptjs').hashSync(process.env.PW, 12))\""
-```
-Gibt einen bcrypt-Hash aus (beginnt mit `$2a$` oder `$2b$`) – merken/kopieren.
-Zufälliges Session-Secret erzeugen:
-```bash
-docker run --rm node:22-alpine node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
-```
-Auch kopieren. (Beide Befehle laden einmalig das `node:22-alpine`-Image, das ohnehin für
-den Website-Build gebraucht wird – geht beim zweiten Mal deutlich schneller.)
-
-### B3. `~/stack/admin.env` anlegen (NICHT ins Git-Repo!)
-```bash
-cat > ~/stack/admin.env <<'EOF'
-NODE_ENV=production
-ADMIN_USERNAME=pascal
-ADMIN_PASSWORD_HASH=
-SESSION_SECRET=
-EOF
+touch ~/stack/admin.env
chmod 600 ~/stack/admin.env
```
-### B4. Dienste in `~/stack/docker-compose.yml` ergänzen
+### B3. Dienste in `~/stack/docker-compose.yml` ergänzen
```bash
nano ~/stack/docker-compose.yml
```
@@ -139,7 +120,7 @@ volumes:
admin-uploads:
```
-### B5. Caddy-Block ergänzen
+### B4. Caddy-Block ergänzen
```bash
nano ~/stack/caddy/Caddyfile
```
@@ -150,22 +131,58 @@ admin.larpaso.de {
}
```
-### B6. DNS bei IONOS
-- Neuer A-Eintrag: Hostname `admin` → Server-IPv4 (gleiche IP wie `@`).
-- Kurz warten/prüfen: `nslookup admin.larpaso.de` muss die Server-IP liefern, bevor B7
- läuft (sonst bekommt Caddy kein Zertifikat).
-
-### B7. Bauen & starten
+### B5. Admin-Image bauen
```bash
cd ~/stack
-docker compose build website admin
+docker compose build admin
+```
+Das baut das Image für den Login-Editor – inklusive eines sauberen `npm ci` für die
+Admin-Abhängigkeiten (bcryptjs, Express usw.), im selben zuverlässigen Schritt, der auch
+gleich für den Live-Betrieb verwendet wird.
+
+### B6. Login-Zugangsdaten erzeugen
+Jetzt mit dem gerade gebauten Image statt einem Wegwerf-Container – robuster als ein
+Ad-hoc-`npm install` (das kann mit dem bekannten npm-Fehler „Tracker 'idealTree' already
+exists" fehlschlagen):
+```bash
+docker compose run --rm admin node hash-password.js "dein-neues-passwort"
+```
+Gibt einen bcrypt-Hash aus (beginnt mit `$2a$` oder `$2b$`) – merken/kopieren.
+Zufälliges Session-Secret erzeugen:
+```bash
+docker compose run --rm admin node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
+```
+Auch kopieren.
+
+### B7. `~/stack/admin.env` mit echten Werten befüllen
+```bash
+cat > ~/stack/admin.env <<'EOF'
+NODE_ENV=production
+ADMIN_USERNAME=pascal
+ADMIN_PASSWORD_HASH=
+SESSION_SECRET=
+EOF
+chmod 600 ~/stack/admin.env
+```
+
+### B8. DNS bei IONOS
+- Neuer A-Eintrag: Hostname `admin` → Server-IPv4 (gleiche IP wie `@`).
+- Kurz warten/prüfen: `nslookup admin.larpaso.de` muss die Server-IP liefern, bevor B9
+ läuft (sonst bekommt Caddy kein Zertifikat).
+
+### B9. Bauen & starten
+```bash
+cd ~/stack
+docker compose build website
docker compose up -d website admin
docker compose restart caddy
docker compose logs -f caddy # auf "certificate obtained successfully" für admin.larpaso.de warten, dann Strg+C
```
+(`admin` wurde schon in B5 gebaut – falls du seitdem nochmal `git pull` gemacht hast,
+einmal `docker compose build admin` vorweg wiederholen.)
-### B8. Prüfen
-1. `https://admin.larpaso.de/admin/login` öffnen, mit den Zugangsdaten aus B2/B3 anmelden.
+### B10. Prüfen
+1. `https://admin.larpaso.de/admin/login` öffnen, mit den Zugangsdaten aus B6/B7 anmelden.
2. Eine Kleinigkeit ändern (z. B. Telefonnummer im Abschnitt „Kontakt" oder eine Farbe im
Abschnitt „Design – Farben"), **Speichern & veröffentlichen** klicken.
3. Build-Ausgabe sollte mit „Complete!" enden.
@@ -191,7 +208,8 @@ cd ~/stack && docker compose build admin && docker compose up -d admin
## Bekannte Grenzen
- Nur ein Login (kein Mehrbenutzer-System, keine Passwort-Reset-Funktion in der
- Oberfläche – neues Passwort = neuer Hash in `admin.env` + `docker compose up -d admin`).
+ Oberfläche – neues Passwort: `docker compose run --rm admin node hash-password.js "..."`
+ ausführen (wie B6), neuen Hash in `admin.env` eintragen, dann `docker compose up -d admin`).
- Sessions sind In-Memory: Ein Neustart des `admin`-Containers meldet alle ab.
- Es gibt keine Versionshistorie der Inhalte oder des Designs – beides liegt aber auf dem
Server und kann bei Bedarf manuell gesichert werden (z. B. regelmäßiges Backup der