D4rkst3randClaude Opus 5 c9cd8eaa84 feat: Token-Grenzen, Speicher nach Art, Aufbewahrungsbericht, Discord-Meldungen
KEINE TOKEN-ARTEN, SONDERN TOKEN-GRENZEN. Fivemanage hat Token-Typen; wir haben
schon ein Rechtesystem, es steht nur auf einer anderen Achse (Praefix und
Loeschrecht). Was wirklich fehlte, sind die drei Schrauben, die die Frage
beantworten "was passiert, wenn DIESER Token abhandenkommt":

  Groessengrenze   ein Token fuer Fahrzeugbilder braucht keine 64 MB
  erlaubte Arten   nur Bilder, kein Video
  Ablaufdatum      fuer eine Anbindung, die man mal ausprobiert

Alle drei am laufenden Dienst durchgemessen: 600 KB gegen 512 KB -> 413 mit
beiden Zahlen; ein mp4 gegen einen Bild-Token -> 403 mit Art UND MIME im Text;
ausserhalb des Praefix -> 403; abgelaufen -> 401 mit DEM DATUM. "Abgelaufen"
und "unbekannt" auseinanderzuhalten spart eine halbe Stunde Suche nach einem
Tippfehler, den es nicht gibt.

Mehr als die Dienstgrenze zu erlauben wird abgelehnt statt still gekappt -- es
waere eine Behauptung, denn durchgelassen wird ohnehin die schaerfere.

SPEICHER NACH ART: ein Balken fuer die Verhaeltnisse, eine Tabelle fuer die
Zahlen, dazu die groessten Dateien.

AUFBEWAHRUNG VORBEREITET, NICHT GEBAUT. Automatisches Loeschen zu bauen, bevor
man weiss, was da liegt, ist der Weg, wie man Daten verliert. Stattdessen der
Bericht davor: wie viel ist aelter als 7/30/90/365 Tage, was liegt doppelt
(gleicher SHA-256 unter zwei Pfaden) und wie viel Platz das kostet. "Alles
aelter als 90 Tage loeschen" ist eine Behauptung, solange niemand weiss, wie
viel das waere. Diese Seite loescht nichts.

DISCORD-MELDUNGEN -- und der Grund ist nicht Discord, sondern eine Luecke, die
wir heute selbst gebaut haben: die Sicherung laeuft nachts um halb fuenf, und
wenn sie aufhoert zu laufen, merkt es niemand. Der Dienst sieht jetzt
STUENDLICH nach und meldet, wenn der letzte Lauf gescheitert oder aelter als 26
Stunden ist (24 waeren zu knapp, 48 zu spaet). Hoechstens EINMAL je Zustand --
eine Meldung, die stuendlich wiederkommt, wird weggeklickt, und dann auch die,
die zaehlt.

Ein Webhook und keine Bot-Anbindung: eine Adresse, kein Token, keine
Berechtigungen. Die Adresse wird geprueft (nur echte discord.com-Webhooks),
und der Testknopf steht gleich daneben -- ein Webhook, den man eintraegt und
erst in drei Wochen im Fehlerfall ausprobiert, ist einer, der dann nicht geht.

Aus deren Doku mitgenommen und NICHT gebaut: presigned URLs (kurzlebige
signierte Adressen fuer Client-Uploads). Waere der richtige Weg fuer
Spieler-Screenshots -- solange keine Spieler hochladen, ist es Vorrat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:30:03 +02:00

d4rk_media

Der Medien-Stack: Fivemanage Lite mit MySQL und MinIO. Ein Ablageort für alle Resourcen — Fahrzeugbilder aus d4rk_photostudio, Item-Icons, Marken, was sonst noch anfällt.

Bisher liegt das auf dem Nextcloud und wird per rclone von Hand hochgeladen. Das trägt, solange ein Mensch hochlädt. Sobald ein Script es tun soll, fehlt ein Schreibweg über HTTP — und genau den bringt Lite mit, samt Token je Resource und einer Oberfläche zum Nachsehen.


Ausprobieren, bevor es auf den Server geht

0.1.0-beta.23 ist früh. Erst auf Docker Desktop:

cp .env.example .env      # und ausfüllen, siehe unten
docker compose -f docker-compose.local.yml up -d

Wenn das trägt, dasselbe auf dem Server.


Auf dem Server, über Portainer

Portainer kann einen Stack direkt aus diesem Repo ziehen:

Stacks → Add stack → Repository

Feld Wert
Repository URL https://git.d4rkst3r.de/D4rkst3r/d4rk_media
Compose path docker-compose.yml
Environment variables die vier Geheimnisse plus PUBLIC_URL

Die Werte gehören in die Stack-Variablen, nicht in eine Datei im Repo. .env steht deshalb in .gitignore.

Wie der Reverse Proxy die Dienste erreicht

Über den Host, und das steht fertig in docker-compose.yml. Genauso laufen d4rkbot auf 3080 und cdn-files auf 8090 schon.

fivecdn.d4rkst3r.de   ->  http://host.docker.internal:9100
fivemanage.d4rkst3r.de ->  http://host.docker.internal:9101

Nicht 9000 und 8080 — die sind auf diesem Server vergeben (Portainer und nextcloud-aio). Im Container bleibt es bei 9000 und 8080; nur die Seite zum Host wandert. Kollidiert später noch etwas, sind es zwei Variablen: MINIO_HOST_PORT und LITE_HOST_PORT.

Die Ports sind an alle Adressen gebunden, nicht an 127.0.0.1. Das ist kein Versehen: NPM liegt im Standard-Bridge-Netz (172.17.0.3) und kommt über die Docker-Brücke herein, nicht über Loopback — ein an 127.0.0.1 gebundener Port nimmt diese Anfrage nicht an. Zu macht sie die Firewall, nicht die Bindung. 9100 und 9101 gehören von außen dicht; öffentlich soll nur NPM auf 80/443 sein.

Wenn der Proxy im selben Docker-Netz hängt

Dann geht es sauberer, ganz ohne offene Ports — dafür liegt docker-compose.proxynet.yml bereit. Portainer nimmt unter Compose path aber nur eine Datei; eine Ergänzung wird dort stillschweigend ignoriert. Wer diesen Weg will, führt die beiden Dateien zusammen oder startet den Stack von Hand:

docker compose -f docker-compose.yml -f docker-compose.proxynet.yml up -d

Auf diesem Server trifft das nicht zu.

Die Ports sind an alle Adressen gebunden, weil host.docker.internal auf diesem Server über die Docker-Brücke kommt und ein 127.0.0.1-Port die Anfrage nicht annähme — dieselbe Bindung, mit der d4rkbot und cdn-files schon laufen.

Damit macht die Firewall den Port zu, nicht die Bindung. 9100 und 9101 gehören von außen dicht; öffentlich erreichbar soll nur NPM auf 80 und 443 sein. Wer weiß, dass Loopback bei ihm erreicht wird, setzt BIND_ADDR=127.0.0.1.


Die vier Geheimnisse

Sie werden erzeugt, nicht irgendwo abgeholt. Das Token für die Resourcen kommt später und woanders her: aus der laufenden Lite-Oberfläche.

PowerShell — schreibt die fertige .env:

function New-Secret([int]$n){ $b = New-Object byte[] $n; [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b); ($b | ForEach-Object { $_.ToString('x2') }) -join '' }
@("DB_PASSWORD=$(New-Secret 16)","MINIO_ROOT_PASSWORD=$(New-Secret 16)","API_TOKEN_HMAC_SECRET=$(New-Secret 32)","ADMIN_PASSWORD=$(New-Secret 12)","PUBLIC_URL=https://fivecdn.d4rkst3r.de","PROXY_NETWORK=web") | Set-Content -Encoding ascii .env

Zwei Fallen stecken darin, beide schon hineingetreten:

  • RandomNumberGenerator::Create() und nicht ::Fill — letzteres gibt es erst ab .NET 6, Windows PowerShell 5.1 kennt es nicht.
  • -Encoding ascii und nicht utf8 — PowerShell 5.1 schreibt sonst ein BOM voran, und dann heißt die erste Variable DB_PASSWORD.
  • Get-Random wäre die naheliegende Wahl und die falsche: es ist nicht kryptografisch sicher, und hier wird ein Token-Signaturgeheimnis erzeugt.

Bash / Linux:

printf 'DB_PASSWORD=%s
MINIO_ROOT_PASSWORD=%s
API_TOKEN_HMAC_SECRET=%s
ADMIN_PASSWORD=%s
PUBLIC_URL=https://fivecdn.d4rkst3r.de
PROXY_NETWORK=proxy
'   "$(openssl rand -hex 16)" "$(openssl rand -hex 16)"   "$(openssl rand -hex 32)" "$(openssl rand -hex 12)" > .env

PostgreSQL, nicht MySQL

0.1.0-beta.23 geht direkt in den PostgreSQL-Treiber; DB_DRIVER wird nicht ausgewertet. Eine MySQL-DSN endet deshalb in

panic: pgdriver: invalid scheme: lite

— der Treiber liest sie als URI und findet als Schema den Benutzernamen.

Die docker-compose.test.yml des Projekts zeigt MySQL, benutzt dafür aber fivemanage/lite:latest von Docker Hub, und die Reihe steht dort bei beta.16 still. Das README des Projekts hat recht: postgres://….

Das Volume heißt deshalb pgdata und nicht db — wer den Stack schon mit MySQL laufen hatte, bekommt so eine frische Ablage, statt dass PostgreSQL über ein MySQL-Verzeichnis stolpert. Das alte Volume bleibt als Waise liegen und kann weg:

docker volume rm fivemanager_db

Zwei Adressen, nicht eine

Lite spricht MinIO unter zwei verschiedenen Namen an, und beide muessen stimmen:

Variable Wert Wofuer
AWS_ENDPOINT http://minio:9000 Wie Lite die Dateien hinlegt — Containername im internen Netz
BUCKET_DOMAIN $PUBLIC_URL/media Woraus die oeffentliche Adresse gebaut wird, die Lite nach dem Upload zurueckgibt

BUCKET_DOMAIN steht nicht im README des Projekts, nur in dessen .env.template. Ohne sie kaemen Adressen zurueck, die nur innerhalb von Docker gelten — der Upload saehe erfolgreich aus, und das Bild waere von aussen nicht abrufbar.

Den Bucket legt Lite beim Start selbst an und setzt dabei die oeffentliche Leserichtlinie (pkg/storage/s3/s3.go). Es ueberspringt beides, wenn der Bucket schon existiert — deshalb legt dieser Stack ihn nicht vorher an.


Zwei Eigenheiten, die Zeit kosten

traces export: … dial tcp [::1]:4318: connect: connection refused, alle fünf Minuten. Kein Fehler: Lite schickt Tracing-Daten an einen Collector, den dieser Stack bewusst nicht mitbringt. Abschalten geht in beta.23 nichtotlpEndpoint ist eine Konstante in pkg/otel/exporter.go, ENV wählt nur zwischen TLS und unverschlüsselt. Weil die Adresse localhost ist, würde auch ein Jaeger-Container daneben nichts nützen; er müsste sich den Netzwerk- Namensraum teilen. Ignorieren.

„Create organization" tut nichts. Doch — die Organisation wird angelegt, nur geht die Oberfläche danach nicht weiter: NewOrganizationRoute.tsx ruft mutate(data) ohne onSuccess, ohne Navigation, ohne Meldung. Seite neu laden, dann findet die App sie. Wer mehrfach geklickt hat, hat mehrere.

Ein Knopf in der Oberfläche tut gar nichts. Dann steht der Grund im Container-Log, nicht auf dem Bildschirm. web/src/utils/http-util.ts wirft jeden Fehler als new Error(…) weiter, die aufrufenden Hooks prüfen aber auf err instanceof ApiError — das ist danach nie wahr, und der Fehler wird verschluckt. 401, 500 und ein falscher Rumpf sehen deshalb alle gleich aus: nichts passiert.


Was hier bewusst fehlt

ClickHouse und Jaeger. Beide stehen in der Entwicklungs-Compose des Projekts und sind für strukturiertes Logging und Tracing da. Zum Ablegen von Bildern braucht es sie nicht, und ClickHouse ist eine schwere Abhängigkeit. Nachrüsten geht jederzeit — die App findet sie über CLICKHOUSE_*.

Offene Ports in der Betriebsfassung. Nur der Reverse Proxy spricht mit der App; Datenbank und MinIO bleiben im internen Netz. Die Entwicklungs-Compose des Projekts macht 5432, 9000, 16686 und mehr auf — dort in Ordnung, hier nicht.


Herkunft

Zusammengestellt aus fivemanage/lite: dessen README.md, deployments/docker-compose.yml (Entwicklung, ohne die App) und deployments/docker-compose.test.yml (zeigt die App-Verdrahtung und dass MySQL genügt).

Nicht laufen gelassen. Rechne mit ein, zwei Anläufen — vor allem beim ersten Login und beim Bucket.

S
Description
Medien-Stack: Fivemanage Lite mit MySQL und MinIO. Ablage fuer Fahrzeugbilder, Item-Icons und Marken.
Readme
3 MiB
Languages
TypeScript 86%
PowerShell 11.8%
Python 1.1%
Dockerfile 0.6%
CSS 0.3%
Other 0.2%