Anleitung aus docs/ — 2026-08-12 08:26

2026-08-12 08:26:11 +02:00
parent 6fb68f8e7a
commit d3a9c83c20
2 changed files with 294 additions and 3 deletions
+117 -3
@@ -212,12 +212,49 @@ Tokens werden im Dashboard angelegt. Der Klartext wird **genau einmal** gezeigt
— danach steht nur noch sein Hash in der Datenbank, und auch der Dienst kann — danach steht nur noch sein Hash in der Datenbank, und auch der Dienst kann
ihn nicht mehr herausgeben. ihn nicht mehr herausgeben.
Ein Token kann zwei Fesseln haben: Ein Token kann **sechs** Fesseln haben — alle im Dashboard einstellbar, auch
nachträglich (`PATCH`, ohne dass sich der Schlüssel ändert):
| Fessel | Wirkung | | Fessel | Wirkung |
|---|---| |---|---|
| Präfix | darf nur unterhalb dieses Pfades schreiben, z. B. `vehicles/` | | Präfix | darf nur unterhalb dieses Pfades schreiben, z. B. `vehicles/` |
| darf löschen | ohne dieses Recht antwortet `DELETE` mit 403 | | darf löschen | ohne dieses Recht antwortet `DELETE` mit 403 |
| größte Datei | schärfer als `MAX_UPLOAD_MB`; darüber 413 |
| Kontingent | wieviel dieser Token **insgesamt** halten darf; darüber 413 |
| erlaubte Arten | Bilder, Video, Ton, Dokumente, Anderes; sonst 403 |
| gültig bis | danach 401 mit dem Datum im Satz |
> **Das Kontingent ist etwas anderes als die Größe einer Datei.** Ein Token mit
> „höchstens 2 MB je Bild" konnte die Platte trotzdem füllen — es brauchte nur
> genug Bilder. Gezählt wird über das, was wirklich liegt, nicht über einen
> mitlaufenden Zähler:
>
> ```
> Kontingent erschoepft: dieser Token haelt 12 KB von 20 KB,
> und diese Datei braucht 12 KB.
> ```
### ⚠️ Bilder können kleiner zurückkommen, als sie hingingen
Ein Token kann eine **größte Kantenlänge** tragen. Ist sie gesetzt und das Bild
größer, rechnet der Dienst es herunter, **bevor** er es ablegt:
```
3000 × 2000 hingeschickt → 512 × 341 abgelegt, 82 KB → 3 KB
```
Für ein Skript heißt das zweierlei:
1. **`size` in der Antwort ist die Größe der abgelegten Datei**, nicht die der
geschickten.
2. **`sha256` ebenso.** Wer den Hash vor dem Upload selbst rechnet und mit der
Antwort vergleicht, bekommt bei gesetzter Kantenlänge einen Unterschied —
und der ist kein Fehler. Der Hash gehört zu dem, was liegt; alles andere
wäre gelogen.
Kleinere Bilder werden **nicht** aufgeblasen, und Video, Ton und Dokumente
bleiben unberührt. Ohne gesetzte Kantenlänge passiert gar nichts — der
Standard ist „ablegen, wie es kommt".
--- ---
@@ -251,6 +288,14 @@ Nimmt die Datei in einer von **drei Rumpfformen** entgegen:
} }
``` ```
Trägt der Token eine Kantenlänge und wurde wirklich gerechnet, kommt ein Feld
dazu — sonst steht es gar nicht erst da, denn ein Feld, das immer dasteht, sagt
nichts:
```json
"verkleinert": { "von": "3000x2000", "auf": "512x341" }
```
### Was schiefgehen kann ### Was schiefgehen kann
| Antwort | Grund | | Antwort | Grund |
@@ -259,7 +304,7 @@ Nimmt die Datei in einer von **drei Rumpfformen** entgegen:
| 403 | Der Token darf dort nicht schreiben (Präfix-Fessel) | | 403 | Der Token darf dort nicht schreiben (Präfix-Fessel) |
| 400 | Pfad unzulässig, Rumpf leer, kaputtes Base64 | | 400 | Pfad unzulässig, Rumpf leer, kaputtes Base64 |
| 409 | `X-Overwrite: false` und der Pfad ist belegt | | 409 | `X-Overwrite: false` und der Pfad ist belegt |
| 413 | größer als `MAX_UPLOAD_MB` (Standard 64) | | 413 | größer als `MAX_UPLOAD_MB` (Standard 64), **oder** größer als die Grenze dieses Tokens, **oder** sein Kontingent ist erschöpft — welches, steht im Satz |
Jede Fehlerantwort ist JSON mit einem Feld `error` und einem **Satz auf Jede Fehlerantwort ist JSON mit einem Feld `error` und einem **Satz auf
Deutsch**. Ein nackter 500 ohne Text ist genau der Fehler, wegen dem dieser Deutsch**. Ein nackter 500 ohne Text ist genau der Fehler, wegen dem dieser
@@ -349,14 +394,83 @@ Papierkorb. Die tägliche Sicherung hilft nur bis zum letzten Lauf.
--- ---
## Die Wege des Dashboards
Diese brauchen eine **Sitzung** und keinen Token — sie sind für die Oberfläche
gedacht. Hier stehen sie, weil man sie auch mit `curl` und einem Cookie
benutzen kann, wenn man etwas einmalig braucht.
| Weg | Wozu |
|---|---|
| `POST /api/dash/upload-url` | Der Dienst holt die Datei selbst von einer Adresse. Siehe unten. |
| `POST /api/dash/fehlende` | Muster + Namensliste → was fehlt, was überzählig ist |
| `POST /api/dash/media/ersetzen` | Suchen und Ersetzen über Pfade. **Ohne `ausfuehren: true` wird nur gerechnet und gezeigt.** |
| `POST /api/dash/media/umbenennen` | Eine Datei; die Antwort nennt ihre Abrufzahl |
| `POST /api/dash/folders/umbenennen` | Ein Ordner mit allem darin, in einer Transaktion |
| `PATCH /api/dash/tokens/:id` | Grenzen ändern. Weggelassene Felder bleiben, `null` löscht eine Grenze. |
| `GET /api/dash/verwaltung` | Wer hat wann was eingestellt |
| `GET /api/dash/verlauf/export` | Verlauf als CSV; `?was=verwaltung` für den anderen. Semikolon + CRLF, höchstens 20 000 Zeilen. |
| `GET /api/dash/einstellungen/export` | Alle Einstellungen als JSON. **Ohne Geheimnisse**, es sei denn `?geheim=1`. |
| `POST /api/dash/einstellungen/import` | Zurück. Leere Werte überschreiben nichts. |
| `GET/POST/DELETE /api/dash/freigaben` | Nur-Lesen-Links auf einen Ordner |
| `POST /api/dash/maintenance/thumbs` \| `/webp` | Abgeleitetes nachziehen, **in Runden zu 300** |
### POST /api/dash/upload-url
```json
{ "url": "https://…/bild.png", "ordner": "items", "name": "optional.png" }
```
**Das ist die vorsichtigste Stelle im ganzen Dienst.** Ein Server, der eine vom
Benutzer genannte Adresse abruft, ist ein Angriff mit eigenem Namen — deshalb
wird geprüft, und zwar die **aufgelöste IP** und nicht der Name:
| abgelehnt | Beispiel |
|---|---|
| alles außer http/https | `file://`, `gopher://` |
| andere Anschlüsse als 80/443 | `http://host:9101/` |
| Rückschleife und Heimnetz | `127.0.0.1`, `::1`, `10.x`, `172.1631.x`, `192.168.x`, `169.254.x`, `0.0.0.0` |
| öffentliche Namen auf private Adressen | `127.0.0.1.nip.io` |
Der letzte Punkt ist der eigentliche: ein auflösbarer Name, der auf eine private
Adresse zeigt, ist der Standardweg um jeden Namensfilter herum. **Und jede
Umleitung wird einzeln neu geprüft** — `fetch` folgen zu lassen wäre genau dort
die Lücke.
---
## Grenzen ## Grenzen
| | | | | |
|---|---| |---|---|
| Größte Datei | 64 MB (`MAX_UPLOAD_MB`) | | Größte Datei | 64 MB (`MAX_UPLOAD_MB`), je Token weiter einschränkbar |
| Kontingent je Token | einstellbar, ohne Grenze wenn leer |
| Pfadlänge | 200 Zeichen, höchstens 8 Ebenen | | Pfadlänge | 200 Zeichen, höchstens 8 Ebenen |
| Anmeldeversuche | 5 frei, danach Sperre ab 30 s (verdoppelt bis 15 min) | | Anmeldeversuche | 5 frei, danach Sperre ab 30 s (verdoppelt bis 15 min) |
| Sitzung | 30 Tage | | Sitzung | 30 Tage |
| Papierkorb | 30 Tage **und** höchstens 20 GB (`PAPIERKORB_MAX_MB`) — was zuerst greift, greift |
Der Papierkorb hat zwei Grenzen, weil eine nicht reicht: wer 200 GB löscht,
hält sie dreißig Tage lang doppelt. Beim Ausleeren fliegt erst, was zu alt ist,
dann das **Älteste**, bis der Deckel wieder passt. Die Zahl steht in der
Antwort von `GET /api/dash/papierkorb` als `maxBytes`.
---
## Meldungen nach Discord
Vier Anlässe, alle einzeln abschaltbar: `sicherung`, `verwaiste`, `platte`,
`upload`.
**`upload` meldet nicht sofort.** Jeder Upload schiebt eine Frist von zwei
Minuten nach hinten; erst wenn es ruhig bleibt, geht **eine** Nachricht raus —
mit Anzahl, Summe, Absendern und den ersten fünf Pfaden.
Das ist keine Bequemlichkeit, sondern der Grund, warum die Funktion überhaupt
brauchbar ist: ein Serienlauf des Fotostudios legt 900 Bilder ab. 900
Nachrichten wären keine Meldung mehr, sondern ein Ausfall des Kanals — Discord
drosselt Webhooks, und wer danach eine *echte* Meldung bekommt, sieht sie nicht
mehr.
--- ---
+177
@@ -0,0 +1,177 @@
# Was noch ginge
Stand 12.08.2026. Diese Liste ist **nicht** eine Sammlung von allem, was
denkbar wäre — sie ist das, was beim Bauen und Messen wirklich aufgefallen ist.
Was hier steht, hat einen Grund; was keinen hatte, steht am Ende unter
„Verworfen, mit Begründung".
Sortiert nach dem, was ich zuerst täte.
---
## 1 · Sicher wertvoll
### ~~Die Oberfläche auf dem Telefon~~ — abgelehnt am 12.08.2026
> Vom Betreiber verworfen: das Dashboard wird nicht mobil benutzt. Der Rest des
> Abschnitts bleibt stehen, damit die Messung nicht verlorengeht, falls sich das
> je ändert.
**Gemessen:** 18 Umbruchpunkte (`sm:`/`lg:`/`xl:`) im ganzen Frontend. Die
Galerie ist ein Raster und passt sich an — die **vier Tabellen** in Verlauf,
Token, Konten und Papierkorb nicht. Auf einem Telefon laufen sie seitlich
heraus.
Der Umbau ist keine Kosmetik: Karten statt Tabellenzeilen unterhalb einer
Breite, so wie es die Galerie schon macht. Betrifft vier Dateien.
**Lohnt sich nur, wenn du das Dashboard mobil aufmachst.** Sonst ist es
Arbeit für einen Bildschirm, den niemand benutzt.
### Sitzungen beenden können
**Gemessen:** 86 offene Sitzungen, davon 82 von `admin` — meine Testanmeldungen
von heute. Sie laufen nach 30 Tagen aus, und `pruneSessions` räumt stündlich
die abgelaufenen weg. Was fehlt, ist ein Knopf **„alle anderen Sitzungen
beenden"** im Kontomenü.
Das ist mehr als Ordnung: wer sich an einem fremden Rechner angemeldet hat und
es später merkt, hat heute keine Möglichkeit, das zurückzunehmen — außer das
Passwort zu ändern und zu hoffen, dass das die Sitzungen mitnimmt (tut es
nicht, sie hängen an einer eigenen Tabelle).
### Was der Verlauf nicht sieht
**Gemessen:** `events` kennt `upload`, `replace`, `delete`, `move`, `rename`
alles über **Dateien**. Nichts über **Einstellungen**. Wer den Discord-Webhook
geändert hat, wer einen Token angelegt oder gelöscht hat, wer ein Konto
entfernt hat: steht nirgends.
Bei zwei Konten ist das verschmerzbar. Sobald ein drittes dazukommt — und über
die Discord-Rolle entsteht es von selbst — ist es die erste Frage, die man
stellt, wenn etwas anders ist als gestern.
---
## 2 · Nützlich, wenn der Fall eintritt
### Massen-Umbenennen
Suchen und Ersetzen über Pfade, mit Vorschau vor dem Zuschlagen. Bei 3578 Items
ist eine Umbenennung von Hand keine.
Der Anlass wäre etwa: `items/` nach `inventar/` verschieben, oder eine
Namenskonvention ändern. Heute geht das nur Datei für Datei oder gar nicht.
**Die Warnung wäre dieselbe wie beim Ordner-Umbenennen** — jede alte Adresse
gibt danach 404 —, nur mal tausend.
### Bilder beim Ablegen verkleinern
Eine Obergrenze für die Kantenlänge, einstellbar je Token. Wer ein 6000 × 4000
großes Foto hochlädt, bekommt es auf, sagen wir, 2048 gerechnet.
**Gemessen und deshalb keine Dringlichkeit:** ein 12000 × 12000 großes PNG hat
den Dienst *nicht* in Verlegenheit gebracht — Vorschau in 374 ms, Speicher bei
47 MB von 31 GB. libvips arbeitet kachelweise und dekodiert nie das ganze Bild.
Es geht also um Plattenplatz und Ladezeit beim Ausliefern, nicht um Stabilität.
### Fehlende Bilder finden
Eine Liste hereinreichen (Fahrzeugmodelle, Item-Namen) und beantwortet
bekommen: **wozu fehlt ein Bild?** Heute beantwortet das niemand — das
Fotostudio weiß, was es aufgenommen hat, der Dienst weiß, was liegt, und die
Differenz rechnet niemand aus.
Das wäre ein Endpunkt, der eine Liste entgegennimmt und die fehlenden
zurückgibt. Klein, und die Antwort auf „warum zeigt das Handy bei manchen
Autos kein Bild".
### Einstellungen aus- und einlesen
Discord-Zugang, Sicherungsziel, Vorlagen, Meldungsanlässe — alles steht in
`settings` und ist beim Neuaufsetzen von Hand nachzutragen. Ein Export als JSON
(**ohne** die Geheimnisse, oder mit einer ausdrücklichen Ansage) macht aus
einer Stunde Klickerei fünf Minuten.
---
## 3 · Wäre schön, drängt nicht
### ~~Verlauf als CSV~~ — gebaut am 12.08.2026
`GET /api/dash/verlauf/export`, wahlweise `?was=verwaltung`. Der Knopf sitzt in
der Reiterleiste und holt **den Verlauf, der gerade offen ist** — zwei Knöpfe
nebeneinander wären die Frage „welchen von beiden?" an einer Stelle, wo die
Antwort schon auf dem Bildschirm steht.
Semikolon und CRLF wie beim Bestands-Export, und aus demselben Grund: Excel in
deutscher Einstellung nimmt bei Komma alles in eine Spalte und bei nacktem LF
die halbe Datei in eine Zelle. Gemessen an der ausgelieferten Datei: 5364
Zeilen, 5364 CRLF, **kein einziges nacktes LF**.
### ~~Meldung bei Upload~~ — gebaut am 12.08.2026
Mit Drossel, und die ist der eigentliche Inhalt. Jeder Upload schiebt eine
Frist von zwei Minuten nach hinten; erst nach der Ruhe geht **eine** Nachricht
raus, mit Anzahl, Summe, Absendern und den ersten fünf Pfaden.
Ohne das wäre die Funktion ein Schaden statt eines Nutzens: 900 Fahrzeugbilder
ergäben 900 Nachrichten, Discord drosselt Webhooks, und wer danach eine echte
Meldung bekommt, sieht sie nicht mehr. Ein Serienlauf ergibt jetzt eine Zeile,
ein einzelnes Bildschirmfoto ergibt eine Zeile — beide sagen dasselbe, nur mit
anderen Zahlen.
Der Anlass heißt `upload` und ist **abschaltbar wie die anderen drei**. Die
Liste steht an zwei Stellen (Server und Oberfläche) — genau die Verdopplung,
die bei `platte` schon einmal ein Kreuzchen verschluckt hat; deshalb steht
jetzt in beiden ein Verweis auf die andere. Nachgemessen: vier geschickt, vier
gespeichert.
### ~~Papierkorb mit Größendeckel~~ — gebaut am 12.08.2026
20 GB. Beim Ausleeren wird erst nach Alter geräumt (30 Tage) und dann nach
Größe, **das Älteste zuerst** — dessen Versehen wäre am ehesten schon
aufgefallen. Die Grenze steht in der Antwort von `/api/dash/papierkorb`, damit
die Oberfläche sie nicht ein zweites Mal hinschreibt.
Bei 910 GB frei ist das heute keine Grenze, sondern eine Zusicherung: der
Papierkorb kann nicht mehr unbemerkt zur zweiten Ablage werden.
### Mehrere Sicherungsziele — offen
Die Archive liegen auf derselben Platte wie die Daten, die Kopie in der
Nextcloud ist die einzige Trennung.
---
## Verworfen, mit Begründung
Diese standen auf der Liste und sind **gemessen** wieder heruntergefallen. Sie
stehen hier, damit sie nicht beim nächsten Mal wieder aufschlagen.
| Idee | Warum nicht |
|---|---|
| Doppelte Dateien zusammenführen | 5 Gruppen, **0,0 MB** verschwendet. Es gibt nichts zu holen. |
| Aufräumen nach Alter | Alle vier Altersstufen im Bericht stehen auf **0 Dateien**. Es ist nichts alt. |
| Grenze für Bildmaße als *Schutz* | 12000 × 12000 → 374 ms, 47 MB. libvips streamt, die Gefahr gibt es nicht. |
| Plattenplatz-Wächter | **Gebaut.** 80/90/95 %, meldet nach Discord. |
| Kontingent je Token | **Gebaut**, auf beiden Upload-Wegen geprüft. |
| Eigene Statusseite bauen | `/status` reicht — der d4rkbot zeigt sie schon an. |
---
## Und was heute noch schnell repariert wurde
**`pruneEvents` lief nie von selbst.** Die Funktion gibt es seit dem ersten Tag
und sie räumt Ereignisse älter als 180 Tage weg — aufgerufen wurde sie aber nur
von `/maintenance/prune-sessions`, einem Weg, den nicht einmal die Oberfläche
anbietet. Die dokumentierte Aufbewahrung griff damit **nie**, und die Tabelle
wuchs für immer.
Zum Vergleich: `pruneSessions` hängt seit jeher an einem stündlichen Takt. Das
eine war verdrahtet, das andere nicht, und der Unterschied fiel niemandem auf,
weil beide in derselben Zeile stehen.
Gemessen: nach **einem** Tag mit Umzug und Serienlauf standen 5355 Zeilen in
`events`. Läuft jetzt täglich, mit zwei Minuten Verzögerung nach dem Start.