Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f7f17ce7e | ||
|
|
7bfd3458a1 | ||
|
|
7cc89512d8 |
+158
-32
@@ -27,19 +27,33 @@ kein zweiter Dienst, kein Passwort dazwischen, und eine Sicherung ist ein
|
||||
`cp -a` über ein Volume. Wenn daraus je Millionen Zeilen werden, wird
|
||||
`server/src/db.ts` ausgetauscht — die Aufrufer merken davon nichts.
|
||||
|
||||
**Ein Name.** `fivecdn.d4rkst3r.de` und `fivemanage.d4rkst3r.de` fallen weg —
|
||||
wir betreiben kein Fivemanage, also tragen unsere Adressen auch nicht dessen
|
||||
Namen. Dashboard und API liegen an der Wurzel, die Dateien unter `/f/`:
|
||||
**Ein Name.** Dashboard und API liegen an der Wurzel, die Dateien unter `/f/`:
|
||||
|
||||
```
|
||||
https://media.d4rkst3r.de/ Dashboard und API
|
||||
https://media.d4rkst3r.de/f/vehicles/adder.webp
|
||||
https://fivemanage.d4rkst3r.de/ Dashboard und API
|
||||
https://fivemanage.d4rkst3r.de/f/vehicles/adder.webp
|
||||
```
|
||||
|
||||
Ein DNS-Eintrag, ein Host im Proxy. Aufgegeben ist damit eine Eigenschaft, die
|
||||
zwei Namen mitbrächten: die öffentliche Adresse trägt jetzt auch das
|
||||
Anmeldeformular.
|
||||
|
||||
> **Und der Name ist `fivemanage`, obwohl hier lange das Gegenteil stand.**
|
||||
> Der ursprüngliche Satz war: `fivecdn` und `fivemanage` fallen weg, wir
|
||||
> betreiben kein Fivemanage und tragen deshalb nicht dessen Namen. Das Argument
|
||||
> stimmt immer noch — es ist nur billiger als das, was dagegen steht, und das
|
||||
> ist nachgemessen: der Name **war schon fertig verkabelt**. DNS-Eintrag da,
|
||||
> Host in NPM da, Let's-Encrypt-Zertifikat gültig bis 09.11.2026, und er zeigte
|
||||
> auf `host.docker.internal:9101` — einen Port, der frei wurde, als die
|
||||
> Lite-Container gelöscht wurden. Unser Stack auf 9101 heißt: **kein
|
||||
> DNS-Eintrag, kein Klick in NPM, kein neues Zertifikat.**
|
||||
>
|
||||
> Der Preis ist ein Name, der nach einem fremden Produkt klingt. Der Wechsel
|
||||
> bleibt billig — die Adressen stehen nirgends in der Datenbank, sie werden bei
|
||||
> jeder Antwort aus `PUBLIC_URL` gebaut. Ein späterer Umzug auf
|
||||
> `media.d4rkst3r.de` ist ein DNS-Eintrag, ein NPM-Host und eine geänderte
|
||||
> Variable.
|
||||
|
||||
Zwei Namen kann der Dienst weiterhin — `FILES_HOST` setzen, dann gibt es unter
|
||||
diesem Namen ausschließlich Dateien und die URLs kommen ohne `/f/` aus. Beides
|
||||
ist gemessen. Der Wechsel ist billig: die Adressen stehen **nirgends in der
|
||||
@@ -160,7 +174,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 +190,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,17 +240,88 @@ 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.
|
||||
Nachmittag gekostet.
|
||||
|
||||
**3 · `server/upload.lua` im Fotostudio.** `Upload.put(pfad, bytes) → url`.
|
||||
Token in `config.upload.lua`, server-only, gitignored, mit committetem
|
||||
`.example` — nie im NUI, dort hat es jeder Spieler im Speicher. An den
|
||||
Serienlauf hängen, damit freigestellte Bilder nach der Freigabe sofort
|
||||
hochgehen.
|
||||
> **Und noch eine Annahme, die beim Nachsehen fiel: es gibt gar keine zweite
|
||||
> Maschine.** Gebaut und gelaufen ist auf **dem Server** — hier laufen Gitea,
|
||||
> Portainer, NPM und die Nextcloud. Die alte Notiz „auf dieser Maschine gibt
|
||||
> es weder Docker noch WSL" galt vor der Installation von Docker Desktop und
|
||||
> ist überholt. Was zum Betrieb fehlt, ist deshalb nicht der Umzug auf einen
|
||||
> anderen Rechner, sondern nur der **öffentliche Auftritt**: DNS-Eintrag für
|
||||
> `media.d4rkst3r.de`, ein Host in NPM mit TLS, ein echtes Passwort.
|
||||
|
||||
Gemessen am 11.08.2026 belegt: 80, 81, 443, 2224, 3000, 3080, 3478, 8000,
|
||||
8080, **8443**, 8090, 9000, 9443, 11000 — 8443 fehlte in der alten Liste.
|
||||
**Der Host-Port ist 9101**, nicht die 9102 aus dem Compose-Standard: darauf
|
||||
zeigt der fertige NPM-Host (siehe oben). Gesetzt wird er über `HOST_PORT` in
|
||||
der `.env`, der Standard im Compose bleibt 9102.
|
||||
|
||||
### ✅ Fertig — der Dienst steht öffentlich
|
||||
|
||||
Seit dem 11.08.2026 läuft er unter **`https://fivemanage.d4rkst3r.de`**, hinter
|
||||
NPM mit Let's-Encrypt-Zertifikat, Container auf 9101. Die Werte stehen in einer
|
||||
`.env` neben dem Compose (gitignored); dieselben Werte sind in Portainer die
|
||||
Stack-Variablen.
|
||||
|
||||
Durchgemessen über die öffentliche Adresse, nicht über localhost:
|
||||
|
||||
- Anmeldung durch echtes HTTPS — **das ist der Fall, den `curl` bisher nicht
|
||||
bezeugen konnte**: `Secure` am Cookie stört jetzt nicht mehr, weil die
|
||||
Verbindung wirklich TLS ist. 200, Cookie da, `/auth/me` 200.
|
||||
- Token mit Präfix-Fessel `vehicles/` und ohne Löschrecht angelegt. Upload nach
|
||||
`anderswo/` → **403 mit Text**, Löschen → **403 mit Text**.
|
||||
- Upload und Abruf über TLS und Proxy: **Byte für Byte identisch.**
|
||||
|
||||
> **Der 502, der keiner war.** Der erste Aufruf gab 502 in 78 ms. Die
|
||||
> naheliegende Erklärung — „ein Moment, gleich geht's" — war falsch: der
|
||||
> NPM-Host zeigte in dem Moment auf 9102, unser Container liegt auf 9101, also
|
||||
> sofortige Ablehnung. Kein Timeout, keine DNS-Frage, kein Zufall. Ein 502
|
||||
> binnen Millisekunden ist immer ein *Connection refused*, und dann stimmt eine
|
||||
> Portnummer nicht.
|
||||
|
||||
### ⬜ Als Nächstes
|
||||
|
||||
**3 · `server/upload.lua` im Fotostudio — geschrieben, im Spiel noch nicht
|
||||
gelaufen.** Liegt in `d4rk_photostudio` (eigenes Repo, `D:\FXServer\txData\…`):
|
||||
|
||||
```
|
||||
server/upload.lua Upload.send(pfad, bytes, cb) und Upload.put(pfad, bytes) → url
|
||||
config.upload.example.lua committet
|
||||
config.upload.lua gitignored, enthaelt den Token
|
||||
```
|
||||
|
||||
Hängt an `d4rk_photostudio:saveKeyed` — also genau an der Freigabe: das Bild
|
||||
geht **erst auf die Platte, dann in den Dienst**, und ein Fehlschlag bricht
|
||||
nichts ab, sondern landet im Protokoll des Laufs. Ein Lauf über 900 Fahrzeuge,
|
||||
der an einem Netzhuster stirbt, wäre der teuerste denkbare Fehler.
|
||||
|
||||
Gemessen ist alles, was sich ohne FiveM messen lässt — und das ist mehr, als es
|
||||
zunächst schien: `upload.lua` wurde in einer **echten Lua-5.3-VM** (fengari,
|
||||
über Node) mit gefälschten FiveM-Funktionen ausgeführt. 30 Prüfungen, keine
|
||||
durchgefallen: fehlende, kaputte und unvollständige `config.upload.lua`;
|
||||
Adresse, Methode und alle vier Kopfzeilen; der Fehlertext des Dienstes statt
|
||||
einer nackten Zahl; Status 0; und dass die Frist den Rückruf nicht ein zweites
|
||||
Mal auslöst. Der Rumpf, den die VM erzeugt hat, ging danach **unverändert an
|
||||
den echten Dienst** — hochgeladen, abgerufen, Byte für Byte identisch.
|
||||
|
||||
**Was offen bleibt, bis es einmal im Spiel lief:** ob `PerformHttpRequest`,
|
||||
`SetTimeout` und `promise` sich in FiveM so verhalten wie die Fälschungen, und
|
||||
ob der Event-Handler den Rückruf sauber erreicht. Dafür fehlt genau ein
|
||||
Handgriff, und er braucht die Serverkonsole:
|
||||
|
||||
```
|
||||
refresh
|
||||
ensure d4rk_photostudio (bzw. restart d4rk_photostudio)
|
||||
```
|
||||
|
||||
Beim Start sagt die Resource in einer Zeile, ob der Dienst antwortet. Danach
|
||||
`/studio shot <modell>` im Spiel — das Bild muss unter
|
||||
`https://fivemanage.d4rkst3r.de/f/vehicles/<modell>.webp` stehen.
|
||||
|
||||
Nach diesem Schritt ist das erreicht, worum es ursprünglich ging.
|
||||
|
||||
@@ -230,8 +337,23 @@ Speicherverbrauch nach Ordnern · Token-Verwaltung, Klartext genau einmal.
|
||||
> haben, und er ist billig zu vermeiden.
|
||||
|
||||
**5 · Umzug.** Root-`docker-compose.yml` wird unsere; die Fivemanage-Dateien
|
||||
wandern nach `legacy/`. Erst wenn unserer trägt — bis dahin bleibt der
|
||||
Lite-Stack als Vergleichsmaßstab laufen.
|
||||
wandern nach `legacy/`.
|
||||
|
||||
**Der Vergleichsmaßstab ist weg.** Am 11.08.2026 sind die Lite-Container
|
||||
gelöscht worden — mit ihnen die Möglichkeit, im Zweifel nachzusehen, wie es
|
||||
dort aussah. Was noch liegt und eine Entscheidung braucht:
|
||||
|
||||
```
|
||||
Volumes fivemanager_db fivemanager_minio fivemanager_pgdata
|
||||
Abbilder ghcr.io/fivemanage/lite:0.1.0-beta.23 minio/minio minio/mc
|
||||
```
|
||||
|
||||
**Nachgesehen, bevor jemand fragt: es ist nichts drin.** Alle drei Volumes
|
||||
schreibgeschützt eingehängt und durchgezählt — **kein einziges Bild**.
|
||||
`fivemanager_minio` sind 244 KB und besteht ausschließlich aus `.minio.sys`;
|
||||
der Eimer `media` hat Metadaten, aber kein einziges Objekt. Die anderen beiden
|
||||
sind leere Datenbanken (MySQL 196 MB, PostgreSQL 47 MB — das ist ihr
|
||||
Leergewicht). **Löschen kostet nichts**, es ist nur noch nicht getan.
|
||||
|
||||
### ⬜ Später, wenn es sich lohnt
|
||||
|
||||
@@ -247,6 +369,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.
|
||||
|
||||
@@ -41,10 +41,16 @@ export function checkPath(raw: string): string {
|
||||
|
||||
for (const segment of segments) {
|
||||
if (!SEGMENT.test(segment)) {
|
||||
// Die Meldung nennt die Regel so, wie sie ist. Vorher stand hier
|
||||
// "nicht mit Punkt beginnend" — das ist nur die Haelfte: das erste
|
||||
// Zeichen muss ein Buchstabe oder eine Ziffer sein, ein fuehrender
|
||||
// Unterstrich faellt also ebenso durch. Aufgefallen an
|
||||
// "_probe.webp", und das ist kein erfundener Fall: das Fotostudio
|
||||
// legt seine Leerbilder als "_plate" ab.
|
||||
throw new PathError(
|
||||
`"${segment}" ist als Pfadteil nicht erlaubt ` +
|
||||
'(Buchstaben, Ziffern, Punkt, Strich, Unterstrich; ' +
|
||||
'nicht mit Punkt beginnend)',
|
||||
'(erstes Zeichen: Buchstabe oder Ziffer; danach auch ' +
|
||||
'Punkt, Strich, Unterstrich)',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user