diff --git a/ROADMAP.md b/ROADMAP.md index 0a7bbb7..e2b640d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -63,8 +63,6 @@ server/src/routes/dash.ts die API fürs Dashboard server/src/index.ts Host-Weiche, Dateiauslieferung, Start ``` -Typprüfung läuft durch. **Noch nicht gestartet und nicht getestet.** - Die API für Skripte: ``` @@ -86,10 +84,73 @@ Die API fürs Dashboard: Anmeldung, Passwortwechsel, Medienliste mit Suche und Paginierung, Einzel- und Sammellöschung, Statistik nach Ordnern, Token-Verwaltung. -### ⬜ Als Nächstes +### ✅ Fertig — Schritt 1, der Smoke-Test -**1 · Smoke-Test.** Server lokal starten, Token anlegen, ein Bild hoch- und -wieder abrufen. Vor Docker, vor allem anderen. +Gestartet, durchgemessen, repariert. Der Durchstich steht: anmelden → Token +anlegen → Bild hochladen → unter der zurückgegebenen URL abrufen, Byte für Byte +identisch, auch unter dem Dateihost. + +**So läuft er lokal:** + +```bash +cp server/.env.example server/.env +``` + +```bash +npm --prefix server ci && npm --prefix server run dev +``` + +> **Der Stolperstein beim lokalen Start:** ohne `FILES_HOST` nimmt der Dienst +> den Host aus `PUBLIC_URL` — lokal also `localhost`. Dann gilt *jede* Anfrage +> als Anfrage an den Dateiwirt, und dort gibt es absichtlich weder Dashboard +> noch API: `/health` antwortet 404, die Anmeldung 405 „hier gibt es nur +> Dateien". Nichts ist kaputt. In Produktion sind es zwei verschiedene Namen +> und der Standard stimmt; lokal gehört ein Name hin, den niemand aufruft. +> Steht samt Begründung in `server/.env.example`. + +**Was der Test gefunden hat** — alles im Grundgerüst, alles repariert: + +1. **Die Token-Prüfung hing an `'*'`** und galt damit auch für `/api/dash/*`, + das daneben liegt. Das Dashboard bekam „Token fehlt oder ist unbekannt" auf + die Anmeldung, obwohl es nie einen Token haben kann. Jetzt hängt sie an den + drei eigenen Pfaden. +2. **`DELETE /api/media/…` und `GET /api/exists/…` sahen am Ziel vorbei.** + `c.req.path` trägt den Einhängepunkt mit, das `replace(/^\/media\//)` schnitt + ihn nicht weg — aus `vehicles/adder.png` wurde `api/media/vehicles/adder.png`. + Löschen fand nie etwas, `exists` meldete immer `false`. Jetzt `:pfad{.+}`; + Hono liefert den Parameter fertig dekodiert (nachgemessen), ein zweites + `decodeURIComponent` wäre eine Dekodierung zu viel gewesen. +3. **Verzeichnisdurchstieg in der SPA-Rückfallroute.** + `GET /..%5Cpackage.json` hat unter Windows die Datei ausgeliefert: Hono + reicht `%5C` durch, `path.join` behandelt den Backslash dort als Trenner, + und eine Eindämmung gab es nicht. Unter Linux trägt genau dieser Angriff + nicht — Glück, keine Abwehr. Jetzt dieselbe `resolve`-Prüfung wie in + `storage.ts`. +4. **Die Auskunft „Oberfläche 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. Jetzt + wird vorher nachgesehen. +5. **CSS und JS kamen als `application/octet-stream`** — die MIME-Tabelle kennt + nur Medientypen. Das Dashboard hätte weder Stylesheet noch Modul geladen. + Die Oberfläche hat jetzt eine **eigene** Tabelle: in der geteilten fehlt + `html` mit Absicht, sonst könnte jeder mit einem Upload-Token eine Seite + unter `fivecdn.d4rkst3r.de` veröffentlichen. Nachgemessen: eine hochgeladene + `.html` kommt weiterhin als `octet-stream` mit `nosniff`. +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. + +**Was sonst nachgemessen wurde und trägt:** alle drei Rumpfformen (multipart, +Base64, rohe Bytes) liefern denselben SHA-256, auch mit `data:`-Vorspann · der +Hash-Pfad ohne `X-Path` · Präfix-Fessel und `can_delete` je Token · `..`, +versteckte Dateien und leerer Rumpf werden abgelehnt · `X-Overwrite: false` +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. + +### ⬜ Als Nächstes **2 · Dockerfile und Compose.** Ein Abbild, das Server und gebaute Oberfläche ausliefert; ein Volume für `data/`; ein Port für NPM. In Portainer aus diesem diff --git a/server/.env.example b/server/.env.example new file mode 100644 index 0000000..fe92e88 --- /dev/null +++ b/server/.env.example @@ -0,0 +1,43 @@ +# Kopieren nach server/.env, dann `npm run dev`. +# +# Ohne PUBLIC_URL und ADMIN_PASSWORD startet der Dienst nicht — das ist +# Absicht, siehe config.ts. + +# Unter welcher Adresse die Dateien oeffentlich stehen. Daraus wird die URL +# gebaut, die nach dem Upload zurueckkommt. Ohne abschliessenden Schraegstrich. +PUBLIC_URL=http://localhost:8080/f + +# DER STOLPERSTEIN BEIM LOKALEN START. +# +# Ohne diese Zeile nimmt der Dienst den Host aus PUBLIC_URL — lokal also +# "localhost". Dann gilt jede Anfrage als Anfrage an den Dateiwirt, und dort +# gibt es absichtlich weder Dashboard noch API: /health antwortet 404, die +# Anmeldung 405 "hier gibt es nur Dateien". Nichts ist kaputt, der Dienst tut +# genau das, was er soll — nur eben unter dem einen Namen, den man lokal hat. +# +# In Produktion sind es zwei verschiedene Namen und der Standard stimmt: +# PUBLIC_URL=https://fivecdn.d4rkst3r.de +# FILES_HOST=fivecdn.d4rkst3r.de (Dashboard: fivemanage.d4rkst3r.de) +# +# Lokal deshalb ein Name, den niemand aufruft — dann bleibt localhost fuer +# Dashboard und API frei. Zum Testen des Dateiwirts: +# curl -H "Host: files.localhost" http://localhost:8080/vehicles/adder.webp +FILES_HOST=files.localhost + +# Wird beim ERSTEN Start angelegt. Ein spaeter geaendertes Passwort setzt das +# bestehende nicht zurueck. +ADMIN_PASSWORD=bitte-aendern +ADMIN_USERNAME=admin + +PORT=8080 + +# Ein Ordner fuer Bilder UND media.db — eine Sicherung ist damit ein cp -a. +# Relativ zum Arbeitsverzeichnis; im Abbild spaeter /data. +DATA_DIR=./data + +# Groesste erlaubte Datei in MB. Ein freigestelltes Fahrzeugbild wiegt rund +# 200 KB. +MAX_UPLOAD_MB=64 + +# Wie lange eine Anmeldung im Dashboard haelt. +SESSION_DAYS=30 diff --git a/server/package.json b/server/package.json index 1d8a8f7..c3a0397 100644 --- a/server/package.json +++ b/server/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "tsx watch src/index.ts", + "dev": "tsx watch --env-file=.env src/index.ts", "build": "tsc -p tsconfig.json", "start": "node dist/index.js" },