WEB-2: Self-hosted public site (publicsite-nginx + POST /api/publish, atomic swap)
POST /api/publish: rendert SiteSnapshot->HTML in _staging_new/, atomic swap -> live/ (rename, ein Syscall). publicsite-nginx:alpine serviert live/ read-only auf Port 8081. Shared Volume api(rw)/publicsite(ro). Manager bleibt LAN-only. 5 neue Tests (atomic swap, UTF-8, mehrfach), 184/184 gruen. compose config OK. Vhost-Snippet + web-deploy.md (Deutsch) beigelegt; <DOMAIN> wartet auf Julian.
This commit is contained in:
136
docs/web-deploy.md
Normal file
136
docs/web-deploy.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# GerbilManager — Oeffentliche Webseite (Self-Hosted, TrueNAS)
|
||||
|
||||
> **Zielgruppe:** Julian.
|
||||
> Die oeffentliche Seite (Jimdo-Ersatz) laeuft self-hosted auf der TrueNAS neben dem Manager.
|
||||
> Strato-Domain → DynDNS → IP → Julians nginx-Proxy → publicsite-Container (Port 8081).
|
||||
|
||||
---
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
Internet
|
||||
| HTTPS/HTTP
|
||||
v
|
||||
Julians nginx-Reverse-Proxy (laeuft schon auf NAS)
|
||||
| proxy_pass http://127.0.0.1:8081
|
||||
v
|
||||
publicsite (nginx:alpine, Port 8081) ← liest nur: /usr/share/nginx/html/live/
|
||||
| (Shared Volume, read-only)
|
||||
| [POST /api/publish im Manager schreibt in dasselbe Volume]
|
||||
v
|
||||
api (.NET, Port 8080 intern) → schreibt: /data/publicsite/live/
|
||||
| (Shared Volume, read-write)
|
||||
v
|
||||
Manager (frontend-nginx, Port 80) ← LAN-only, NIE internet-exponiert
|
||||
```
|
||||
|
||||
**SICHERHEIT — harte Bedingung:**
|
||||
- Nur `publicsite` (Port 8081) wird ins Internet weitergeleitet.
|
||||
- Der Manager (API + Frontend, Port 80) hat KEINE Authentifizierung → LAN-only.
|
||||
- Der `publicsite`-nginx proxied NICHT auf die API — er serviert nur statisches HTML.
|
||||
|
||||
---
|
||||
|
||||
## Erstinstallation
|
||||
|
||||
### 1. Verzeichnis anlegen
|
||||
|
||||
```bash
|
||||
mkdir -p /mnt/JailStorage/DockerVolumes/gerbilmanager/publicsite
|
||||
```
|
||||
|
||||
Das Verzeichnis wird von der API beschrieben (laeuft als root im Container) — keine ACL-Aenderung noetig.
|
||||
Beim ersten `POST /api/publish` legt die API automatisch `live/` und `_staging_new/` darunter an.
|
||||
|
||||
### 2. .env erganzen
|
||||
|
||||
In `deploy/truenas/.env` hinzufuegen (oder aus `.env.example` uebernehmen):
|
||||
|
||||
```env
|
||||
PUBLICSITE_PATH=/mnt/JailStorage/DockerVolumes/gerbilmanager/publicsite
|
||||
PUBLICSITE_PORT=8081
|
||||
```
|
||||
|
||||
### 3. Compose-Stack neu starten
|
||||
|
||||
```bash
|
||||
cd /opt/gerbilmanager
|
||||
docker compose -f deploy/truenas/compose.yaml up -d
|
||||
```
|
||||
|
||||
Der neue `publicsite`-Container startet und serviert Port 8081.
|
||||
Solange noch nicht veroeffentlicht wurde, zeigt er einen 404 (live/-Verzeichnis leer).
|
||||
|
||||
### 4. Julians externen nginx konfigurieren
|
||||
|
||||
Inhalt von `deploy/truenas/vhost-snippet.conf` in den bestehenden nginx-Proxy einfuegen
|
||||
(als eigenen `server`-Block oder per `include`):
|
||||
|
||||
```bash
|
||||
# Auf der NAS, nginx-Konfigverzeichnis (z.B. /etc/nginx/conf.d/ oder sites-available):
|
||||
nano /etc/nginx/conf.d/gerbilmanager-public.conf
|
||||
# <DOMAIN> durch den tatsaechlichen Hostnamen ersetzen
|
||||
nginx -t && nginx -s reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Seite veroeffentlichen (Publish-Ablauf)
|
||||
|
||||
1. Im Manager einloggen (http://\<NAS-IP\>/)
|
||||
2. Navigiere zu **Webseite** → Inhalte bearbeiten → **Veroeffentlichen**
|
||||
3. Klick auf "Veroeffentlichen" loest `POST /api/publish` aus.
|
||||
|
||||
**Was passiert intern:**
|
||||
```
|
||||
POST /api/publish
|
||||
→ API baut SiteSnapshot aus DB (alle Published-Seiten)
|
||||
→ SiteRenderer rendert Snapshot → HTML-Dateien (path → content Map)
|
||||
→ Schreibt Dateien nach /data/publicsite/_staging_new/
|
||||
→ Atomic Swap: _staging_new/ → live/ (rename = ein Syscall, nie halb-geschrieben)
|
||||
→ publicsite-nginx serviert beim naechsten Request sofort den neuen Stand
|
||||
→ Kein Container-Restart, kein Image-Rebuild, kein CI
|
||||
Response: { "filesPublished": N }
|
||||
```
|
||||
|
||||
**Endergebnis:** publicsite-nginx liest sofort den neuen Stand aus `live/`.
|
||||
|
||||
---
|
||||
|
||||
## Verifikation
|
||||
|
||||
```bash
|
||||
# publicsite-Container laeuft?
|
||||
docker compose -f deploy/truenas/compose.yaml ps publicsite
|
||||
|
||||
# Seite lokal abrufbar?
|
||||
curl -s http://localhost:8081/ | head -5
|
||||
|
||||
# live/-Verzeichnis gefuellt?
|
||||
ls /mnt/JailStorage/DockerVolumes/gerbilmanager/publicsite/live/
|
||||
|
||||
# Oeffentlich erreichbar (nach DNS-Propagation)?
|
||||
curl -s http://<DOMAIN>/ | grep "Kleine Chaoten"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sicherheitstrennung (Pflichtcheck)
|
||||
|
||||
| Was | Port | Internet-exponiert? |
|
||||
|-----|------|---------------------|
|
||||
| Manager (api + frontend) | 80 | **NEIN** — LAN-only |
|
||||
| Oeffentliche Seite (publicsite) | 8081 | Ja, via Julians nginx-Proxy |
|
||||
| API-Doku (Scalar) | 80/scalar | **NEIN** — LAN-only |
|
||||
|
||||
Der Manager-nginx (gerbilmanager-frontend, Port 80) und die API (Port 8080 intern)
|
||||
sind NICHT in `vhost-snippet.conf` eingetragen und NICHT in Julians externem Proxy konfiguriert.
|
||||
Sie sind ausschliesslich im Heimnetz erreichbar.
|
||||
|
||||
---
|
||||
|
||||
## Hostname noch ausstehend
|
||||
|
||||
`<DOMAIN>` in `deploy/truenas/vhost-snippet.conf` ist ein Platzhalter.
|
||||
Julian nennt den Hostnamen/Subdomain → ersetzen und nginx neu laden.
|
||||
Reference in New Issue
Block a user