docs: Schritt 2 ist gemessen -- das Abbild ist gebaut und gelaufen

Der Bau stand ungeprueft im Repo, und alles daran war Behauptung: ob
`npm ci` die fertige Binaerdatei zieht, ob das Volume greift, ob der
Healthcheck etwas taugt. Jetzt nachgesehen, auf einer Maschine mit Docker
Desktop 29.6.2:

- better-sqlite3 kommt als Binaerdatei, nicht aus node-gyp (datiert Mai 2025,
  kein Makefile daneben). Node 22, ABI 127 -- die Festnagelung traegt.
- Derselbe Durchstich wie in Schritt 1, diesmal gegen den Container: 24
  Pruefungen, keine durchgefallen. Am Grundgeruest war nichts zu aendern.
- Healthcheck meldet healthy; das Volume traegt ueber einen Containerwechsel;
  Compose bricht ohne PUBLIC_URL/ADMIN_PASSWORD ab, wie das :? verspricht.

Neu aufgefallen, weil der Container es sichtbar macht: im Volume liegen VIER
Dinge, nicht zwei. SQLite laeuft im WAL-Modus, und das WAL hielt nach wenigen
Uploads 152 KB, die in media.db noch nicht standen -- eine Sicherung nur ueber
media.db waere unvollstaendig. Steht jetzt bei den offenen Entscheidungen.

Und ein Zeuge weniger: curl taugt fuer die Secure-Cookie-Falle nicht, er
schickt das Cookie auch ueber http und meldet Erfolg. Was zaehlt, ist der
Browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 16:47:51 +02:00
co-authored by Claude Opus 5
parent 076d909f62
commit 7cc89512d8
+50 -19
View File
@@ -160,7 +160,7 @@ gibt 409, Überschreiben `replaced: true` · 413 vor dem Einlesen · ETag/304,
HEAD, Bereichsanfragen und 416 · unter dem Dateihost gibt es weder API noch
`/f/`-Präfix · Medienliste, Suche, Statistik nach Ordnern.
Nicht getestet: Docker, das Dashboard (gibt es noch nicht), und echte Last.
Nicht getestet: das Dashboard (gibt es noch nicht) und echte Last.
### ✅ Fertig — ein Name statt zwei
@@ -176,27 +176,49 @@ Beide Betriebsarten sind durchgemessen: mit einem Namen kommt
Dashboard-Namen zusätzlich unter `/f/` erreichbar, und unter dem Dateihost gibt
es weiterhin keine API.
### ⬜ Als Nächstes
### ✅ Fertig — Schritt 2, das Abbild ist gebaut und gelaufen
**2 · Dockerfile und Compose — geschrieben, aber NIE GEBAUT.**
`server/Dockerfile` und `docker-compose.media.yml` stehen. Auf der Maschine,
auf der sie entstanden sind, gibt es weder Docker noch WSL — das Abbild ist
also ungebaut und der Container nie gelaufen. Das ist der nächste Handgriff,
und er gehört auf eine Maschine mit Docker.
Gebaut, gestartet, durchgemessenauf einer Maschine mit Docker (Desktop
29.6.2, WSL2). Derselbe Durchstich wie in Schritt 1, diesmal gegen den
**Container** statt gegen `tsx`: 24 Prüfungen, keine durchgefallen. Am
Grundgerüst war nichts zu ändern; der Bau hat keinen einzigen Befund erzeugt.
Was daran trotzdem geprüft ist:
**So läuft er:**
- **Node 22 ist festgenagelt, mit Grund.** `better-sqlite3` 11.10.0 liefert
fertige Binärdateien nur für ABI 108 (Node 20), 115, 127 (Node 22) und 131.
**Node 24 hat ABI 137 und fehlt** — der naheliegende Griff zur neuesten
Version fällt auf `node-gyp` zurück und braucht python3, make und g++ im
Abbild. Aus den Veröffentlichungen des Projekts abgelesen, nicht geraten.
- **`node dist/index.js`** — der Startbefehl des Abbilds — läuft und antwortet
auf `/health`. Bis dahin war der Dienst nur je über `tsx` gestartet.
- **Der Healthcheck** gibt Exitcode 0 bei laufendem und 1 bei totem Dienst.
```bash
export PUBLIC_URL=http://localhost:9102 ADMIN_PASSWORD=
docker compose -f docker-compose.media.yml up -d --build
```
Ungeprüft bleibt alles, wofür es Docker braucht: ob `npm ci` im Abbild die
Binärdatei zieht, ob das Volume greift, ob der Bau überhaupt durchläuft.
**Was jetzt gemessen ist und vorher nur behauptet war:**
- **`npm ci --omit=dev` zieht die fertige Binärdatei.** Im Abbild liegt
`better-sqlite3/build/Release/better_sqlite3.node`, datiert Mai 2025 —
heruntergeladen, nicht übersetzt; ein `Makefile` oder `obj.target` von
`node-gyp` steht nirgends daneben. `require('better-sqlite3')` läuft im
Laufzeit-Abbild, Node 22.23.2, ABI 127. **Die Festnagelung trägt.**
- **Der Bau läuft durch**, alle drei Stufen. Das Abbild: 373 MB auf der
Platte, 88,4 MB Inhalt.
- **Der Healthcheck greift.** Docker meldet `healthy`, Exitcode 0, kein
Fehlschlag — ohne `curl` im Abbild, `fetch` aus Node reicht.
- **Das Volume greift.** Container weggeworfen, einen neuen an dasselbe
Volume gehängt: die Datei aus dem ersten kommt weiter unter ihrer URL, und
`admin` wird nicht noch einmal angelegt. Nebenbei bestätigt, was im Compose
steht: ein geändertes `ADMIN_PASSWORD` setzt nichts zurück — die alte
Anmeldung gilt (200), die neue nicht (401).
- **Compose bricht ohne Variablen ab**, wie das `:?` verspricht, und zwar mit
dem Text, der danebensteht — erst `PUBLIC_URL`, dann `ADMIN_PASSWORD`. Mit
beiden fährt der Stack hoch und bindet `0.0.0.0:9102->8080`.
- **`Secure` steht wirklich im Cookie**, wenn `NODE_ENV=production` gilt:
`sid=…; Path=/; HttpOnly; Secure; SameSite=Lax`. Ohne Cookie oder mit einem
erfundenen gibt jede Dashboard-Route 401 — auch `/auth/me`.
**Neu aufgefallen, weil der Container es sichtbar macht:** im Volume liegen
**vier** Dinge, nicht zwei — `files/`, `media.db`, `media.db-shm` und
`media.db-wal`. SQLite läuft im WAL-Modus (`db.ts`), und das WAL war nach
wenigen Uploads 152 KB groß, mit Daten, die in `media.db` noch nicht standen.
Eine Sicherung, die nur `media.db` mitnimmt, ist deshalb unvollständig. Siehe
*Offene Entscheidungen*.
> **Beim Ausprobieren:** `NODE_ENV=production` setzt das Sitzungs-Cookie auf
> `Secure`. Wer den Container ohne HTTPS aufruft, bekommt auf die Anmeldung
@@ -204,11 +226,16 @@ Binärdatei zieht, ob das Volume greift, ob der Bau überhaupt durchläuft.
> still verwirft; die nächste Anfrage ist 401. Im Browser gegen eine
> LAN-Adresse nachgemessen. Die naheliegende Gegenprobe über `localhost`
> führt in die Irre: das gilt Browsern als sicherer Kontext und funktioniert.
> Und `curl` taugt hier nicht als Zeuge — der schickt ein Secure-Cookie auch
> über `http` und meldet fröhlich Erfolg. Was zählt, ist der Browser.
In Portainer aus diesem Repo deploybar — **`Compose path` nimmt EINE Datei**,
Ergänzungen werden stillschweigend ignoriert. Das hat uns bei Lite einen
Nachmittag gekostet. Host-Port ist 9102, solange der Lite-Stack 9100/9101
belegt.
belegt. **Auf dem Server selbst ist noch nicht deployt** — gebaut und gelaufen
ist auf der Arbeitsmaschine. Das ist Schritt 5.
### ⬜ Als Nächstes
**3 · `server/upload.lua` im Fotostudio.** `Upload.put(pfad, bytes) → url`.
Token in `config.upload.lua`, server-only, gitignored, mit committetem
@@ -247,6 +274,10 @@ Lite-Stack als Vergleichsmaßstab laufen.
- **Sicherung.** Ein Volume mit Bildern und `media.db`. Restic gegen die
Nextcloud? Oder reicht ein `docker cp` vor größeren Änderungen?
**Was dabei feststeht:** wegen WAL gehören `media.db-wal` und `media.db-shm`
mit ins Kopieren, sonst fehlen die letzten Uploads. Sauberer wäre
`sqlite3 media.db ".backup …"` oder ein `wal_checkpoint(TRUNCATE)` davor —
beides ungeprüft, weil noch nicht entschieden ist, wie gesichert wird.
- **Bilder aus dem Spiel** (Screenshots, Clips von Spielern) — dafür brauchte
es Größenbegrenzungen je Token und vermutlich eine Warteschlange. Erst
planen, wenn es ansteht.