Commit Graph
15 Commits
Author SHA1 Message Date
D4rkst3randClaude Opus 5 ab93e0ae4e feat: die Archive lassen sich aus dem Panel holen
Einstellungen -> "Sicherungen holen": die fertigen Archive mit Datum, Groesse
und einem Knopf. Der erste Schritt, der eine Kopie VON DIESEM RECHNER HERUNTER
bringt -- bisher haette man sich dafuer per Fernwartung auf den Server klicken
muessen, und genau daran hat es gelegen. Nicht Unwille, sondern
Umstaendlichkeit.

Der Ordner haengt NUR LESEND in den Container. Ein Dienst, der seine eigenen
Sicherungen loeschen kann, ist genau das Werkzeug, das man einem uebernommenen
Dienst nicht in die Hand geben will. Nachgemessen im Container:

    touch /sicherungen/probe  ->  Read-only file system

Und es gibt NUR EINE FORM VON NAMEN -- nicht "alles ausser .. und /", sondern
ein Muster, das genau auf die eigenen Archive passt. Wer einen Pfad
hineinschmuggeln will, muesste ihn erst so benennen. Das ist der Unterschied
zwischen dicht gemacht und dicht gebaut:

    ../../etc/passwd                -> 404
    ..%2f..%2fdata%2fmedia.db       -> 400
    media.db                        -> 400
    <archiv>.tar.gz.evil            -> 400
    ohne Anmeldung                  -> 401

Der ganze Weg einmal wirklich gegangen, nicht nur die Kopfzeilen: 336 MB in
15,9 s durch den Proxy, sha256 auf der Platte gleich sha256 heruntergeladen,
tar tzf zeigt 4639 Eintraege. Gestroemt und nicht in den Speicher gelesen --
sonst haetten Dienst UND Proxy die 336 MB gleichzeitig im Arbeitsspeicher.

Jeder Griff steht im Verwaltungs-Verlauf. Geloggt wird der GRIFF und nicht der
Erfolg: ein abgebrochener Download taucht ebenfalls auf, und das ist gewollt --
wer spaeter fragt, wer sich den Bestand geholt hat, will die Anfrage sehen.

Die fuenf neuen Pruefungen sind in tools/rundgang.py mit drin; der steht jetzt
bei 29 in Ordnung, 0 nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 15:46:03 +02:00
D4rkst3randClaude Opus 5 608b3b3097 feat: tools/rundgang.py -- 24 Pruefungen gegen den laufenden Dienst
WOZU, wo doch jede Aenderung einzeln gemessen wird: weil sie einzeln gemessen
wird. An einem Tag mit sechs Neustarts fasst niemand mehr alles an, und die
Sachen, die keiner mehr anfasst, sind genau die, die kaputtgehen -- ein WebP,
das nach einem Umbau nicht mehr ausgehandelt wird, meldet sich nicht.

Kein Ersatz fuer die Tests: er prueft nicht, ob eine Funktion das Richtige
rechnet, sondern ob der Dienst, so wie er gerade laeuft, die Sachen noch tut,
die er koennen muss. Deshalb geht er ueber HTTP und nicht am Code vorbei. Nur
lesend -- kein Upload, kein Loeschen, keine Einstellung wird angefasst.

ZUERST kommt, was OHNE ANMELDUNG DICHT SEIN MUSS (acht Wege, alle 401). Das ist
die Sorte Fehler, die man nicht sieht: eine Route, die versehentlich vor dem
Waechter registriert wird, funktioniert praechtig -- fuer alle. Genau das ist
hier schon zweimal passiert.

Dann Bestand, /f/ mit WebP-Aushandlung an derselben Adresse und Vary: Accept,
beide CSV-Exporte auf nackte LF, Papierkorb-Deckel, Sicherungsziele und die
Statusseite.

ZWEI FEHLSCHLAEGE BEIM BAUEN, beide im Test und nicht im Dienst -- sie stehen
als Kommentar drin, damit der naechste sie nicht wiederholt:

  1. Ohne Filter zog er vehicles/asbo.webp und verglich WebP mit WebP. Gleiche
     Groesse, Test rot, Dienst richtig.
  2. Der Filter hiess dann "q=" statt "query=". Unbekannte Parameter ignoriert
     der Dienst und liefert ALLES -- was wie ein kaputter Filter aussieht und
     ein kaputter Aufruf ist.

Ein Test, der das Falsche misst, ist schlimmer als keiner: er wird geglaubt.

Gemessen: 24 in Ordnung, 0 nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 09:01:23 +02:00
D4rkst3randClaude Opus 5 74e2128afb feat: die Sicherung liegt an drei Orten statt an zweien
Der letzte offene Punkt aus docs/ideen.md. Ein ZWEITES LAUFWERK, einstellbar im
Panel unter Einstellungen -> Sicherung; sichern.ps1 legt das Archiv dort ab,
vergleicht die Pruefsumme und duennt auch dort aus.

WARUM DAS NICHT DASSELBE IST WIE DIE NEXTCLOUD. Die hilft, wenn der ganze
Rechner weg ist -- aber wer 335 MB, spaeter 100 GB, zurueckholen muss, laedt sie
ueber die Leitung. Das zweite Laufwerk ist in Minuten zurueckgespielt. Zwei
Fragen, zwei Antworten, deshalb zwei Ziele.

UND ES WARNT, WENN ES DIESELBE PLATTE IST -- das ist der eigentliche Inhalt.
Ein zweiter Ordner auf C: sieht im Panel genauso gruen aus wie eine echte zweite
Platte und hilft gegen gar nichts. Verglichen wird die PHYSISCHE Platte und
nicht der Laufwerksbuchstabe: zwei Partitionen derselben NVMe sterben zusammen.
Nachgemessen in beide Richtungen:

    C:\backup gegen D:\backup  ->  Nr. 0 gegen Nr. 1  ->  "andere Platte"
    C:\backup gegen C:\Users   ->  Nr. 0 gegen Nr. 0  ->  Warnung

Auf diesem Rechner sind das zwei getrennte NVMe zu je 954 GB.

GEMESSEN AN EINEM ECHTEN LAUF, keinem Trockentest: 4630 Eintraege, 335,9 MB,
auf beiden lokalen Zielen und in der Nextcloud dieselbe Pruefsumme
FFCE4D0A7310... -- identisch.

Geprueft wird mit der Pruefsumme und nicht mit der Dateigroesse: eine
abgebrochene Kopie auf eine volle Platte hat oft genau die richtige Laenge und
trotzdem Nullen am Ende. Schlaegt das Kopieren oder der Vergleich fehl, ist der
ganze Lauf gescheitert (exit 1, ok:false) -- ein Ziel, das still ausfaellt, ist
genau das, wogegen das hier gebaut ist.

Der Bericht traegt jetzt zweit:true. Fehlt das Feld, ist KEIN zweites Ziel
eingerichtet -- es steht absichtlich nicht als false da, denn das laese sich wie
"hat nicht geklappt".

Die Statusseite zaehlt die Orte mit: "sicherung ok an 3 Orten" statt nur "ok".
Eine Sicherung, die es nur einmal gibt, ist gruen und trotzdem eine, die ein
Plattenausfall mitnimmt -- das gehoert dorthin, wo jemand hinsieht.

Der Pfad wird beim Speichern auf seine FORM geprueft (Laufwerksbuchstabe oder
UNC-Freigabe): der Dienst laeuft im Container und kann ihn nicht nachschlagen,
aber ein relativer Pfad ist mit Sicherheit ein Tippfehler -- und ein Tippfehler
in einem Sicherungsziel faellt sonst erst auf, wenn man die Sicherung braucht.
Nachgemessen: "backup/woauchimmer" -> 400 mit Text.

Und zurueckspielen.ps1 sagt im Kopf, was zu tippen ist, wenn genau diese Platte
das Problem ist. Das ist der Fall, fuer den das Ganze da ist, und niemand soll
ihn um vier Uhr nachts erst herleiten muessen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 08:50:44 +02:00
D4rkst3randClaude Opus 5 463a821199 feat: der Dienst heisst media.d4rkst3r.de -- beide Namen liefern weiter aus
PUBLIC_URL umgestellt. FILES_HOST ist leer, der Dienst achtet also gar nicht
auf den Hostnamen, und in NPM stehen beide auf DEMSELBEN Host -- die drei
^~-Bloecke gelten damit fuer beide. Nachgemessen: unter beiden Namen health 200
und dasselbe Bild, bytegleich.

Nachgezogen:

    7 Stellen in den Ressourcen   d4rk_phone/config.lua (2)
                                  d4rk_photostudio/config.upload.lua
                                  d4rk_photostudio/config.upload.example.lua (2)
                                  d4rk_photostudio/settings.json (2)
    2 Watchdog-Eintraege im Bot   inklusive Anzeigename
   16 Stellen in Doku/Werkzeugen  README, docs/API.md, Home.md,
                                  npm-advanced.conf, proxy-pruefen.ps1,
                                  uebernehmen.ps1, .env.example

Die Freigabe-Links zeigen von selbst auf den neuen Namen -- sie werden aus
PUBLIC_URL gebaut und stehen nirgends gespeichert. Die alten funktionieren
weiter.

DIE ROADMAP BEHAELT DEN ALTEN NAMEN, an sechzehn Stellen und mit Absicht: dort
steht die Geschichte, warum er ueberhaupt "fivemanage" hiess. Sie zu ersetzen
machte aus einer Begruendung Unsinn.

ZWEI DINGE, DIE DABEI AUFFIELEN.

Cloudflare stand vor dem neuen Namen und brach die WebP-Auslieferung: der
Cache-Schluessel kennt Accept nicht, also bekam ein Aufrufer OHNE
"Accept: image/webp" die WebP-Fassung aus dem Zwischenspeicher -- unsere
eigenen Bytes unter einem .png-Namen. Nach dem Umstellen auf DNS-only gemessen
und in Ordnung: ohne Accept image/png 12295 B, mit Accept image/webp 2270 B,
Server: openresty, vary: Accept, dreimal hintereinander stabil.

Und der Lua-Syntaxtest schlug bei d4rk_phone/config.lua fehl -- an Zeile 93,
`customModel = \`prop_player_phone_02\``. Das ist kein Schaden, sondern eine
CfxLua-Erweiterung: Backticks sind dort ein joaat-Hash und in Standard-Lua 5.4
ein Syntaxfehler. 85 davon stecken in der Datei. luac -p taugt fuer
CfxLua-Dateien also nicht; geprueft wurde stattdessen, dass genau die zwei
URL-Zeilen anders sind.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 01:02:28 +02:00
D4rkst3randClaude Opus 5 3f706b27e1 feat: /status fuer die Statusseite -- und der Fehler, den es dabei gefunden hat
/health gab es und bleibt, wie es ist: app.get('/health', c => c.json({ok:true})).
Diese Zeile beweist genau eines -- der Prozess nimmt Anfragen an. Daran haengt
der HEALTHCHECK des Containers, und DORT ist billig richtig: eine schwere
Pruefung, die bei einer langsamen Platte einmal ausfaellt, liesse Docker den
Container neu starten, also genau dann, wenn er unter Last steht.

Fuer eine Statusseite ist das zu duenn -- sie stuende auf Gruen, waehrend die
Platte voll ist und kein Upload mehr angenommen wird.

/status sieht deshalb wirklich nach: Datenbank (eine echte Abfrage, nicht "die
Datei ist da"), Platte (schreiben UND wieder loeschen, der einzige Beweis),
Ausliefern (eine zufaellige Datei aus der Datenbank auf der Platte nachmessen),
Bestand, Platz und Sicherung. 200 wenn der Dienst sein Geschaeft tut, 503 wenn
nicht; ?streng=1 laesst auch eine Beeintraechtigung rot werden -- WELCHES von
beiden richtig ist, weiss nur, wer die Statusseite betreibt.

Oeffentlich, aber wortkarg: keine Dateizahlen, Groessen, Pfade, Tokennamen,
Benutzer. Und mit einer Zehn-Sekunden-Bremse -- ein oeffentlicher Endpunkt, der
auf die Platte schreibt, waere sonst ein Verstaerker.

UND DABEI FIEL EIN FEHLER IN MEINEM EIGENEN ZURUECKSPIEL-SKRIPT AUF.

Der harte Weg sollte an einem Wegwerf-Container geprueft werden. Der traf
zufaellig auf ein GEBRAUCHTES Volume, und die Zahlen waren eindeutig:

    media.db aus dem Archiv, mit altem WAL daneben :     0 Zeilen
    dieselbe Datei ohne die beiden Begleiter       :  4452 Zeilen
    Dateien auf der Platte                        :  4452

zurueckspielen.ps1 entfernte media.db, aber NICHT media.db-wal und
media.db-shm. Die liegen bei einem echten Zurueckspielen immer da -- der
laufende Dienst arbeitet im WAL-Modus. SQLite spielt das WAL der ALTEN
Datenbank ueber die NEUE, und heraus kommt der schlimmste denkbare Zustand: der
Dienst kommt hoch, /health ist gruen, die Mediathek ist leer, waehrend alle
Dateien danebenliegen.

Drei Konsequenzen:

1. Die Aufraeumzeile steht jetzt an EINER Stelle und nimmt media.db-wal,
   media.db-shm und *.tmp mit. Uebung und Ernstfall fahren denselben Befehl --
   zwei Fassungen waeren zwei, von denen die geuebte die harmlosere ist.

2. Die Uebung TAEUSCHT JETZT EINE BESTEHENDE INSTALLATION VOR, bevor sie
   zurueckspielt: Container starten, warten bis media.db-wal daliegt, stoppen,
   und erst dann einspielen. In ein leeres Volume zu spielen probt den Fall,
   der nie eintritt.

3. /status erkennt den Zustand selbst -- "kein Eintrag in der Datenbank, aber
   Dateien auf der Platte". Am kaputten Container gemessen:

       /health sagt:  HTTP 200
       /status sagt:  HTTP 503

Der geuebte Lauf danach, ueber eine vorgetaeuschte Installation:

    im Volume liegt jetzt: files media.db media.db-shm media.db-wal
    4452 Medieneintraege, 4452 Dateien -- gleich viele
    ok  items/shushi.png · items/weedbud_1.png · items/cc-castella.png
    Die Uebung ist bestanden.

Gefunden beim Ueben und nicht im Ernstfall. Genau dafuer gibt es sie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 00:04:26 +02:00
D4rkst3randClaude Opus 5 338f8e59cf feat: die Sicherung laesst sich jetzt zurueckspielen -- und wurde es auch
Bis hierhin war sie GEPRUEFT, nicht ERPROBT. sichern.ps1 packt sein Archiv aus,
oeffnet die Datenbank, zaehlt, laedt hoch, holt zurueck und vergleicht die
Pruefsumme. Das beantwortet "ist die Datei heil?".

Es beantwortet nicht die Frage, auf die es ankommt: "kommt daraus wieder ein
laufender Dienst?" Dazwischen liegen Handgriffe, die man sonst zum ersten Mal
an dem Abend macht, an dem man sie unter Druck macht -- wie das Volume heisst,
dass der Container stehen muss, wem die Dateien gehoeren muessen, dass thumbs/
und webp/ abgeleitet sind und neu gerechnet werden wollen.

Ein Backup, das man nie zurueckgespielt hat, ist kein Backup, sondern eine
Datei.

DIE UEBUNG IST DER STANDARD, und sie ist der eigentliche Gewinn: Wegwerf-
Volume anlegen, Archiv hineinspielen, einen ZWEITEN Container davorhaengen
(ohne veroeffentlichten Port -- der Uebungslauf soll sich mit nichts anlegen,
was schon laeuft), auf /health warten, nachzaehlen, und dann der Teil, auf den
es ankommt: drei zufaellige Dateien DURCH den Dienst holen und ihr SHA-256
gegen den vergleichen, der in der Datenbank steht. Danach alles abraeumen, im
finally -- ein liegengebliebenes Uebungs-Volume mit 300 MB findet sonst niemand
wieder.

Gelaufen, nicht behauptet:

    4452 Medieneintraege, 316,3 MB, 2 Token, 2 Benutzer, 0 im Papierkorb
    4452 Dateien -- gleich viele wie Eintraege
    ok  items/velum2.png                  113567 B
    ok  items/flesh-dolphin-2star.png       3623 B
    ok  items/burgermeat.png                4269 B
    Die Uebung ist bestanden.

Der echte Dienst lief die ganze Zeit weiter und war danach unveraendert
(health 200, dasselbe Volume, 4542 Dateien).

DER ERNSTFALL verlangt ein getipptes ZURUECKSPIELEN und sagt vorher, was
passiert. Er loescht thumbs/ und webp/ mit: Abgeleitetes stehenzulassen waere
schlimmer als es neu zu rechnen -- die Galerie zeigte sonst die Vorschau einer
Datei, die dort nicht mehr liegt. Loeschen und Einspielen laufen in EINEM
Container-Aufruf, damit dazwischen niemand den Dienst wieder startet. Geht
etwas schief, sagt das Skript, dass der Container steht, und wie man ihn
startet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:50:46 +02:00
D4rkst3randClaude Opus 5 914f2fb854 docs: der NPM-Block zum Einfuegen, und ein Skript, das sagt ob er sitzt
docs/npm-advanced.conf enthaelt genau das, was in NPM unter Advanced ->
Custom Nginx Configuration gehoert: die drei ^~-Bloecke fuer /f/, /t/ und
/api/, samt Begruendung als Kommentar. Die ganze Datei ist einfuegbar --
nginx kennt '#'. Syntax gegengeprueft mit `nginx -t` in einem
Wegwerf-Container, und zwar mit DIESEM Dateiinhalt und nicht mit einer
abgetippten Kurzfassung.

/s/ steht bewusst NICHT drin: die Schluessel bestehen aus einem festen
Alphabet ohne Punkt, eine Freigabe-Adresse endet also nie auf eine
Bildendung und faellt gar nicht erst in die assets.conf-Regel. /assets/
ebenfalls nicht -- die Dateinamen der gebauten Oberflaeche tragen einen Hash
und aendern sich nie unter demselben Namen, dort ist der Zwischenspeicher
richtig.

tools/proxy-pruefen.ps1 misst danach, ob es gewirkt hat, und sagt sonst, was
zu tun ist. Beim Schreiben hat es gleich eine SCHWAECHE IN SICH SELBST
gezeigt: die erste Fassung prueft "/api/ ohne Token -> 401" und meldete
Erfolg -- der 401 war aber nur ein zwischengespeicherter, den der Proxy an
alle ausliefert. Gemessen bekam ihn sogar ein Aufrufer MIT gueltigem Token.

Verlaesslich ist nur das Paar auf einer FRISCHEN, zufaelligen Adresse:

    GET /api/exists/probe-xyz.webp   mit Token    -> 200
    GET /api/exists/probe-xyz.webp   ohne Token   -> 401  gut
                                                  -> 200  Block fehlt

Aktueller Stand, gemessen:

    [ok]  /f/  Cache-Control                     public, max-age=300
    [!!]  /t/  Cache-Control                     max-age=10608
    [ok]  /api/ mit Token -> 200                 HTTP 200
    [!!]  /api/ dieselbe Adresse ohne Token      HTTP 200 -- der Proxy gibt sie weiter
    [ok]  /api/exists?pfad ohne Token -> 401     HTTP 401

Die letzte Zeile ist der Grund, warum die Abfrageform gebaut wurde: sie ist
dicht, ohne von dieser Proxy-Einstellung abzuhaengen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:34:02 +02:00
D4rkst3randClaude Opus 5 06e924daf5 fix: die Sicherung liess den Papierkorb aus -- ein Versprechen, das sie nicht halten konnte
GEFUNDEN BEIM NACHSEHEN, nicht beim Suchen: das Sicherungsskript packte

    tar czf ... -C /data files -C /data/.sicherung media.db

also die Bilder und die Datenbank. Die EINTRAEGE des Papierkorbs stehen in
media.db und waren damit laengst gesichert -- die DATEIEN lagen unter
/data/papierkorb und waren es nicht.

Nach einem Zurueckspielen haette der Papierkorb also Zeilen gezeigt, deren
"zurueckholen" ins Leere greift: die Oberflaeche verspricht etwas, das die
Platte nicht hergibt. Genau die Sorte stiller Abweichung, wegen der dieses
Projekt Sicherungen ueberhaupt gegenprueft.

Er ist jetzt im Archiv, und er kostet fast nichts: was darin liegt, ist
hoechstens 30 Tage alt, danach raeumt der Dienst selbst auf. Die Abfrage
`if [ -d /data/papierkorb ]` davor ist noetig -- hat noch nie jemand etwas
geloescht, gibt es den Ordner gar nicht, und tar braeche mit "Cannot stat" ab.
Ein `mkdir` als Ausweg ginge nicht: das Volume haengt nur lesend.

Gemessen an einem echten Lauf, nicht angenommen:

    d4rk_media-2026-08-11-2307.tar.gz -- 311,9 MB
    in der Sicherung: 4452 Medieneintraege, 4452 Dateien, 2 Token, 2 Benutzer
    hochgeladen (201), zurueckgeholt und verglichen: identisch
    Inhalt:  4459 files/   1 media.db   1 papierkorb/

UND DER PAPIERKORB SAGT ES JETZT SELBST. Die Liste prueft je Zeile, ob die
Datei wirklich noch daliegt (hoechstens 500 stat-Aufrufe), und zeigt fehlende
durchgestrichen samt Erklaerung. Die Sicherung nimmt ihn zwar inzwischen mit --
aber ein Papierkorb, der etwas verspricht, muss es halten koennen, und wenn
nicht, soll er es SAGEN statt es beim Druecken herauszufinden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 23:11:38 +02:00
D4rkst3randClaude Opus 5 44ad7dc6b3 feat: Umbenennen, Fivemanage-Sprache, Freigabe-Links, sichtbare Zahlen
UMBENENNEN gab es gar nicht -- verschieben ja, umbenennen nirgends. Jetzt fuer
Datei und Ordner. Die WARNUNG ist dabei der eigentliche Teil: beim Verschieben
wandert eine Datei, beim Umbenennen aendert sich IHRE ADRESSE, und die steht
womoeglich in einem Skript, das niemand mehr im Kopf hat. Der Dialog zeigt
deshalb die Abrufzahl und beim Ordner die Zahl der betroffenen Dateien, BEVOR
gedrueckt wird -- eine Datei mit viertausend Abrufen umzubenennen ist etwas
anderes als eine mit null, und der Dienst ist die einzige Stelle, die den
Unterschied kennt.

Am echten Fall geprueft, dem Kollisionsfund: items/coiloverss.png (hielt die
+-Fassung) -> items/coiloverss-plus.png, alte Adresse 404, neue 200 mit 4375 B,
Vorschau mitgewandert. Ordner: probe -> beispiele, 5 Dateien, alle Pfade in
einer Transaktion umgeschrieben.

Ein Fallstrick dabei: thumbPath haengt ".webp" an. Fuer eine Datei richtig, fuer
einen ORDNER Unsinn -- der Vorschauordner heisst thumbs/vehicles und nicht
thumbs/vehicles.webp. Dafuer gibt es jetzt moveFolder.

DIE 93 KOLLISIONEN SIND ERLEDIGT. tools/kollisionen.ps1 rechnet dieselbe
Zaehmung auf der Quelle nach und zeigt, was zusammenfaellt -- es aendert nichts,
damit die Entscheidung auf Zahlen steht. Ergebnis: 91 von 93 sind dieselbe Datei
zweimal (WEAPON_SMG.png und weapon_smg.png, byteweise gleich gross), da fehlte
nichts. Echt verloren waren ZWEI, beide mit + im Namen. Beide nachgeholt.

FIVEMANAGE-SPRACHE. POST /api/image|video|audio (v1) und POST /api/v3/file (v3),
Schluessel nackt im Authorization-Kopf. Auf einem laufenden Server stecken die
Fivemanage-Aufrufe in einem Dutzend fremder Ressourcen; sie alle umzuschreiben
tut niemand, und deshalb bliebe dieser Dienst ungenutzt daneben stehen. So ist
der Umzug eine Zeile je Skript: die Adresse.

Beide Formen sind AUS ECHTEM CODE abgelesen und nicht geraten -- fivemanage/sdk
fuer v3, Awleks/Devm-Camera fuer den aelteren Weg ueber screenshot-basic.
Gemessen: v1 -> {url,id,path}, v3 -> {status:"ok",data:{id,url}}, ohne
Schluessel 401, ein Video an /api/image -> 415 mit Grund.

Dabei bin ich in eine Falle gelaufen, vor der im eigenen Repo ein Kommentar
warnt: die Token-Wache hing an use('*'), und der Einhaengepunkt ist /api -- also
galt sie auch fuer /api/dash daneben. Das Dashboard bekam 401 auf die ANMELDUNG.
Wortwoertlich derselbe Fehler steht in upload.ts als Kommentar, weil er dort
schon einmal passiert ist. Gemerkt hat es der Gegentest, nicht der Kopf.

FREIGABE-LINKS. /s/<schluessel> zeigt einen Ordner ohne Anmeldung. Was dabei
ausdruecklich dabeisteht, in der Karte und in der Rueckfrage vor dem
Zurueckziehen: FREIGEGEBEN WIRD DIE LISTE, NICHT DER INHALT. Die Dateien sind
ohnehin oeffentlich; ein zurueckgezogener Link macht sie nicht wieder privat, er
nimmt nur die Uebersicht weg. Ohne diesen Satz zieht jemand einen Link zurueck
und glaubt, etwas sei verschwunden.

Die Antwort ist abgemessen: Name, Groesse, Art, Adresse, Laenge. NICHT Hash,
Token, Zeitpunkte, Abrufzahlen, IDs -- nichts davon braucht, wer einen Katalog
ansieht, und jedes davon waere eine Auskunft ueber den Betrieb. Ein unbekannter
Schluessel und ein zurueckgezogener geben dieselbe Antwort.

ZAHLEN, DIE SCHON DA WAREN. Sortieren nach Abrufen und nach "zuletzt geholt";
die Kachel zeigt dann auch diese Zahl statt Groesse und Datum, denn nach etwas
zu ordnen, das man nirgends sieht, ist eine Reihenfolge ohne Begruendung. Und je
Token, was damit abgelegt wurde: media.token_id wird seit dem ersten Tag
geschrieben und war NIRGENDS zu sehen. Gemessen: d4rk_photostudio haelt 810
Dateien / 99,5 MB, 3640 liegen ohne Token da.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:53:22 +02:00
D4rkst3randClaude Opus 5 e4a125e1c6 fix: ein Upload durfte den ganzen Dienst umbringen -- und tat es
BEIM UEBERNEHMEN VON 3670 DATEIEN STARB DER DIENST. Nicht einmal, sondern in
Schleife: acht Neustarts, nach aussen 502 vom Proxy.

    TypeError [ERR_INVALID_STATE]: ReadableStream is already closed
        at ReadableByteStreamController.close (node:internal/webstreams/...)

DIE URSACHE WAR MEINE EIGENE AENDERUNG von vor einer Stunde. Um PowerShells
Formular-Rumpf zu vertragen, hatte ich `c.req.parseBody()` durch
`c.req.raw.formData()` ersetzt -- und damit Hono den Rumpf weggenommen, den es
selbst verwaltet. Beim Aufraeumen der Antwort schliesst dann jemand einen
Strom, der schon zu ist. Die Ausnahme faellt in einem Microtask an, also
AUSSERHALB jedes try/catch, und Node beendet den Prozess.

Beim Test mit 57 Dateien fiel das nicht auf. Bei 3670 schon.

Jetzt geht der Normalfall wieder ueber Hono; der eigene Weg gilt nur noch fuer
die Antwort mit Anfuehrungszeichen an der Grenzmarkierung, fuer die er gedacht
war.

UND EIN NETZ DARUNTER: process.on('uncaughtException') faengt, was ausserhalb
jedes try/catch anfaellt, und laesst den Dienst weiterlaufen. Die Abwaegung
steht im Code: das ist NICHT in jedem Fall richtig -- der Prozess kann danach
kaputt sein. Hier ueberwiegt das Weiterlaufen, weil dieser Dienst keinen
Zustand im Speicher haelt (alles in SQLite und auf der Platte) und ein Dienst,
der wegen EINER Anfrage fuer alle weg ist, der schlechtere Tausch waere. Laut
wird es trotzdem.

DER SCHADEN WAR REPARIERBAR, und zwar mit dem Werkzeug von heute Nachmittag:
206 Dateien waren durch, 2 lagen auf der Platte OHNE Datensatz (geschrieben,
dann starb der Prozess vor dem Eintrag), dazu eine verwaiste Vorschau. Der
Verwaisten-Finder hat sie gefunden, "aufnehmen" hat sie eingetragen -- keine
Datei verloren.

Und die Fehlermeldung im Uebernahmeskript verschwieg die Ursache: sie meldete
"Conversion from JSON failed ... Unexpected character <", obwohl die Wahrheit
ein 502 war. Jetzt wird der HTTP-Code mitgelesen, bei 502 sauber abgebrochen
und gesagt, dass ein neuer Lauf einfach weitermacht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 21:04:27 +02:00
D4rkst3randClaude Opus 5 5d35381952 feat: Dateien aus der Nextcloud uebernehmen
tools/uebernehmen.ps1 holt einen Nextcloud-Ordner in den Dienst. Unterordner
bleiben erhalten, -Probe zeigt erst nur, was passieren wuerde.

UEBER DAS DASHBOARD UND NICHT UEBER DIE TOKEN-API. Nextcloud-Dateien heissen
"Brand Logo (final).PNG" -- checkPath lehnt das mit Recht ab. Der
Dashboard-Weg biegt den Namen bereits vorhersagbar zurecht. Dieselbe Regel an
einer Stelle ist besser als dieselbe Regel an zweien, von denen eine irgendwann
abweicht.

WIEDERHOLBAR ueber overwrite=false: was schon da ist, meldet der Dienst je
Datei als "Pfad ist belegt". Nachgemessen mit 57 Marken -- zweiter Lauf: 0
uebernommen, 57 waren schon da.

DREI FEHLER BEIM BAUEN, alle durch Messen gefunden:

1. Ein kaputter Formular-Rumpf ergab einen NACKTEN 500, aufgefangen nur von der
   Auffanglinie. Jetzt 400 mit Grund.

2. PowerShells `Invoke-RestMethod -Form` scheitert am Dienst. Ich hatte die
   Ursache zuerst falsch: nicht (nur) die Grenzmarkierung in
   Anfuehrungszeichen, die RFC 2046 erlaubt und die wir jetzt vertragen --
   sondern der Rumpf selbst. Mitgeschnitten:

       Content-Disposition: form-data; name=file; filename=a.png

   RFC 7578 verlangt name="file" MIT Anfuehrungszeichen, .NET laesst sie weg,
   und Nodes Parser besteht darauf. Das steht im RUMPF; dafuer braeuchte es
   einen eigenen Multipart-Parser, und der waere unverhaeltnismaessig. Die
   Werkzeuge nehmen deshalb curl.exe -F.

3. PowerShell-Falle: curl.exe gibt zeilenweise ein ARRAY zurueck, und
   -match/-notmatch darauf FILTERT, statt zu pruefen. Ein nicht-leeres Ergebnis
   gilt als wahr -- damit meldete das Skript "Kein Verzeichnis gefunden" bei
   einer tadellosen Antwort mit HTTP 207. Jetzt wird vorher zusammengefuegt,
   auch in sichern.ps1, wo dieselbe Falle nur deshalb nicht zuschlug, weil die
   Ausgabe eine Zeile hat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:59:59 +02:00
D4rkst3randClaude Opus 5 66c5de1e31 docs: das Wiki gibt es jetzt -- und es kommt aus docs/
Gitea legt das Wiki-Repo erst mit der ersten Seite an; Klon und Push antworten
davor mit 500, obwohl has_wiki auf true steht. Angelegt ueber die API
(POST /api/v1/repos/<besitzer>/<repo>/wiki/new) -- danach ist es ein
gewoehnliches Git-Repo und laesst sich klonen wie jedes andere.

  https://git.d4rkst3r.de/D4rkst3r/d4rk_media/wiki

DIE QUELLE BLEIBT docs/ IM REPO. Ein Wiki hat keinen Zusammenhang mit dem Code:
niemand sieht, ob die Anleitung noch zu dem passt, was der Dienst tut. Unter
docs/ wandert sie im selben Commit mit der Aenderung, die sie beschreibt.
tools/wiki.ps1 traegt sie danach hinaus -- EINSEITIG, und die Wiki-Seite sagt
das auch: wer dort tippt, verliert es beim naechsten Lauf. Unschoen, aber
ehrlicher als zwei Quellen, die auseinanderlaufen.

Zwei Seiten: Home (Zuschnitt, was der Dienst kann, was zu tun ist, wenn etwas
nicht geht) und API (Endpunkte, Kopfzeilen, Fehlerantworten, Grenzen -- und die
beiden Proxy-Fallen von heute).

Nachgemessen: beide Seiten sind ohne Anmeldung lesbar (200), ein zweiter Lauf
des Skripts erkennt "bereits aktuell", und eine Aenderung kommt an.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:09:35 +02:00
D4rkst3randClaude Opus 5 4e613dd40d feat: ein Einstellungen-Tab -- und die Sicherung meldet sich zurueck
Die Zugangsdaten der Sicherung standen in einer Textdatei auf der Platte. Jetzt
stehen sie im Panel, zusammen mit der Discord-Einrichtung, die vom Konto
dorthin umgezogen ist.

DER EIGENTLICHE GEWINN IST NICHT DIE BEQUEMLICHKEIT. Eine Textdatei sagt
niemandem, ob die Sicherung heute Nacht gelaufen ist -- und eine Sicherung, die
still aufhoert zu laufen, ist der eigentliche Schaden, nicht die eine Nacht, in
der sie fehlt. Das Skript schreibt sein Ergebnis deshalb nach jedem Lauf in die
Datenbank, und die Karte zeigt es als Erstes: wann, ob erfolgreich, wie viele
Dateien, wie gross, und ob es in der Nextcloud gelandet ist oder nur lokal.
Nachgemessen: {"dateien":228,"groesse":12063419,"hoch":false,"ok":true}.

DAS PASSWORT GEHT NICHT UEBER DIE API. Das Skript laeuft auf dem Wirt, nicht im
Dienst -- es liest die Werte per `docker exec` DIREKT aus media.db. Ueber HTTP
geht das Passwort nur beim Eintragen im Browser, und das ueber TLS. Zurueck gibt
die API nur "hatPasswort: true", wie beim Discord-Geheimnis.

nextcloud.txt neben den Sicherungen gilt weiter und hat VORRANG: wer sie
angelegt hat, soll nicht suchen muessen, warum sie ploetzlich ignoriert wird.

Zwei Fussangeln beim Melden, beide beim Bauen aufgefallen:
- Der JSON-Text geht ueber die STANDARDEINGABE in den Container und nicht als
  Argument -- Anfuehrungszeichen ueberleben den Weg durch zwei Shells nicht.
- Auch dieses Hier-Dokument braucht das -replace "`r": dieselbe CRLF-Falle wie
  bei der Pruefung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:39:16 +02:00
D4rkst3randClaude Opus 5 74cf56ce81 feat: eine Startseite, und die Sicherung geht in die Nextcloud
STARTSEITE. Wer das Dashboard oeffnet, landete bisher in der Galerie -- mit 228
Kacheln und ohne Antwort auf die erste Frage, die man beim Oeffnen hat: laeuft
alles? Jetzt zuerst eine Uebersicht: Zahlen, was heute dazukam, ein
Bilderstreifen der letzten Uploads, die letzten acht Schritte aus dem Verlauf,
die groessten Ordner, die Zugaenge -- und die ADRESSVORLAGE zum Kopieren, weil
das die haeufigste Frage ueberhaupt ist. Die Galerie zieht auf /galerie um.

Alles in EINER Antwort (/api/dash/uebersicht, gemessen 14 ms). Vier
Einzelabfragen waeren vier Runden ueber den Proxy und koennten auseinander-
laufen: Dateizahl aus der einen und letzte Ereignisse aus der naechsten Antwort
sind zwei verschiedene Zeitpunkte.

Der Router zog dabei nach src/router.ts um. Der Grund ist mehr als Geschmack:
die Startseite braucht eine Navigationsfunktion, die anderen Seiten nicht --
und sobald EINE Seite eine Eigenschaft mehr hat, passt die gemeinsame Liste in
App.tsx nicht mehr. Genau daran ist der erste Versuch gescheitert (TypeScript
hat es gefangen). Als Modulfunktion hat wieder jede Seite dieselbe Signatur.

SICHERUNG IN DIE NEXTCLOUD. Bisher lagen die Archive auf derselben Platte wie
die Daten -- gegen einen Plattenausfall half das gar nicht. Jetzt gehen sie per
WebDAV hinauf, mit curl statt rclone: Nextcloud spricht WebDAV, curl spricht
WebDAV, und curl ist ohnehin da.

Auch dort wird NACHGEZAEHLT: die Kopie wird zurueckgeholt und die Pruefsumme
verglichen. Ein PUT, der 201 sagt, hat nichts bewiesen -- abgeschnittene
Uebertragungen sehen genauso aus. Und ausgeduennt wird ebenfalls, sonst
waechst die Nextcloud still voll.

Die Zugangsdaten liegen NEBEN den Sicherungen (C:\backup\d4rk_media\
nextcloud.txt), nicht im Repo: sie gehoeren zur Maschine, nicht zum Code. Fehlt
die Datei, sagt das Skript das einmal und sichert lokal weiter -- eine
Sicherung, die wegen eines fehlenden Passworts GAR NICHT stattfindet, waere der
schlechtere Tausch.

Dabei einmal reingefallen: die Pruefung brach mit "sh: set: Illegal option -"
ab, nachdem ein Werkzeug die Datei mit CRLF gespeichert hatte -- die Shell im
Container liest dann "set -e\r". Sieht aus wie ein Tippfehler und ist keiner.
Das Skript entfernt die Wagenruecklaeufe jetzt selbst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:27:09 +02:00
D4rkst3randClaude Opus 5 aa2575bdf4 feat: Sicherung, Anmeldebremse, Vorschaubilder -- und Schritt 5
SICHERUNG (tools/sichern.ps1, taeglich 04:30 als geplante Aufgabe).
Die Datenbank wird nicht kopiert, sondern ueber SQLites eigene
Sicherungsschnittstelle herausgeholt (server/src/backup.ts): im WAL-Modus
liegt das Zuletzte noch nicht in media.db, und selbst alle drei Dateien zu
kopieren ist nicht sicher, wenn waehrenddessen geschrieben wird. Die Bilder
kommen aus einem NUR LESEND eingehaengten Volume dazu.

Und sie prueft sich selbst: auspacken, Datenbank oeffnen, Medien/Token/
Benutzer zaehlen, mit dem laufenden Dienst vergleichen, sonst Fehler. Einmal
wirklich zurueckgespielt -- leeres Volume, zweiter Dienst, Anmeldung mit dem
echten Passwort, Bild abgerufen, Byte fuer Byte identisch. Eine Sicherung, die
nie zurueckgespielt wurde, ist eine Hoffnung.

ANMELDEBREMSE. Das Formular steht oeffentlich, und scrypt macht einen Versuch
teuer -- aber teuer ist nicht selten. Fuenf freie Versuche je Adresse, dann
Sperre ab 30 s mit Verdopplung bis 15 min, 429 samt Retry-After und einem Text,
der sagt wie lange. Erfolg setzt zurueck. Nur im Speicher: wer sich aussperrt,
startet den Container neu. Durchgemessen bis zur Erholung.

VORSCHAUBILDER. sharp erzeugt beim Upload eine 320er WebP-Fassung unter
/data/thumbs, ausgeliefert unter /t/<pfad>. Gemessen: 131502 -> 13078 Bytes,
Faktor 10; eine Galerieseite faellt von 7,5 MB auf 766 KB. KEINE Spalte in der
Datenbank -- ob es eine Vorschau gibt, sagt das Dateisystem, und die Galerie
faellt bei 404 aufs Vollbild zurueck. Eine zweite Wahrheit, die auseinander-
laufen kann, gibt es damit gar nicht erst. Ein Knopf zieht Fehlendes nach und
nennt Zahlen statt "fertig".

SCHRITT 5. Die Fivemanage-Dateien liegen in legacy/ mit Erklaerung; unsere
docker-compose.media.yml heisst jetzt docker-compose.yml. Drei Volumes und drei
Abbilder geloescht, rund 670 MB -- nachgezaehlt war vorher, dass kein Bild
darin lag.

Nebenbefund: `npm i sharp` scheitert auf diesem Rechner nicht an sharp, sondern
an better-sqlite3 -- der Host laeuft auf Node 24 (ABI 137), node-gyp uebernimmt
und findet keine Bauwerkzeuge. Genau die Falle aus dem Dockerfile. Eingetragen
wurde mit --package-lock-only, gebaut wird im Abbild auf Node 22.

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