diff --git a/API.md b/API.md index bbd62cc..5e81a05 100644 --- a/API.md +++ b/API.md @@ -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 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 | |---|---| | Präfix | darf nur unterhalb dieses Pfades schreiben, z. B. `vehicles/` | | 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 | 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) | | 400 | Pfad unzulässig, Rumpf leer, kaputtes Base64 | | 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 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.16–31.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 | | | |---|---| -| 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 | | Anmeldeversuche | 5 frei, danach Sperre ab 30 s (verdoppelt bis 15 min) | | 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. --- diff --git a/ideen.md b/ideen.md new file mode 100644 index 0000000..f115386 --- /dev/null +++ b/ideen.md @@ -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.