VERSCHIEBEN. Dateien in einen anderen Ordner legen -- mit einer Warnung, die
OBEN steht und nicht im Kleingedruckten: der Pfad IST die oeffentliche Adresse,
nach dem Verschieben fuehrt die alte URL ins Leere. Das ist kein Fehler,
sondern der Preis vorhersagbarer Adressen; man muss es nur vorher wissen.
Der Umzug ist ein `rename` und kein Kopieren-dann-Loeschen: auf derselben
Platte ist das unteilbar. Die Vorschau zieht mit, sonst zeigte die Galerie am
neuen Ort nichts und am alten das Bild einer Datei, die dort nicht mehr liegt.
Eine belegte Zieladresse wird NICHT still ueberschrieben -- da verschwaende
sonst eine fremde Datei, und der Verlauf zeigte nur einen Umzug.
Der Verlauf kennt dafuer eine neue Spalte `von` und eine neue Art `move`; ohne
sie stuende dort nur das Ziel und die Frage "wo war die vorher" waere nicht
mehr zu beantworten.
ORDNER anlegen und entfernen. Leere Ordner gibt es sonst nicht -- sie sind der
Teil eines Pfades vor dem letzten Schraegstrich, mehr nicht. Jetzt sind sie ein
echtes Verzeichnis, und /folders liest Tabelle und Platte zusammen. Ein Ordner
MIT Inhalt wird nicht mitgeloescht: das waere ein Knopf, der hunderte Dateien
wegraeumt, ohne sie zu zeigen.
ADRESSVORLAGEN. Der Bauplan einer oeffentlichen Adresse, benannt und
gespeichert (vehicles/{model}.webp). Sie werden auf der Startseite und der
API-Seite mit ausgefuellter Adresse gezeigt. Ohne Platzhalter oder mit ".." im
Muster wird abgelehnt -- beides nachgemessen.
API-SEITE. Anleitung fuer die eigenen Skripte, und der Punkt daran: die
Beispiele stehen MIT DER ECHTEN ADRESSE drin statt mit example.com. Wer
abschreibt, schreibt keinen Tippfehler ab. Dazu Export in drei Formen -- JSON,
CSV (Semikolon und CRLF, sonst steht in Excel alles in einer Spalte) und eine
fertige Lua-Tabelle Name -> Adresse, die man in eine Resource legt.
DREI FEHLER BEIM MESSEN GEFUNDEN:
- rm mit recursive:false wirft bei einem Verzeichnis EISDIR und sagt damit
etwas ganz anderes, als der Fall ist. rmdir scheitert von selbst, wenn der
Ordner nicht leer ist -- genau das gewuenschte Verhalten.
- In der Lua-Ausgabe war "\'" gemeint und "\'" geschrieben: in JavaScript ist
das schlicht ein Apostroph, der Backslash faellt weg. Ein Modellname mit
Apostroph haette die erzeugte Datei zerlegt.
- Der Export mit Ordnerfilter antwortete mit einem NACKTEN 500 (kaputtes
ESCAPE in der SQL-Zeile). Ausgerechnet das.
Deshalb hat der Dienst jetzt eine AUFFANGLINIE: app.onError gibt einen Text
statt "Internal Server Error". Wir haben das bisher an den bekannten Stellen
einzeln abgefangen -- und prompt hat ein Tippfehler die Luecke gefunden. Eine
Stelle schliesst sie fuer alles, woran heute niemand denkt.
Dazu: die Verlaufstabelle hat feste Spaltenbreiten (colgroup) und ueberall
dieselben Abstaende -- vorher hatten Kopf und Koerper verschiedene, und der
Pfad wurde abgeschnitten, obwohl daneben Platz frei war. Und die Anmeldeseite
hat zwei weiche Lichter statt einer leeren Flaeche.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
- Oberfläche: http://localhost:8080 —
adminund deinADMIN_PASSWORD - MinIO: http://localhost:9001 —
liteund deinMINIO_ROOT_PASSWORD
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 asciiund nichtutf8— PowerShell 5.1 schreibt sonst ein BOM voran, und dann heißt die erste VariableDB_PASSWORD.Get-Randomwä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 nicht —
otlpEndpoint 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.