SICHERUNG (tools/sichern.ps1, taeglich 04:30 als geplante Aufgabe).
Die Datenbank wird nicht kopiert, sondern ueber SQLites eigene
Sicherungsschnittstelle herausgeholt (server/src/backup.ts): im WAL-Modus
liegt das Zuletzte noch nicht in media.db, und selbst alle drei Dateien zu
kopieren ist nicht sicher, wenn waehrenddessen geschrieben wird. Die Bilder
kommen aus einem NUR LESEND eingehaengten Volume dazu.
Und sie prueft sich selbst: auspacken, Datenbank oeffnen, Medien/Token/
Benutzer zaehlen, mit dem laufenden Dienst vergleichen, sonst Fehler. Einmal
wirklich zurueckgespielt -- leeres Volume, zweiter Dienst, Anmeldung mit dem
echten Passwort, Bild abgerufen, Byte fuer Byte identisch. Eine Sicherung, die
nie zurueckgespielt wurde, ist eine Hoffnung.
ANMELDEBREMSE. Das Formular steht oeffentlich, und scrypt macht einen Versuch
teuer -- aber teuer ist nicht selten. Fuenf freie Versuche je Adresse, dann
Sperre ab 30 s mit Verdopplung bis 15 min, 429 samt Retry-After und einem Text,
der sagt wie lange. Erfolg setzt zurueck. Nur im Speicher: wer sich aussperrt,
startet den Container neu. Durchgemessen bis zur Erholung.
VORSCHAUBILDER. sharp erzeugt beim Upload eine 320er WebP-Fassung unter
/data/thumbs, ausgeliefert unter /t/<pfad>. Gemessen: 131502 -> 13078 Bytes,
Faktor 10; eine Galerieseite faellt von 7,5 MB auf 766 KB. KEINE Spalte in der
Datenbank -- ob es eine Vorschau gibt, sagt das Dateisystem, und die Galerie
faellt bei 404 aufs Vollbild zurueck. Eine zweite Wahrheit, die auseinander-
laufen kann, gibt es damit gar nicht erst. Ein Knopf zieht Fehlendes nach und
nennt Zahlen statt "fertig".
SCHRITT 5. Die Fivemanage-Dateien liegen in legacy/ mit Erklaerung; unsere
docker-compose.media.yml heisst jetzt docker-compose.yml. Drei Volumes und drei
Abbilder geloescht, rund 670 MB -- nachgezaehlt war vorher, dass kein Bild
darin lag.
Nebenbefund: `npm i sharp` scheitert auf diesem Rechner nicht an sharp, sondern
an better-sqlite3 -- der Host laeuft auf Node 24 (ABI 137), node-gyp uebernimmt
und findet keine Bauwerkzeuge. Genau die Falle aus dem Dockerfile. Eingetragen
wurde mit --package-lock-only, gebaut wird im Abbild auf Node 22.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Grundgeruest war typgeprueft, aber nie gelaufen. Beim ersten Start
haben sich sechs Dinge gezeigt, alle nachgemessen statt vermutet:
1. Die Token-Pruefung hing an '*' und galt damit auch fuer /api/dash/*,
das daneben liegt. Das Dashboard bekam "Token fehlt oder ist
unbekannt" auf die Anmeldung, obwohl es nie einen Token haben kann.
Sie haengt jetzt an den drei eigenen Pfaden.
2. DELETE /api/media/... und GET /api/exists/... sahen am Ziel vorbei.
c.req.path traegt den Einhaengepunkt mit, das replace(/^\/media\//)
schnitt ihn nicht weg -- aus vehicles/adder.png wurde
api/media/vehicles/adder.png. Loeschen fand nie etwas, exists meldete
immer false. Jetzt :pfad{.+}; Hono liefert den Parameter fertig
dekodiert (an einer Probe gemessen), ein zweites decodeURIComponent
waere eine Dekodierung zu viel.
3. Verzeichnisdurchstieg in der SPA-Rueckfallroute: GET /..%5Cpackage.json
hat unter Windows die Datei ausgeliefert. Hono reicht %5C durch,
path.join behandelt den Backslash dort als Trenner, und eine
Eindaemmung gab es nicht. Unter Linux traegt genau dieser Angriff
nicht -- Glueck, keine Abwehr. Jetzt dieselbe resolve-Pruefung wie in
storage.ts.
4. Die Auskunft "Oberflaeche ist nicht gebaut" war unerreichbar.
createReadStream meldet eine fehlende Datei asynchron, das try/catch
darum fing nichts. Ergebnis war ein leerer 200 samt ENOENT im Log.
5. CSS und JS kamen als application/octet-stream -- die MIME-Tabelle
kennt nur Medientypen, das Dashboard haette weder Stylesheet noch
Modul geladen. Die Oberflaeche bekommt eine eigene Tabelle: in der
geteilten fehlt html mit Absicht, sonst koennte jeder mit einem
Upload-Token eine Seite unter fivecdn.d4rkst3r.de veroeffentlichen.
6. Kaputtes JSON endete als nackter "Internal Server Error".
Ausgerechnet das -- ein 500 ohne ein Wort dazu ist der Fehler, wegen
dem wir hier neu bauen. Jetzt 400 mit Text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Container statt vier: Hono, SQLite, Dateien auf einer Platte. Kein
PostgreSQL, kein MinIO, kein ClickHouse. Das ist nicht die kleine Loesung,
sondern die, die man nicht pflegt — eine Sicherung ist ein cp -a.
Der Ausloeser war nicht der Funktionsumfang von Lite, sondern dessen
Zustand. web/src/utils/http-util.ts wirft jeden Fehler als new Error(...),
die Hooks pruefen auf "instanceof ApiError" — damit wird jeder Fehlschlag
lautlos verschluckt. "Create organization" meldet weder Erfolg noch
Misserfolg; das Ergebnis waren 19 gleichnamige Organisationen, die sich auch
nicht loeschen lassen, weil die Route fehlt. Von allem, was Lite darueber
hinaus kann, haben wir nichts gebraucht.
Zwei Hostnamen, ein Prozess: unter FILES_HOST gibt es ausschliesslich
Dateien — kein Dashboard, keine API, nichts anzumelden. Das haelt die
oeffentliche Adresse frei von Angriffsflaeche und die URLs huebsch, ohne
/f/-Praefix. Die Vorlage im Handy bleibt {model}.webp.
Der Pfad ist der Schluessel: X-Path legt die Datei genau dort ab, wo der
Aufrufer sie erwartet. Bei Lite haetten wir die zurueckgegebenen Adressen in
einer urls.json mitschleppen muessen — vorhersagbare URLs sind fuer das
Fotostudio die ganze Voraussetzung.
storage.ts bereinigt Pfade NICHT, es lehnt sie ab. Ein zurechtgebogener Pfad
legt die Datei unter einem anderen Namen ab als dem erwarteten, und das faellt
erst auf, wenn das Bild fehlt.
Typprueft. Noch nicht gestartet, noch nicht getestet — das ist der naechste
Schritt und steht in ROADMAP.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>