docs: Stand nach dem Smoke-Test, und der Stolperstein beim lokalen Start
Der Durchstich steht: anmelden, Token anlegen, Bild hochladen, unter der zurueckgegebenen URL abrufen -- Byte fuer Byte identisch, auch unter dem Dateihost. server/.env.example ist die Datei, die .gitignore ohnehin schon erwartet hat. Wichtigster Eintrag darin ist FILES_HOST: ohne ihn 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. Das dev-Skript liest die .env jetzt mit --env-file (funktioniert unter tsx, nachgeprueft). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+66
-5
@@ -63,8 +63,6 @@ server/src/routes/dash.ts die API fürs Dashboard
|
|||||||
server/src/index.ts Host-Weiche, Dateiauslieferung, Start
|
server/src/index.ts Host-Weiche, Dateiauslieferung, Start
|
||||||
```
|
```
|
||||||
|
|
||||||
Typprüfung läuft durch. **Noch nicht gestartet und nicht getestet.**
|
|
||||||
|
|
||||||
Die API für Skripte:
|
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,
|
Paginierung, Einzel- und Sammellöschung, Statistik nach Ordnern,
|
||||||
Token-Verwaltung.
|
Token-Verwaltung.
|
||||||
|
|
||||||
### ⬜ Als Nächstes
|
### ✅ Fertig — Schritt 1, der Smoke-Test
|
||||||
|
|
||||||
**1 · Smoke-Test.** Server lokal starten, Token anlegen, ein Bild hoch- und
|
Gestartet, durchgemessen, repariert. Der Durchstich steht: anmelden → Token
|
||||||
wieder abrufen. Vor Docker, vor allem anderen.
|
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
|
**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
|
ausliefert; ein Volume für `data/`; ein Port für NPM. In Portainer aus diesem
|
||||||
|
|||||||
@@ -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
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "tsx watch --env-file=.env src/index.ts",
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"start": "node dist/index.js"
|
"start": "node dist/index.js"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user