Files
d4rk_media/README.md
T
D4rkst3randClaude Opus 5 0b45f31c15 docs: zwei Eigenheiten festhalten, die sonst zweimal Zeit kosten
Das Tracing-Rauschen alle fuenf Minuten ist kein Fehler und laesst sich in
beta.23 nicht abstellen: otlpEndpoint ist eine Konstante, ENV waehlt nur
zwischen TLS und unverschluesselt. Da die Adresse localhost lautet, hilft auch
kein Jaeger-Container daneben — der muesste sich den Netzwerk-Namensraum
teilen.

Wichtiger: wenn ein Knopf in der Oberflaeche nichts tut, steht der Grund im
Log und nie auf dem Bildschirm. http-util.ts wirft jeden Fehler als
new Error(...) weiter, die Hooks pruefen auf "instanceof ApiError" — das ist
danach nie wahr. 401, 500 und ein falscher Rumpf sehen alle gleich aus.

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

216 lines
8.1 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
```bash
cp .env.example .env # und ausfüllen, siehe unten
docker compose -f docker-compose.local.yml up -d
```
- Oberfläche: <http://localhost:8080> — `admin` und dein `ADMIN_PASSWORD`
- MinIO: <http://localhost:9001> — `lite` und dein `MINIO_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:
```bash
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`:
```powershell
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:**
```bash
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:
```bash
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.
**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](https://github.com/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.