diff --git a/ROADMAP.md b/ROADMAP.md index 9f5890a..843def3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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, durchgemessen — auf 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.