a494f472d1d6edcb08de1ebc1d213d116a07bdf2
37
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
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>
|
||
|
|
bbb74138c8 |
feat: Verlauf als CSV, Meldung bei Upload mit Drossel, Deckel auf dem Papierkorb
Abschnitt 3 aus docs/ideen.md, bis auf die Sicherungsziele. VERLAUF ALS CSV. Der Export konnte nur den Bestand; wer nachrechnen wollte, was ueber einen Zeitraum passiert ist, sass vor einer Tabelle, die nach 500 Zeilen umblaettert. Der Knopf sitzt in der Reiterleiste und holt den Verlauf, DER GERADE OFFEN IST -- zwei Knoepfe nebeneinander waeren die Frage "welchen von beiden?" an einer Stelle, wo die Antwort schon auf dem Bildschirm steht. Semikolon und CRLF wie beim Bestands-Export; das war dort nachgemessen worden. Gemessen an der ausgelieferten Datei: 5371 Zeilen, kein einziges nacktes LF. Ohne Anmeldung 401 -- die Route liegt hinter dem Waechter, was nach dem Sitzungs-Fehler von vorgestern ausdruecklich nachgeprueft wurde. MELDUNG BEI UPLOAD -- die Drossel ist der eigentliche Inhalt. Jeder Upload schiebt eine Frist von zwei Minuten nach hinten; erst nach der Ruhe geht EINE Nachricht raus. Ohne das waere die Funktion ein Schaden: 900 Fahrzeugbilder ergaeben 900 Nachrichten, Discord drosselt Webhooks, und wer danach eine echte Meldung bekommt, sieht sie nicht mehr. Gemessen: drei Uploads mit zehn Sekunden Abstand -> eine Meldung ueber drei Dateien. Ein einzelner Upload -> eine Meldung ueber eine Datei. Nur Token-Uploads, absichtlich. Was ueber das Dashboard hereinkommt, hat gerade jemand selbst hochgeladen und bestaetigt bekommen. Zurueckholen aus dem Papierkorb meldet gar nichts: das ist kein Neues, das ist ein Wiedergefundenes. Und derselbe Fehler wie bei 'platte' lauerte schon wieder: die Liste der Anlaesse steht im Server UND in der Oberflaeche, und der Server verwirft beim Speichern alles, was in seiner Liste fehlt -- ein Kreuzchen, das sich setzen laesst und beim naechsten Laden weg ist. Diesmal beide angefasst, in beiden steht jetzt ein Verweis auf die andere. Nachgemessen: vier geschickt, vier gespeichert. DECKEL AUF DEM PAPIERKORB. 30 Tage UND 20 GB, was zuerst greift. Wer 200 GB loescht, haelt sie sonst 30 Tage doppelt und merkt es erst, wenn die Platte voll ist. Geraeumt wird das AELTESTE zuerst -- dessen Versehen waere am ehesten schon aufgefallen. Ueber PAPIERKORB_MAX_MB verstellbar, und das nicht aus Bequemlichkeit: ein Deckel von 20 GB laesst sich nicht pruefen, ohne 20 GB zu loeschen. Mit 0.0002 MB nachgemessen -- vier Eintraege zu 422 Bytes, drei entfernt, uebrig blieb der NEUESTE. Danach zurueck auf 20 GB, gemessen 21474836480. NEBENBEI, DREIMAL DIESELBE SORTE FEHLER: "0.0 MB" fuer 422 Bytes, "0 KB" fuer 106 Bytes, "0.0 MB von 0.0 MB" beim Deckel. Es gab drei Byte-Formatierer, jeder mit anderer Untergrenze. Jetzt einer in meldung.ts, von Bytes bis GB, exportiert und von dash.ts mitbenutzt. Nachgemessen ueber alle Stufen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
381feade4e |
feat: fehlende Bilder finden, Massen-Umbenennen, Bilder verkleinern, Einstellungen mitnehmen
FEHLENDE BILDER. Die Frage, die bisher niemand beantwortet hat: das Fotostudio weiss, was es aufgenommen hat, der Dienst weiss, was liegt, und die Differenz rechnet keiner aus -- weshalb "das Handy zeigt bei manchen Autos kein Bild" erst im Spiel auffaellt. POST /api/dash/fehlende nimmt ein Muster mit genau EINEM Platzhalter und eine Liste (als Array oder als Textblock aus einem Textfeld) und gibt beide Richtungen zurueck: was fehlt, und was daliegt ohne in der Liste zu stehen. Beides bedeutet etwas anderes -- fehlende sind Arbeit fuers Fotostudio, ueberzaehlige sind Platz, den man zurueckbekommt. Gesucht wird KLEINGESCHRIEBEN, weil so abgelegt wird: wer "WEAPON_SMG" hereinreicht, meint weapon_smg.png und bekaeme sonst ein "fehlt", das gelogen waere. Gemessen an echten Namen: 6 geprueft, 4 da, 2 fehlen, 985 ueberzaehlig. MASSEN-UMBENENNEN mit Vorschau. Bei 3578 Items ist eine Umbenennung von Hand keine. DIE VORSCHAU IST DER EIGENTLICHE TEIL: was hier passiert, ist dieselbe Warnung wie beim Ordner-Umbenennen -- jede alte Adresse gibt danach 404 --, nur mal tausend. Geprueft wird VOR dem ersten Schritt: Kollisionen untereinander, belegte Ziele, unzulaessige Pfade. Und EIN Problem verhindert ALLES; eine halbe Umbenennung ist der Zustand, aus dem niemand mehr herausfindet, weil die Grenze nirgends steht. Gemessen: Vorschau ueber 134 Dateien fasst nichts an; eine erzwungene Kollision gibt 409 und laesst alle drei Dateien stehen; der saubere Lauf benennt zwei um, Vorschau wandert mit, ein Eintrag im Verlauf statt zwei. BILDER BEIM ABLEGEN VERKLEINERN, je Token einstellbar. NICHT als Schutz -- das waere gemessen falsch, ein 12000x12000-PNG kostet 374 ms und 47 MB, weil libvips kachelweise arbeitet. Es geht um Plattenplatz und Ladezeit: ein Bildschirmfoto ist 3840 breit und wird auf einem Handy auf 400 gezeigt. withoutEnlargement ist dabei entscheidend, sonst blaeht die Einstellung kleine Icons auf. DABEI WAERE MIR EIN ECHTER FEHLER UNTERLAUFEN: der sha256 wird weiter oben aus dem Original gerechnet (fuer den Ersatzpfad, wenn kein X-Path kam). Wird das Bild verkleinert, ist das eine ANDERE Datei -- ein Datensatz mit dem Hash des Originals waere schlicht falsch, und jede spaetere Gegenprobe verglichen gegen etwas, das nirgends liegt. Gemessen nach der Korrektur: 3000x2000 -> 512x341, 82 KB -> 3 KB, und Datenbank und Platte stimmen in Groesse und Hash ueberein. EINSTELLUNGEN MITNEHMEN. Export als JSON, Import zurueck. GEHEIMNISSE GEHEN STANDARDMAESSIG NICHT MIT: unter den Schluesseln liegen das Discord-Client-Geheimnis und das Nextcloud-Passwort, und eine Datei, die man "mal eben" exportiert und in einen Chat zieht, ist genau der Weg, auf dem so etwas abhandenkommt. Wer sie braucht, sagt ?geheim=1 -- und der Hinweis steht dann IN der Datei, nicht nur auf dem Bildschirm, von dem er beim Weiterreichen abfaellt. Der Export mit Geheimnissen steht im Verwaltungs-Verlauf. Beim Import ueberschreiben LEERE WERTE NICHTS: eine Datei ohne Geheimnisse -- der Normalfall -- traegt sie als leere Zeichenkette, und die darf ein vorhandenes Passwort nicht loeschen. Gemessen: nach einem Import mit leerem client_secret stehen die 32 Zeichen unveraendert da. Nicht mitgenommen wird sicherung.letzte -- das ist der Bericht des letzten Laufs, kein Einstellungswert. In eine frische Anlage eingespielt behauptete er dem Wachhund eine Sicherung, die es dort nie gab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
29e516559a |
feat: Sitzungen beenden, Verwaltungs-Verlauf -- und ein Kaestchen, das log
DER FEHLER ZUERST, denn er ist meiner von gestern: die Liste der
Meldungsanlaesse stand ZWEIMAL da -- als Standard in meldung.ts und noch einmal
als Weissliste beim Speichern in dash.ts. Beim Einbau des Plattenwaechters habe
ich nur die erste angefasst.
Ergebnis: das Kaestchen "Der Platz auf der Platte wird knapp" liess sich
ankreuzen, der Server warf den Wert beim Speichern weg, und beim naechsten
Laden war es wieder aus. Ohne ein Wort dazu. Nachgemessen am laufenden Dienst:
geschickt ["sicherung","verwaiste","platte"]
gespeichert ["sicherung","verwaiste"]
Die Liste steht jetzt an EINER Stelle (ANLAESSE in meldung.ts) und wird von
beiden benutzt. Gegengeprueft: alle drei kommen an.
SITZUNGEN BEENDEN. Wer sich an einem fremden Rechner angemeldet hat und es
spaeter merkt, hatte keine Moeglichkeit das zurueckzunehmen -- ein geaendertes
Passwort half nicht, die Sitzungen haengen an einer eigenen Tabelle und
ueberleben es. Der Knopf steht im Kontomenue und nur dann, wenn es ueberhaupt
eine zweite Sitzung gibt. Gemessen: 87 offen, 86 beendet, meine lebt.
Dabei in die eigene Falle getreten: der Weg lag zuerst bei den anderen
/auth-Wegen -- und die stehen mit Absicht VOR der Wache, weil man sich anmelden
koennen muss, ohne angemeldet zu sein. Dort ist c.get('user') leer, und der Weg
antwortete mit "Cannot read properties of undefined". Hono setzt Middleware und
Handler in der Reihenfolge ihrer Anmeldung zusammen; der Pfad sagt darueber
nichts.
VERWALTUNGS-VERLAUF. Der Datei-Verlauf beantwortet "was ist mit den Dateien
passiert". Wer den Discord-Webhook geaendert, einen Token angelegt oder ein
Konto entfernt hat, stand nirgends. Bei zwei Konten verschmerzbar -- sobald ein
drittes ueber die Discord-Rolle von selbst entsteht, ist es die erste Frage.
Eigene Tabelle und nicht events: dort traegt jede Zeile path und size, und eine
Einstellungsaenderung muesste path mit etwas fuellen, das kein Pfad ist.
WERTE STEHEN DORT NIE, nur Schluesselnamen. Unter den Einstellungen liegen das
Discord-Geheimnis und das Nextcloud-Passwort; ein Verlauf, der sie mitschreibt,
macht aus einer Tabelle mit einem Passwort eine Tabelle mit allen, die es je
gab.
Beim Einbau derselbe Fehlertyp wie gestern beim SVG: im SQL-Kommentar standen
Backticks, und das Schema liegt in einem Template-Literal -- ein Backtick
beendet es. Der Uebersetzer hat es gefangen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
a91c4261de |
fix: pruneEvents lief nie von selbst -- und eine Liste, was noch ginge
DIE FUNKTION GIBT ES SEIT DEM ERSTEN TAG und sie raeumt Ereignisse aelter als 180 Tage weg. Aufgerufen wurde sie aber nur von /maintenance/prune-sessions -- einem Weg, den nicht einmal die Oberflaeche anbietet. Die dokumentierte Aufbewahrung griff damit NIE, und die Tabelle wuchs fuer immer. Zum Vergleich: pruneSessions haengt seit jeher an einem stuendlichen Takt. Das eine war verdrahtet, das andere nicht, und der Unterschied fiel niemandem auf, weil beide in derselben Zeile des Wartungswegs stehen. Gemessen: nach EINEM Tag mit Umzug und Serienlauf standen 5355 Zeilen in events. Laeuft jetzt taeglich, mit zwei Minuten Verzoegerung nach dem Start -- nicht stuendlich wie die Sitzungen, denn ein DELETE ueber ein halbes Jahr hat es nicht eilig. Dazu docs/ideen.md: was beim Bauen und Messen wirklich aufgefallen ist, nach Dringlichkeit sortiert. Mit einem eigenen Abschnitt fuer das, was gemessen wieder heruntergefallen ist -- Doppelte (0,0 MB verschwendet), Aufraeumen nach Alter (alle Stufen auf 0), eine Grenze fuer Bildmasse (12000x12000 kostet 374 ms und 47 MB, libvips streamt). Damit sie nicht beim naechsten Mal wieder aufschlagen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e8efa2431d |
feat: Favicon, Token bearbeiten, und Hochladen per Adresse
FAVICON. /favicon.ico antwortete mit 200 und text/html -- die SPA-Rueckfall-
route lieferte index.html als Icon aus. Ein leeres Blatt im Tab und 2 KB umsonst
bei jedem Aufruf, nachgemessen bevor es hier steht.
Jetzt ein SVG in den Farben der Oberflaeche (Grund #0b0d10, Akzent #4ea3ff,
dieselbe Plattenform wie in der Kopfzeile), dazu PNG in 32, 180 und 512 -- AUS
DERSELBEN SVG-Datei gerechnet und nicht zweimal gezeichnet. Plus ein
web-manifest, damit das Symbol auf einem Telefon-Startbildschirm stimmt.
Der erste Anlauf war kaputt, und zwar fuer JEDEN Parser: im SVG-Kommentar stand
"--color-grund", und XML verbietet den doppelten Bindestrich in Kommentaren.
Aufgefallen beim Rechnen der PNG, nicht erst im Browser.
TOKEN BEARBEITEN. Kontingent, Groessengrenze, Arten, Ablauf und Praefix liessen
sich nur beim ANLEGEN setzen -- wer einem bestehenden Token nachtraeglich eine
Grenze geben wollte, musste ihn neu anlegen und damit den Schluessel in jedem
Skript tauschen. Fuer eine Zahl in einer Tabelle der falsche Preis.
PATCH /api/dash/tokens/:id, jedes Feld einzeln. WEGGELASSEN heisst UNVERAENDERT,
null heisst ausdruecklich "keine Grenze" -- ohne diesen Unterschied liesse sich
eine einmal gesetzte Grenze nie wieder loeswerden. Der Schluessel selbst bleibt
unberuehrt, und das ist keine Vorsicht: in der Tabelle steht nur sein Hash.
HOCHLADEN PER ADRESSE. Der Dienst holt die Datei selbst. Das ist die
gefaehrlichste Funktion in diesem Dienst und steht deshalb in einer eigenen
Datei (holen.ts) -- ein Server, der eine vom Benutzer genannte Adresse abruft,
ist ein Angriff mit eigenem Namen.
Geprueft wird das Schema, der Anschluss und die AUFGELOESTE IP -- nicht der
Name. Ein Namensfilter waere einer fuer den, der ihn nicht umgehen will. Und
jede Umleitung wird SELBST gelaufen (redirect: 'manual') und neu geprueft;
liesse man fetch folgen, waere genau dort die Luecke.
Durchgemessen am laufenden Dienst, fuenfzehn Faelle:
Erfolgsfall abgelegt, 131956 B
127.0.0.1 / localhost / [::1] / 0.0.0.0 abgelehnt
10.x / 172.20.x / 192.168.x / 169.254.169.254 abgelehnt
127.0.0.1.nip.io (oeffentlicher Name, private IP) abgelehnt
192.168.2.1.nip.io / 169.254.169.254.nip.io abgelehnt
file:// und gopher:// abgelehnt
Anschluss 8080 / 9101 abgelehnt
Die nip.io-Faelle sind der eigentliche Beleg: ein oeffentlich aufloesbarer Name,
der auf eine private Adresse zeigt, ist der Standardweg um einen Namensfilter
herum -- und faellt hier durch, weil die IP geprueft wird.
Nicht abschliessend geprueft: eine Umleitung, die ins Private zeigt. Mir fehlt
ein oeffentlicher Umleiter, der das tut (httpbingo lehnt es mit 403 ab). Der
Code laeuft die Kette selbst und ruft je Sprung dieselbe Pruefung -- belegt ist
also die Pruefung, nicht die Kette.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
72fd330ae6 |
feat: der Dienst nennt sich selbst -- und Cloudflare bricht die WebP-Auslieferung
DER NAME KAM VIERMAL FEST AUS DEM QUELLTEXT: Kopfzeile, Anmeldeseite, Fusszeile
einer Freigabe, Tab-Titel -- und zwar als "d4rk_media", was der Name des
Repositorys ist und nicht der des Dienstes. Wer ihn aendern wollte, musste vier
Stellen finden und eine davon uebersehen.
Er kommt jetzt vom Server, aus der Einstellung marke.name -- DERSELBEN, die
schon in der Fusszeile der Discord-Meldungen steht ("D4RKST3R // MEDIA"). Ein
zweiter Name fuer die Oberflaeche waere ein zweiter, der beim naechsten Mal
anders lautet als der erste.
Ausgeliefert wird er ueber /api/dash/auth/config (oeffentlich, die
Anmeldeseite hat noch keine Sitzung) und in der Freigabe-Antwort (die Seite ist
oeffentlich und holt sonst nichts, was ihn kennen koennte). Angezeigt wird
"<marke> Media" -- die Marke allein waere falsch, sie steht ueber allem, was
hier laeuft.
EINE STELLE BLEIBT ABSICHTLICH: der Schluessel im Speicher des Browsers
(merker.ts) heisst weiter "d4rk_media.". Wer ihn umbenennt, wirft still alle
gemerkten Ansichtseinstellungen weg, und niemand versteht, warum die Galerie
ploetzlich wieder bei 60 Kacheln steht. Steht als Kommentar daneben.
---
UND DER NEUE NAME HAENGT HINTER CLOUDFLARE, was die WebP-Auslieferung bricht.
NPM ist sauber, sogar besser als vorgeschlagen: beide Namen auf DEMSELBEN Host,
die drei ^~-Bloecke gelten damit fuer beide. Der DNS-Eintrag geht aber ueber
Cloudflare (188.114.97.3, Server: cloudflare), waehrend fivemanage direkt auf
88.218.224.10 zeigt.
Gemessen, dieselbe Datei:
fivemanage (direkt) ohne Accept -> image/png 12295 B richtig
mit Accept -> image/webp 2270 B richtig
media (Cloudflare) 1. mit Accept -> image/webp 2270 B MISS
2. mit Accept -> image/webp 2270 B HIT
3. OHNE Accept -> image/webp 2270 B HIT FALSCH
Und andersherum genauso -- wer zuerst kommt, praegt den Zwischenspeicher fuer
alle. Es sind unsere eigenen Bytes (2270 = unsere WebP-Fassung), also kein
Polish, sondern ein Cache-Schluessel ohne Accept. Vary: Accept wird an den
Client durchgereicht, aber nicht zum Unterscheiden benutzt.
Fuer ein Lua-Skript heisst das: items/foo.png liefert WebP-Bytes unter einem
.png-Namen, und das faellt erst auf, wenn irgendwo ein Bild nicht laedt.
PUBLIC_URL ist deshalb NICHT umgestellt. Genau davor stand die Warnung im
letzten Commit, und sie war keine Vorsicht, sondern eine Vorhersage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
458775f9d9 |
feat: Konten sichtbar, Kontingent je Token, Plattenwaechter, Umbenennen im Verlauf
KONTEN SICHTBAR. Es gab zwei -- "admin" und eines aus Discord -- und KEINE
Stelle, an der man sie sehen konnte. Weil jeder mit der Rolle auf dem
Discord-Server beim ersten Anmelden still ein Konto bekommt, waeren aus zwei
unbemerkt zwanzig geworden: man erfaehrt es nicht, und wegnehmen ging auch
nicht.
Jetzt eine Liste mit Weg (Discord/Passwort), angelegt, zuletzt da und offenen
Sitzungen. Die Spalte "Weg" ist dabei die wichtigere Auskunft als der Name: ein
Discord-Konto haengt an einer Rolle, die jemand anders vergibt.
Zwei Sperren, beide mit Grund: das EIGENE Konto laesst sich hier nicht
entfernen -- das ist keine Bevormundung, sondern die Vermeidung des einen
Klicks, nach dem niemand mehr hereinkommt -- und das LETZTE auch nicht. Die
Rueckfrage sagt bei einem Discord-Konto dazu, dass die naechste Anmeldung ein
neues anlegt, solange die Rolle bleibt; sonst haelt jemand das Entfernen fuer
eine Aussperrung.
"zuletzt da" wird hoechstens einmal je Stunde geschrieben. Bei jedem Aufruf
waere es ein UPDATE je Anfrage, und das Dashboard stellt beim Blaettern durch
3600 Bilder eine Menge davon.
KONTINGENT JE TOKEN. max_bytes gab es laengst, aber es galt JE DATEI: ein Token
mit "hoechstens 2 MB je Bild" konnte trotzdem die Platte fuellen, es brauchte
nur genug Bilder. quota_bytes gilt fuer alles zusammen, gezaehlt ueber
media.token_id -- also ueber das, was wirklich liegt, statt ueber einen
mitlaufenden Zaehler, der auseinanderlaufen kann.
Auf BEIDEN Wegen geprueft, eigene API und Fivemanage-Weg: eine Grenze, die nur
an einer von zwei Tueren haengt, ist keine.
Die erste Fassung meldete "haelt 0.0 MB von 0.0 MB, und diese Datei braucht 0.0
MB" -- formal richtig, praktisch die Meldung, die genau die Frage nicht
beantwortet, wegen der man sie liest. Die Einheit waechst jetzt mit: "haelt 12
KB von 20 KB, und diese Datei braucht 12 KB."
PLATTENWAECHTER. Meldet bei 80, 90 und 95 Prozent, je Stufe genau einmal; wird
wieder Platz frei, meldet der naechste Engpass erneut. Der Grund steht in der
Meldung: eine volle Platte ist der eine Zustand, in dem gleichzeitig nichts mehr
hereinkommt UND die Sicherung nicht mehr schreiben kann.
Gemessen, und die Abweichung zu df ist gewollt:
gesamt 1006,9 GB
frei (bavail) 909,5 GB -> 9,7 % belegt <- was der Waechter sieht
frei (bfree) 960,7 GB -> 4,6 % belegt
df sagt 5 %
bavail zieht die ext4-Reserve ab, an die der Dienst als Nicht-root ohnehin nicht
herankommt. Er warnt damit etwas frueher -- die richtige Richtung. Wer spaeter
den Unterschied zu df sucht, findet ihn als Kommentar an Ort und Stelle.
Dabei fiel ein SCHLAFENDER FEHLER auf: der Merker gegen Wiederholungen lag unter
EINEM Schluessel fuer alle Wachhunde. Mit zwei davon haetten sie sich
gegenseitig ueberschrieben -- einer meldet, der andere haelt daraufhin seinen
eigenen Zustand fuer neu und meldet auch, oder schweigt, obwohl sich etwas
geaendert hat. Der Merker haengt jetzt am Anlass.
UMBENENNEN IM VERLAUF. Stand dort als "verschoben". Technisch stimmt das, fuer
den Leser nicht: verschoben heisst "liegt woanders", umbenannt heisst "heisst
anders, liegt noch da" -- und genau diesen Unterschied schlaegt man im Verlauf
nach. Eigene Art mit eigenem Zeichen. Die Filterleiste kennt jetzt auch move und
rename, die vorher gar nicht filterbar waren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
92b49a59a5 |
fix: /api/exists gab ohne Token Auskunft -- der Proxy hatte sie zwischengespeichert
GEMESSEN, nicht vermutet: ein GET auf
https://fivemanage.d4rkst3r.de/api/exists/vehicles/adder.webp
antwortet OHNE Authorization-Kopf mit 200 und voller Auskunft -- Pfad, Groesse,
SHA-256, Zeitpunkt. Nicht weil die Pruefung fehlt, sondern weil Nginx Proxy
Manager die berechtigte Antwort weggelegt hat und sie danach an jeden ausliefert.
Der Grund steht in dessen assets.conf und greift auf JEDE URL mit Bildendung:
location ~* ^.*\.(css|js|jpe?g|gif|png|webp|...)$ {
proxy_cache public-cache;
proxy_cache_key $host$request_uri; <- ohne Authorization
proxy_ignore_headers Set-Cookie Cache-Control Expires ...;
proxy_cache_valid any 30m;
}
Unser eigenes Cache-Control hilft nicht, proxy_ignore_headers wirft es weg. Und
es geht auch andersherum: landet zuerst ein 401 im Zwischenspeicher, bekommen
ihn 30 Minuten lang alle -- auch die mit gueltigem Token.
Der Block "location ^~ /api/ { proxy_cache off; }" schliesst das. Aber eine
ZUGANGSPRUEFUNG, DIE AN EINER ZEILE IN EINER FREMDEN OBERFLAECHE HAENGT, IST
KEINE: sie ueberlebt kein Neuanlegen des Proxy-Hosts und keinen Umzug.
Deshalb eine zweite Form, die von sich aus dicht ist:
GET /api/exists?pfad=vehicles/adder.webp
Die Abfrage zaehlt fuer die location-Regel nicht mit, die URL endet also nie auf
eine Bildendung. Gemessen: mit Token 200, ohne Token 401, und KEINE
Cache-Control-Kopfzeile vom Proxy -- die Regel greift nicht.
Die alte Form bleibt, weil das Fotostudio sie benutzt (server/upload.lua:185).
Dokumentation und die API-Seite im Dashboard nennen ab jetzt nur noch die neue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
d125c6bf72 |
feat: Vorschau fuer Video und Ton, Ordner anlegen dort, wo man es sucht
Mit den ersten echten Clips fiel auf, was bei 3600 Bildern nie auffiel: alles,
was kein Bild ist, bekam ein FRAGEZEICHEN auf die Kachel -- ausgerechnet das
Zeichen fuer "unbekannt" auf einer voellig gesunden MP4.
Jetzt: ein Standbild aus dem Clip (ein Zehntel hinein, weil viele mit Schwarz
oder einem Einblender beginnen und eine schwarze Kachel so aussagekraeftig ist
wie gar keine), bei Ton das eingebettete Titelbild, dazu ein Abspielzeichen und
die LAUFZEIT in der Ecke. Fehlt eine Vorschau, kommt ein Zeichen, das die ART
benennt, statt eines Fragezeichens.
DER PREIS IST FFMPEG IM ABBILD, und der wurde zweimal gemessen und zweimal
gedrueckt:
vorher 460 MB
apt-get install ffmpeg 1,11 GB
statisches ffmpeg + ffprobe 835 MB
statisches ffmpeg allein 648 MB
Debians Paket schaltet jeden Filter frei und zieht die Abhaengigkeiten mit:
libllvm15 (114 MB), Mesa samt libgl1-mesa-dri, libz3 und libflite1 -- ein
SPRACHSYNTHESIZER. Alles fuer Filter, die ein Dienst ohne Bildschirm nie
anfasst; --no-install-recommends half nicht, es sind harte Abhaengigkeiten.
ffprobe flog danach auch noch raus: 135 MB fuer eine einzige Aufgabe, die
Laenge zu nennen -- die steht auch im Kopf, den ffmpeg beim Oeffnen druckt.
Gegengeprueft an beiden Dateien, auf zwei Nachkommastellen identisch:
ffprobe 29.767967 | ffmpeg Duration: 00:00:29.77
ffprobe 645.642449 | ffmpeg Duration: 00:10:45.64
Der Preis dafuer ist ebenfalls zu benennen: die Fassung haengt jetzt an einer
Zeile im Dockerfile und nicht mehr an apt. Vertretbar, weil ffmpeg hier nur
eigene, bereits abgelegte Dateien anfasst und nicht am Netz haengt.
EIN FEHLER, DER ZWEIMAL DERSELBE WAR. "Keine Bildspur" ist bei Ton der
Normalfall -- die meisten MP3 tragen kein Titelbild. Der erste Anlauf erkannte
das am WORTLAUT der Fehlermeldung, und der ist zwischen den Fassungen anders:
ffmpeg 5.1 "Stream map '0:v:0' matches no streams."
ffmpeg 7.1 "Failed to set value '0:v:0' for option 'map'"
Ergebnis: eine gesunde MP3 wurde als "1 fehlgeschlagen" gezaehlt, und dann
sucht jemand einen Fehler, den es nicht gibt. Gelesen wird jetzt die
SPURENLISTE ("Stream #0:1: Video:"), die seit jeher so dasteht -- und die kommt
aus demselben Aufruf wie die Laenge, also ein Blick in die Datei statt drei.
Gemessen: fehler 0, ohneBild 1.
Beide Wege durchgeprueft. Video: 320x180, Durchschnittsfarbe RGB(97,140,155) --
echtes Bild und kein schwarzer Vorspann. Ton mit angehaengtem Titelbild:
Vorschau erzeugt, dauer 8.05, Kachel zeigt 0:08.
UND ZWEI ORDNER-LUECKEN, beide beim Benutzen gefunden.
"Wie lege ich neue Ordner an?" -- gar nicht, ausser man wusste es: der Knopf
sass NUR im Verschieben-Dialog. Wer einen leeren Ordner wollte, musste erst
Dateien auswaehlen und "Verschieben" druecken. Eine Funktion, die man nur ueber
eine andere Funktion erreicht, ist fuer den, der sie sucht, nicht vorhanden.
Jetzt steht sie unter dem Ordnerbaum, mit der Angabe, IN welchem Ordner sie
entsteht -- sonst tippt jemand "waffen", landet in "items/waffen" und sucht
danach an der Wurzel.
Zielordner beim Hochladen: vorher galt stillschweigend der in der Galerie
angeklickte. Richtig, solange man es weiss, und ein Fallstrick, sobald man es
nicht weiss -- wer nach einer Suche hochlaedt, steht in "Alle" und legt an der
Wurzel ab. Der angeklickte bleibt die Voreinstellung, ist jetzt aber sichtbar
und aenderbar, samt "neuer Ordner" direkt daneben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
7b35ef5698 |
feat: Betrachter fuer Lesbares, und aus dem <video> wird ein Player
Bild, Video, Ton und PDF konnte der Betrachter schon. Was fehlte, war
ausgerechnet die Art, die man am ehesten nur lesen und nicht herunterladen
will: eine .md landete im Zweig "laesst sich hier nicht anzeigen".
LESBARES. Markdown gesetzt, .lua und .json eingefaerbt, CSV als Tabelle, alles
Uebrige mit Zeilennummern. Der Tokenizer ist DERSELBE wie fuer die Schnipsel
auf der API-Seite und nicht ein zweiter -- zwei waeren zwei, die auseinander
laufen. Die Zeilennummern liegen in einer eigenen Spalte: so nimmt ein
Markieren mit der Maus sie nicht mit.
Die CSV-Zerlegung kann Anfuehrungszeichen und doppelte darin. Das ist der Teil,
den ein split(';') falsch macht, und CSV aus Excel hat ihn regelmaessig; das
Trennzeichen wird aus der Kopfzeile geraten.
NICHT ALLES WIRD GEHOLT. Die Groesse steht in der Datenbank, also wissen wir
vorher, worauf wir uns einlassen: ueber 512 KB kommt per Bereichsanfrage nur
der Anfang, und das steht auch da. Ein Betrachter, der bei einem 400-MB-
Protokoll den Tab abschiesst, ist schlimmer als einer, der die Datei gar nicht
erst oeffnet. Gemessen: Range: bytes=0-49 -> 206, 50 Bytes.
MARKDOWN OHNE BIBLIOTHEK, und das ist kein Geiz. Der uebliche Weg heisst marked
plus DOMPurify und endet bei dangerouslySetInnerHTML -- damit haengt die
Sicherheit dieser Seite an der Frage, ob die Filterliste vollstaendig ist. Hier
entsteht NIE eine HTML-Zeichenkette: der Text wird zu React-Knoten, und ein
<script> in einer hochgeladenen .md erscheint als die acht Zeichen, die es ist.
Im ganzen ui/ steht kein einziges dangerouslySetInnerHTML.
Die eine Luecke, die React nicht schliesst, ist href: [klick](javascript:...)
kaeme durch. Zwoelf Faelle in Node durchgeprueft, alle bestanden -- http,
https, mailto und relative Ziele durch; javascript:, JavaScript:, mit
Leerzeichen davor, data:, vbscript:, file: abgelehnt und als Text stehen
gelassen.
DER PLAYER. Die Bedienelemente bleiben die des Browsers: ein selbstgebauter
Schieber sieht in jedem Browser anders falsch aus, kennt keine
Tastatursteuerung und keine Untertitel. Drumherum kam, was der Browser nicht
mitbringt.
Lautstaerke ueber Dateien hinweg -- <video> setzt sie bei jedem neuen Element
auf 1 zurueck, und wer sich durch zwanzig Clips klickt, stellt sie sonst
zwanzigmal leise, waehrend der einundzwanzigste ungefragt wieder hochfaehrt.
Weiter zum naechsten Stueck, abschaltbar, und nur wenn das Naechste auch etwas
zum Abspielen ist: nach einem Lied ungefragt bei einem Bild zu landen waere
schlechter als stehenzubleiben.
Tastatur an EINER Stelle: Leertaste haelt an, Pfeile springen fuenf Sekunden,
Umschalt+Pfeil wechselt die Datei. Bei allem anderen blaettern die Pfeile wie
bisher. Dass das so ist, steht als Zeile unter dem Bild -- eine unsichtbare
Sonderregel fuehlt sich an wie ein Fehler.
SERVER. Die Typtabelle kennt jetzt md, markdown, csv, lua, xml, yml, yaml,
toml, ini, cfg, sql und log -- alle als text/plain. Nicht als text/markdown
(braechte nichts) und erst recht nicht als text/html: wer einen Upload-Token
hat, koennte damit eine Seite unter unserem Namen veroeffentlichen. html, css
und js stehen deshalb weiterhin NICHT drin.
Der Betrachter entscheidet trotzdem nach der Endung und erst danach nach der
gemeldeten Art. Grund steht in der Datenbank: items/readme.md lag schon da und
trug application/octet-stream, weil sie vor der erweiterten Tabelle hochgeladen
wurde. Wer nur auf die Art schaut, zeigt die alte README nicht an und eine neue
schon -- und sucht den Unterschied an der falschen Stelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
40c446f859 |
feat: Papierkorb, Abrufzaehler, WebP ohne Adressaenderung, ZIP hochladen
Vier Dinge, alle am laufenden Dienst gemessen. PAPIERKORB. Loeschen war endgueltig -- bei einem Knopf "alle 3861 waehlen" direkt neben "loeschen" ist das die falsche Haerte, und die Sicherung half nur bis zum letzten naechtlichen Lauf. Die Datei wandert jetzt nach /data/papierkorb, der Datensatz in eine eigene Tabelle, nach 30 Tagen raeumt der Dienst stuendlich selbst auf. Der Ablagename traegt eine laufende Nummer: unter demselben Pfad koennen nacheinander verschiedene Dateien gelegen haben, und beide sollen zurueckholbar sein. Geprueft: loeschen -> oeffentlich 404, Eintrag im Papierkorb, zurueckholen -> 200 samt neu gerechneter Vorschau. ABRUFZAEHLER. media.abrufe und zuletzt_abgerufen. Gezaehlt wird im Speicher und alle 30 Sekunden weggeschrieben -- ein UPDATE je Kachel waeren bei einer Galerieseite 120 Schreibvorgaenge. Der 304 zaehlt mit (der Aufrufer WOLLTE die Datei, er hatte sie nur schon), HEAD nicht. Was vor dem Einbau lag, steht als "nie geholt" da, auch wenn es taeglich benutzt wurde -- die Karte im Speicherbericht sagt das auch dazu, statt eine Zahl zu zeigen, die luegt. WEBP OHNE ADRESSAENDERUNG. Der Pfad ist die Adresse: aus items/foo.png darf nicht items/foo.webp werden. Die sparsame Fassung liegt deshalb DANEBEN und wird unter DERSELBEN Adresse ausgeliefert, wenn der Aufrufer per Accept sagt, dass er WebP versteht. Nachgemessen an einer Datei: 6448 Bytes fuer curl, 2758 fuer einen Browser. Ueber den ganzen Bestand: 3606 Fassungen, 199,3 MB -> 33,1 MB, gespart 166,2 MB. Bei 29 lohnt WebP nicht -- dort bleibt es beim Original. Zwei Dinge haengen daran, beide gegengeprueft: "Vary: Accept" (ohne die Zeile legt ein Zwischenspeicher die WebP-Fassung fuer alle ab, auch fuer die, die sie nicht lesen koennen) und getrennte ETags mit "-w" (sonst bekaeme jemand auf seinen If-None-Match hin ein 304 fuer das falsche Bild). Gemessen: derselbe ETag mit Accept -> 304, ohne Accept -> 200 mit dem PNG. ZIP HOCHLADEN. Ein Archiv wird ausgepackt statt abgelegt, die Ordner darin bleiben erhalten und haengen sich hinter den gewaehlten Zielordner. fflate und nicht adm-zip: reines JavaScript, keine native Bibliothek -- dieses Projekt hat schon einen halben Abend an einer ABI-Nummer verloren. Grenzen: 5000 Eintraege, 256 MB entpackt, geprueft VOR dem Entpacken (eine Zip-Bombe waere sonst schon im Speicher). Zip-Slip gemessen: "../../../../etc/passwd" und "..\..\windows\hosts" abgelehnt, die harmlose Datei im selben Archiv abgelegt. UND EIN KNOPF, DER GELOGEN HAT. Der erste Nachruestlauf ueber alle Dateien brauchte mehr als 90 Sekunden -- genau da gibt Nginx Proxy Manager auf. Der Aufrufer sah einen 504, waehrend die Arbeit im Hintergrund weiterlief und fertig wurde. Ein Knopf, der Erfolg als Fehler meldet, ist schlimmer als einer ohne Rueckmeldung. Beide Wartungsknoepfe laufen deshalb in Runden zu 300 Stueck: der Server meldet "offen" und "fertig", die Oberflaeche ruft erneut auf und zeigt dabei "280 erzeugt, noch 129 offen ...". Gemessen: 7,8 s je Runde statt 90+ am Stueck. Der Vorschau-Knopf hatte dieselbe Wand und wurde mitgezogen, obwohl er noch nicht dagegengelaufen war. Nebenbei: der Verwaisten-Sucher kennt jetzt auch /data/webp (gleiche Schleife, Ordner im Ergebnis vorangestellt), Verschieben und Loeschen nehmen die sparsame Fassung mit, und das Loeschen eines Ordners raeumt thumbs/ und webp/ hinterher. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
51071fc292 |
docs: README und .env.example auf den Stand -- und Embeds im Stil des Bots
DIE README BESCHRIEB NOCH DEN VORGAENGER: "Fivemanage Lite mit MySQL und MinIO", Ausprobieren ueber docker-compose.local.yml, vier Geheimnisse, davon drei fuer Dienste, die es nicht mehr gibt. Jetzt beschreibt sie, was da ist: ein Container, SQLite, Dateien auf einer Platte -- Starten, was der Dienst kann, die eine Regel und wie sie im Code durchgesetzt wird, Betrieb (Sicherung, Wiki, Zurueckspielen), was bewusst fehlt, und die zwei Dinge, die Zeit gekostet haben. Und die .env.example nannte DB_PASSWORD, MINIO_ROOT_PASSWORD und API_TOKEN_HMAC_SECRET -- drei Werte fuer Dienste, die seit Wochen weg sind, und KEINEN der acht, die das Compose wirklich liest. Nachgezaehlt: jetzt sind alle acht genannt, zwei als Pflicht, sechs als Optional mit dem Grund dahinter. EMBEDS IM STIL DES BOTS. Auf den Hinweis hin in d4rkbot/src/embeds.js nachgesehen: dort gibt es eine zentrale Embed-Fabrik mit Markenfarbe (#f5c518), Fusszeile "D4RKST3R // <TAG>" und Zeitstempel. Zwei Dienste desselben Hauses sollen in einem Kanal nicht wie zwei Fremde aussehen -- also uebernommen statt neu erfunden, samt Feldern statt Fliesstext und Discords Zeitmarken (<t:...:R>), die "vor 3 Stunden" in der Zeitzone des Lesers anzeigen. EINE ABWEICHUNG, mit Absicht: der Bot faerbt alles in der Markenfarbe, hier faerbt die SCHWERE. Eine Warnung, die aussieht wie jede andere Nachricht, ist eine Warnung, die man ueberliest -- und diese Meldungen gibt es nur, weil jemand sie sehen soll. Die Testnachricht schickt jetzt echte Zahlen mit. So sieht man nicht nur, DASS etwas ankommt, sondern auch, ob es lesbar ist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c9cd8eaa84 |
feat: Token-Grenzen, Speicher nach Art, Aufbewahrungsbericht, Discord-Meldungen
KEINE TOKEN-ARTEN, SONDERN TOKEN-GRENZEN. Fivemanage hat Token-Typen; wir haben schon ein Rechtesystem, es steht nur auf einer anderen Achse (Praefix und Loeschrecht). Was wirklich fehlte, sind die drei Schrauben, die die Frage beantworten "was passiert, wenn DIESER Token abhandenkommt": Groessengrenze ein Token fuer Fahrzeugbilder braucht keine 64 MB erlaubte Arten nur Bilder, kein Video Ablaufdatum fuer eine Anbindung, die man mal ausprobiert Alle drei am laufenden Dienst durchgemessen: 600 KB gegen 512 KB -> 413 mit beiden Zahlen; ein mp4 gegen einen Bild-Token -> 403 mit Art UND MIME im Text; ausserhalb des Praefix -> 403; abgelaufen -> 401 mit DEM DATUM. "Abgelaufen" und "unbekannt" auseinanderzuhalten spart eine halbe Stunde Suche nach einem Tippfehler, den es nicht gibt. Mehr als die Dienstgrenze zu erlauben wird abgelehnt statt still gekappt -- es waere eine Behauptung, denn durchgelassen wird ohnehin die schaerfere. SPEICHER NACH ART: ein Balken fuer die Verhaeltnisse, eine Tabelle fuer die Zahlen, dazu die groessten Dateien. AUFBEWAHRUNG VORBEREITET, NICHT GEBAUT. Automatisches Loeschen zu bauen, bevor man weiss, was da liegt, ist der Weg, wie man Daten verliert. Stattdessen der Bericht davor: wie viel ist aelter als 7/30/90/365 Tage, was liegt doppelt (gleicher SHA-256 unter zwei Pfaden) und wie viel Platz das kostet. "Alles aelter als 90 Tage loeschen" ist eine Behauptung, solange niemand weiss, wie viel das waere. Diese Seite loescht nichts. DISCORD-MELDUNGEN -- und der Grund ist nicht Discord, sondern eine Luecke, die wir heute selbst gebaut haben: die Sicherung laeuft nachts um halb fuenf, und wenn sie aufhoert zu laufen, merkt es niemand. Der Dienst sieht jetzt STUENDLICH nach und meldet, wenn der letzte Lauf gescheitert oder aelter als 26 Stunden ist (24 waeren zu knapp, 48 zu spaet). Hoechstens EINMAL je Zustand -- eine Meldung, die stuendlich wiederkommt, wird weggeklickt, und dann auch die, die zaehlt. Ein Webhook und keine Bot-Anbindung: eine Adresse, kein Token, keine Berechtigungen. Die Adresse wird geprueft (nur echte discord.com-Webhooks), und der Testknopf steht gleich daneben -- ein Webhook, den man eintraegt und erst in drei Wochen im Fehlerfall ausprobiert, ist einer, der dann nicht geht. Aus deren Doku mitgenommen und NICHT gebaut: presigned URLs (kurzlebige signierte Adressen fuer Client-Uploads). Waere der richtige Weg fuer Spieler-Screenshots -- solange keine Spieler hochladen, ist es Vorrat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
67d2f4ec93 |
feat: verschieben, Ordner, Adressvorlagen, API-Seite mit Export
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>
|
||
|
|
2efc04b0b2 |
feat: nach Medienart filtern -- und die Endung schlaegt das Achselzucken
FILTER. Eine Leiste ueber der Galerie: Alles, Bilder, Video, Ton, Dokumente, Anderes -- mit der Anzahl AM KNOPF. Ein Knopf "Video", hinter dem nichts liegt, kostet sonst einen Klick, um das herauszufinden; leere Arten sind deshalb sichtbar, aber gesperrt. Die Uebersetzung Art -> SQL steht an EINER Stelle (ARTEN), weil zwei Stellen zwangslaeufig auseinanderlaufen: die Liste zeigte dann etwas anderes, als der Zaehler daneben behauptet. "Anderes" ist bewusst die VERNEINUNG der uebrigen und keine eigene Liste -- sonst faellt ein neuer Typ durch alle Filter und ist nirgends zu finden. Nachgemessen: die Summe der fuenf Arten ergibt die Gesamtzahl. /media/ids nimmt denselben Filter. Ohne das waehlt "alle waehlen" mehr aus, als zu sehen ist -- und geloescht wuerde Unsichtbares. DER FUND BEIM MESSEN: clip.mp4, ton.mp3, handbuch.pdf und notiz.txt landeten ALLE unter "Anderes". Ein Formularteil ohne erkannte Art traegt "application/octet-stream", und mein Code hat das der Endungstabelle vorgezogen. Was der Absender ueber die Art sagt, gilt weiterhin -- aber nicht sein Achselzucken. Danach: mp4 -> video/mp4, mp3 -> audio/mpeg, pdf und txt -> Dokumente, und .zip bleibt richtigerweise "Anderes". POLITUR. Ein ruhiger Verlauf im Hintergrund statt einer flachen Flaeche (ein einfarbiges Dunkel sieht auf einem grossen Monitor aus wie ein Ladefehler), Bildlaufleisten im Ton der Oberflaeche, ein Balken unter der aktiven Seite, Karten mit einem Hauch Tiefe, ein spuerbarer Druckpunkt an den Knoepfen, und Kacheln, die sich beim Darueberfahren leicht heben. Der Seitenwechsel blendet auf -- und wer prefers-reduced-motion gesetzt hat, bekommt nichts davon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
84cb3ee200 |
feat: Galerie fuer 900 Dateien, und ein Finder fuer Verwaiste
SORTIERUNG. Nach Pfad, Groesse, Anlage- oder Aenderungsdatum, auf- und absteigend. Bisher gab es genau eine Reihenfolge (zuletzt geaendert), was bei 228 Dateien schon nicht mehr reicht. Die Sortierung kommt vom Aufrufer und geht in SQL, also wird sie NIEMALS durchgereicht, sondern gegen eine feste Liste getauscht. Nachgemessen mit "sort=; DROP TABLE media--": faellt auf den Standard zurueck, Datenbank unveraendert. Nach dem Pfad wird ohne Ruecksicht auf Gross- und Kleinschreibung sortiert, sonst stuende "Adder" vor "adder" und dazwischen nichts. ALLE TREFFER WAEHLEN. GET /media/ids liefert nur die IDs zur aktuellen Auswahl -- ohne das braucht das Leeren eines Ordners mit 900 Bildern fuenfzehn Seiten Klicken. Gedeckelt auf 5000, und wer daran stoesst, bekommt es GESAGT (gekappt: true) statt still eine halbe Auswahl. VERWAISTE. Ein Knopf zaehlt drei Arten von Abweichung: Dateien ohne Datensatz, Datensaetze ohne Datei, Vorschauen ohne Bild. ERST ZAEHLEN, DANN FRAGEN -- die Suche veraendert nichts. Und das Aufraeumen geht in ZWEI Richtungen, wobei die schonende die Voreinstellung ist: "aufnehmen" gibt Dateien ohne Datensatz einen, sie sind ja da und werden ausgeliefert -- sie fehlen nur in der Liste. Erst "aufraeumen" entfernt etwas, und auch dann nur Verweise auf Dateien, die es nicht mehr gibt, und abgeleitete Vorschauen. Beides landet im Verlauf. Am laufenden System durchgespielt: Datei am Dienst vorbei ins Volume gelegt -> gefunden; aufgenommen -> steht in der Galerie; Datei hinter dem Ruecken geloescht -> Datensatz ohne Datei gefunden; aufgeraeumt -> weg. Verlauf hat beide Schritte mit user:admin. (Der erste Versuch dieses Tests hat gelogen: Git Bash schreibt einen absoluten Pfad in `docker exec` zu einem Windows-Pfad um -- rm suchte nach "C:/Program Files/Git/data/...". `rm -f` schweigt dabei. Mit `sh -c` drumherum bleibt der Pfad heil.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4f149e365f |
feat: Discord-Bild und -Name im Dashboard
Wer ueber Discord hereinkommt, sieht sein Bild in der Kopfzeile, auf der
Kontoseite und neben seinen Eintraegen im Verlauf.
DIE ADRESSE BAUT DER DIENST, nicht die Oberflaeche. Gespeichert wird nur der
Bildstempel; das URL-Schema von Discord steht an EINER Stelle (avatarUrl in
db.ts) und nicht in jeder Ansicht, die ein Bild zeigt. Es hat drei Faelle, und
alle drei stehen dort:
- Stempel faengt mit "a_" an -> bewegtes Bild, muss als .gif angefragt werden
- Stempel vorhanden -> .png mit gewuenschter Groesse
- kein Stempel -> Discords Ersatzbild, dessen Nummer sich aus
der ID ergibt: (id >> 22) % 6. Das ist die
NEUE Rechnung; die alte ueber den
Diskriminator gilt seit den Pseudonymen
nicht mehr. Gegengerechnet an drei IDs.
Bild und Anzeigename werden BEI JEDER ANMELDUNG nachgezogen: wer sein Bild
wechselt, saehe sonst monatelang das alte und fragte sich, ob er im richtigen
Konto sitzt. Der Benutzername im Dashboard bleibt dagegen, wie er ist -- an ihm
haengen die Eintraege im Verlauf.
Der Avatar-Baustein kennt drei Faelle und keinen kaputten: Bild, sonst die
Initialen auf einer Farbe, die sich aus dem Namen ergibt (damit sie beim
Neuladen nicht springt), und fuer einen Token einen Schluessel -- ein Token hat
kein Gesicht, ein Platzhalterbild daneben waere eine Behauptung. Laedt das Bild
nicht (CDN nicht erreichbar, Stempel veraltet), faellt es auf die Initialen
zurueck statt ein kaputtes Bildsymbol zu zeigen.
Nachgemessen: cdn.discordapp.com liefert das Ersatzbild (200, image/png), die
Form der Bildadresse stimmt, und ein Passwort-Konto bekommt sauber avatar:null.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
23405d68d3 |
feat: mehrere Rollen duerfen herein, nicht nur eine
Wer EINE der eingetragenen Rollen hat, kommt ins Dashboard. Mehrere sind der Normalfall, sobald mehr als eine Person Zugang haben soll -- Admin und Fotograf sind selten dieselbe Rolle. Die bisherige einzelne Rolle geht NICHT verloren: der alte Schluessel (discord.role_id) wird weitergelesen, bis die Liste einmal gepflegt wird; dann raeumt das Speichern ihn weg. Zwei Quellen fuer dieselbe Frage laufen sonst auseinander. Nachgemessen: die vorhandene Rolle stand nach dem Umbau unveraendert drin, bereit blieb true. Getrennt wird an Komma, Semikolon, Leerzeichen und Zeilenumbruch -- wer drei IDs untereinander aus Discord kopiert, soll sie nicht erst zu einer Zeile zusammenschieben. Doppelte fallen zusammen. UND DIE SCHLECHTE WIRD BEIM NAMEN GENANNT: "123 ist keine Rollen-ID (17 bis 20 Ziffern)" statt "Rollen-ID ungueltig". Bei fuenf Eintraegen ist die zweite Meldung eine Suchaufgabe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1c5bf20eb0 |
feat: Medien im Fenster ansehen, und eine Auffrischung, die nichts wegzieht
ANSEHEN OHNE NEUEN TAB. Klick auf eine Kachel oeffnet den Betrachter: Bilder, Videos, Ton und PDF direkt, alles andere mit ehrlichem Hinweis und Herunterladen-Knopf -- ein Betrachter, der bei einer .zip ein schwarzes Feld zeigt, sieht aus wie ein kaputter Betrachter. Pfeiltasten blaettern, Escape schliesst; Adresse kopieren, neuer Tab und Loeschen sitzen in der Kopfzeile. Gezeigt wird IMMER die Vollfassung, nie die Vorschau: 320 Pixel reichen fuer eine Kachel, aber nicht dafuer, ein Bild zu beurteilen -- und genau dazu klickt man darauf. Damit musste die Auswahl umziehen: der Klick auf die Kachel oeffnet jetzt, das Kaestchen oben links waehlt aus. Es erscheint beim Darueberfahren und bleibt sichtbar, solange etwas gewaehlt ist. AUTOMATISCH AUFFRISCHEN, aber nicht stumpf. Ein billiger Fuehler (/api/dash/stand: drei Zahlen, GEMESSEN 6 ms je Abfrage ueber TLS und Proxy) wird alle fuenf Sekunden gefragt; erst wenn er sich aendert, wird richtig nachgeladen. Ein Tab im Hintergrund fragt gar nicht. Die dritte Zahl ist kein Zierrat: nach einem LOESCHEN gehen Anzahl und neuester Zeitstempel auf die alten Werte zurueck -- nur die Ereignis-ID zaehlt weiter. Ohne sie bliebe eine geloeschte Datei in der Galerie stehen. Nachgemessen. UND ES ZIEHT NICHTS WEG: wer etwas ausgewaehlt hat, ein Bild offen hat oder geblaettert ist, bekommt keinen Neuaufbau unter den Haenden, sondern einen Knopf "Neues da -- laden". Eine Liste, die sich beim Klicken selbst neu sortiert, ist der Grund, warum Leute Auto-Refresh abschalten. Der Schalter merkt sich seinen Zustand. Kein SSE, und das mit Absicht: dafuer braeuchte NPM zusaetzlich `proxy_buffering off` -- ein weiterer Handgriff im Proxy, der vergessen werden kann und dann zu einer Verbindung fuehrt, die scheinbar haengt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dffec7714c |
feat: Discord-Anmeldung -- im Panel einrichtbar, nicht in der .env
Auf Wunsch ueber die Oberflaeche statt ueber Umgebungsvariablen. Der Gewinn ist
nicht nur Bequemlichkeit: das Panel legt die REDIRECT-URI zum Kopieren hin, und
genau daran scheitert ein OAuth-Anlauf fast immer.
settings.ts Schluessel/Wert in SQLite, mit Umgebung als Rueckfall. Wer
schon Stack-Variablen gesetzt hat, behaelt sie; das Panel
uebersteuert. In der Umgebung bleibt, was den Dienst ueberhaupt
erreichbar macht (PUBLIC_URL, PORT, DATA_DIR) -- wer das per
Formular verstellen koennte, sperrt sich mit einem Klick aus.
discord.ts Der Weg in drei Schritten, ohne Bibliothek und OHNE BOT-TOKEN:
die Rolle wird mit dem Merkmal des BENUTZERS geholt
(guilds.members.read). Kein Bot, keine Bot-Rechte, kein zweites
Geheimnis, das ablaufen kann.
DAS GEHEIMNIS GEHT NIE HERAUS -- auch nicht an eine angemeldete Sitzung. Das
Panel bekommt nur "hatGeheimnis: true". Ein Formular, das den Client-Schluessel
im Klartext zurueckschickt, streut ihn in jeden Browser-Cache. Ein leeres Feld
heisst "unveraendert", nicht "loeschen"; zum Entfernen gibt es "-".
JEDER ABBRUCH IST SICHTBAR: kein weisser Bildschirm, sondern zurueck aufs
Formular mit dem Grund im Adressfeld -- "Du bist nicht auf diesem Server",
"Dir fehlt die noetige Rolle", oder bei 4xx vom Tausch der Hinweis auf die
Redirect-URI. Die IDs werden schon beim Speichern geprueft (17-20 Ziffern),
damit ein halber Link nicht erst beim Anmelden auffaellt.
Das Passwort bleibt als Notausgang. Ein ueber Discord angelegtes Konto bekommt
KEIN brauchbares Passwort ("nur-ueber-discord"): verifyPassword scheitert an
allem, was nicht dem scrypt-Format entspricht -- nachgesehen, nicht vermutet.
Dazu der Verlauf, wie gewuenscht: Suche nach Pfad (mit derselben
LIKE-Maskierung wie in der Galerie -- Suche nach "_" findet 0 statt allem),
waehlbare Seitengroesse (25/50/100/200, Standard 50) und mehr Luft zwischen
"Groesse" und "Von wem".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
0b295a2302 |
feat: Verlauf -- wer hat wann was abgelegt, ersetzt oder geloescht
Neuer Reiter, und dahinter eine eigene Tabelle. WARUM NICHT EINFACH
media.created_at -- drei Gruende, jeder allein wuerde reichen:
1. media kennt nur den JETZIGEN Stand. Was geloescht wurde, ist weg, samt
der Frage, wer es geloescht hat.
2. Ein Ueberschreiben hebt updated_at an und ueberschreibt damit die
Auskunft, wann die Datei urspruenglich kam.
3. media.token_id ist beim Upload aus dem Dashboard NULL -- dort hat niemand
einen Token, sondern eine Sitzung. Das "von wem" stand also nirgends.
Das ist NICHT das Logging, das die ROADMAP ablehnt: dort geht es um
Anfrage-Protokolle samt ClickHouse. Hier sind es ein paar Zeilen je Upload in
derselben SQLite-Datei, und nach einem halben Jahr raeumt pruneEvents auf.
DER VERLAUF STARTET NICHT LEER. Beim ersten Start wird er aus dem Bestand
nachgetragen (68 Eintraege) -- ein Tab, der am ersten Tag leer ist, obwohl 68
Bilder in der Ablage liegen, sieht aus wie ein kaputter Tab. Die nachgetragenen
Eintraege sind als solche gekennzeichnet: Zeitpunkt stimmt, Urheber steht als
"vor der Aufzeichnung", weil ihn niemand mehr kennt. Die Oberflaeche sagt das
unter der Tabelle, statt es zu verschweigen.
logEvent wirft nie: ein Verlauf, der einen Upload scheitern laesst, waere die
Buchhaltung, die das Geschaeft verhindert.
Nachgemessen: Upload, Ersetzen und Loeschen aus dem Dashboard erscheinen als
user:admin, die Uploads des Fotostudios als token:d4rk_photostudio; beim
Loeschen wird die Groesse VOR dem Loeschen geholt, sonst stuende dort nichts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
3ddab937e7 |
feat: hochladen aus dem Dashboard -- Bilder, Videos, Ton, PDF
POST /api/dash/upload, eigener Weg neben /api/upload. Der dortige haengt an
einem Bearer-Token; wer im Dashboard sitzt, hat eine SITZUNG und keinen Token,
und einen Token nur zum Hochladen anzulegen waere ein Geheimnis mehr, das
jemand verlieren kann.
Mehrere Dateien auf einmal, und JEDE bekommt ihr eigenes Ergebnis mit Pfad,
Groesse und Grund im Fehlerfall. Ein "3 von 5 hochgeladen" ohne die Namen der
zwei anderen ist genau die Sorte Rueckmeldung, wegen der wir hier neu bauen.
Eine kaputte Datei bricht die anderen nicht ab.
parseBody({ all: true }) ist noetig: ohne das liefert Hono bei mehreren Feldern
gleichen Namens nur das LETZTE, und aus fuenf Dateien wuerde eine.
DATEINAMEN WERDEN ZURECHTGEBOGEN, und zwar sichtbar. checkPath laesst
"Mein Video (final) 2.MP4" mit Recht nicht durch, also macht safeFilename
daraus "mein-video-final-2.mp4" -- Umlaute werden umschrieben und nicht
weggeworfen ("Grüße" -> "gruesse", nicht "gr-e"). Die Ergebnisliste zeigt den
neuen Pfad neben dem alten Namen.
BEIM MESSEN GEFUNDEN: bleibt vom Namen nichts uebrig -- "ЖУРНАЛ.txt",
" .png", "★★★.txt" --, hiessen bisher ALLE "datei.txt", und die zweite
Datei ueberschrieb die erste stillschweigend. Jetzt haengen die ersten acht
Stellen des SHA-256 daran: gleicher Inhalt ergibt denselben Namen,
verschiedener Inhalt verschiedene. Nachgemessen mit drei solchen Namen.
Der Fortschrittsbalken laeuft ueber XMLHttpRequest und nicht fetch: nur der
kennt upload.onprogress, und bei einem 40-MB-Video ist ein Balken der
Unterschied zwischen "es tut sich was" und "ist es abgestuerzt?".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f7086c231c |
feat: Ordneruebersicht in der Galerie -- und der Proxy haelt keine Bilder mehr fest
ORDNERUEBERSICHT. Eine Spalte neben der Galerie mit allen Ordnern, auf allen Ebenen, mit Anzahl und Groesse; ein Klick filtert. Neu dafuer: - GET /api/dash/folders -- die Ordner werden in JavaScript zusammengezaehlt und nicht in SQL: fuer eine Baumansicht braucht es JEDE Ebene, und das waere in SQLite eine rekursive Abfrage, die niemand mehr liest. - GET /api/dash/media?folder=... als EIGENER Filter neben der Suche. Die Suche findet mit LIKE %...% auch "alt/vehicles/adder.webp"; ein Ordner, der Dinge zeigt, die nicht darin liegen, ist keiner. Hier ist der Anfang verankert. - Die LIKE-Sonderzeichen werden maskiert: ein Ordner "fotos_alt" wuerde sonst auch "fotosXalt" treffen, weil _ in LIKE ein beliebiges Zeichen ist. Die Zahlen sind kumulativ -- "vehicles" enthaelt, was in "vehicles/showroom" liegt. Alles andere ueberrascht in einer Baumansicht. Nachgemessen ueber drei Ebenen: vehicles (24) > showroom (2) > tief (1), Filter und Zaehlung stimmen, und nach dem Aufraeumen steht wieder vehicles (22). Flache Liste mit Einrueckung statt aufklappbarem Baum: bei einer Handvoll Ordner ist ein Aufklappmechanismus mehr Bedienung als Nutzen -- und was man nicht aufklappen muss, kann man auch nicht zugeklappt uebersehen. Dazu: der NPM-Zwischenspeicher ist zu. Der Schnipsel steht im Feld "Custom Nginx Configuration"; NPM setzt ihn innerhalb des server-Blocks ein, eine location ist dort also gueltig. Nachgemessen: cache-control kommt wieder von uns, und ein ueberschriebenes Bild ist sofort das neue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
5f7f17ce7e |
feat: der Dienst steht oeffentlich, und die Pfadmeldung sagt jetzt die Regel
Der Stack laeuft unter https://fivemanage.d4rkst3r.de, hinter NPM mit Let's-Encrypt-Zertifikat, Container auf Port 9101. DER NAME IST EINE UMKEHR, und sie steht ausfuehrlich im ROADMAP: hier stand, dass fivecdn und fivemanage wegfallen, weil wir kein Fivemanage betreiben. Das Argument gilt weiter -- es ist nur teurer als das, was dagegen steht. Der Name war bereits fertig verkabelt: DNS da, NPM-Host da, Zertifikat gueltig bis 09.11.2026, Ziel host.docker.internal:9101, und dieser Port wurde frei, als die Lite-Container gingen. Unser Stack auf 9101 kostet damit null Klicks. Der Wechsel auf media.d4rkst3r.de bleibt billig, weil keine Adresse in der Datenbank steht. Ueber die oeffentliche Adresse durchgemessen: Anmeldung durch echtes HTTPS (der Fall, den curl bisher nicht bezeugen konnte), Token mit Praefix-Fessel, 403 mit Text fuer anderswo/ und fuers Loeschen, Upload und Abruf Byte fuer Byte identisch. Und ein Befund aus dem Fotostudio-Anschluss: die Pfadmeldung war irrefuehrend. SEGMENT verlangt als ERSTES Zeichen einen Buchstaben oder eine Ziffer, die Meldung sprach aber nur von "nicht mit Punkt beginnend" -- ein fuehrender Unterstrich faellt ebenso durch. Kein erfundener Fall: das Fotostudio legt seine Leerbilder als "_plate" ab. Jetzt nennt die Meldung die Regel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5edbe6c45f |
feat: ein Name statt zwei, und PUBLIC_URL ist nur noch der Ursprung
fivecdn.d4rkst3r.de und fivemanage.d4rkst3r.de fallen weg -- wir betreiben kein Fivemanage, also tragen unsere Adressen auch nicht dessen Namen. Dashboard und API liegen an der Wurzel, die Dateien unter /f/. Der Ein-Namen-Betrieb war bis jetzt nicht ausdrueckbar. Der Kommentar in config.ts versprach "leer lassen, wenn alles unter einem Namen laufen soll", aber optional() faellt bei leerem Wert auf den Host aus PUBLIC_URL zurueck -- und dann gilt JEDE Anfrage als Anfrage an den Dateiwirt: kein Dashboard, keine API, /health gibt 404 und die Anmeldung 405 "hier gibt es nur Dateien". Nichts ist kaputt, und niemand kommt darauf. Deshalb ist der Ein-Namen-Betrieb jetzt der Standard und der zweite Name die Ansage. PUBLIC_URL ist nur noch der Ursprung; das /f haengt der Dienst selbst an. Wer es mitschriebe, bekaeme Adressen mit /f/f/, und das faellt erst auf, wenn das erste Bild fehlt. Praefix und Route haengen jetzt an derselben Stelle (config.filePrefix), damit die zurueckgegebene Adresse und die Route, die sie ausliefert, nicht auseinanderlaufen koennen. Beide Betriebsarten durchgemessen: mit einem Namen kommt .../f/vehicles/adder.png zurueck und liefert die Datei; mit gesetztem FILES_HOST kommt sie ohne Praefix, liegt unter dem Dateihost, ist am Dashboard-Namen zusaetzlich unter /f/ erreichbar, und unter dem Dateihost gibt es weiterhin keine API. Welche Betriebsart laeuft, sagt der Dienst in der zweiten Startzeile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
cfd0f0747a |
fix: sechs Befunde aus dem ersten Start des Servers
Das Grundgeruest war typgeprueft, aber nie gelaufen. Beim ersten Start
haben sich sechs Dinge gezeigt, alle nachgemessen statt vermutet:
1. Die Token-Pruefung hing an '*' und galt damit auch fuer /api/dash/*,
das daneben liegt. Das Dashboard bekam "Token fehlt oder ist
unbekannt" auf die Anmeldung, obwohl es nie einen Token haben kann.
Sie haengt jetzt an den drei eigenen Pfaden.
2. DELETE /api/media/... und GET /api/exists/... sahen am Ziel vorbei.
c.req.path traegt den Einhaengepunkt mit, das replace(/^\/media\//)
schnitt ihn nicht weg -- aus vehicles/adder.png wurde
api/media/vehicles/adder.png. Loeschen fand nie etwas, exists meldete
immer false. Jetzt :pfad{.+}; Hono liefert den Parameter fertig
dekodiert (an einer Probe gemessen), ein zweites decodeURIComponent
waere eine Dekodierung zu viel.
3. Verzeichnisdurchstieg in der SPA-Rueckfallroute: GET /..%5Cpackage.json
hat unter Windows die Datei ausgeliefert. Hono reicht %5C durch,
path.join behandelt den Backslash dort als Trenner, und eine
Eindaemmung gab es nicht. Unter Linux traegt genau dieser Angriff
nicht -- Glueck, keine Abwehr. Jetzt dieselbe resolve-Pruefung wie in
storage.ts.
4. Die Auskunft "Oberflaeche ist nicht gebaut" war unerreichbar.
createReadStream meldet eine fehlende Datei asynchron, das try/catch
darum fing nichts. Ergebnis war ein leerer 200 samt ENOENT im Log.
5. CSS und JS kamen als application/octet-stream -- die MIME-Tabelle
kennt nur Medientypen, das Dashboard haette weder Stylesheet noch
Modul geladen. Die Oberflaeche bekommt eine eigene Tabelle: in der
geteilten fehlt html mit Absicht, sonst koennte jeder mit einem
Upload-Token eine Seite unter fivecdn.d4rkst3r.de veroeffentlichen.
6. Kaputtes JSON endete als nackter "Internal Server Error".
Ausgerechnet das -- ein 500 ohne ein Wort dazu ist der Fehler, wegen
dem wir hier neu bauen. Jetzt 400 mit Text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e36db8c015 |
feat: eigener Medien-Dienst statt Fivemanage Lite — Server-Grundgeruest
Ein Container statt vier: Hono, SQLite, Dateien auf einer Platte. Kein
PostgreSQL, kein MinIO, kein ClickHouse. Das ist nicht die kleine Loesung,
sondern die, die man nicht pflegt — eine Sicherung ist ein cp -a.
Der Ausloeser war nicht der Funktionsumfang von Lite, sondern dessen
Zustand. web/src/utils/http-util.ts wirft jeden Fehler als new Error(...),
die Hooks pruefen auf "instanceof ApiError" — damit wird jeder Fehlschlag
lautlos verschluckt. "Create organization" meldet weder Erfolg noch
Misserfolg; das Ergebnis waren 19 gleichnamige Organisationen, die sich auch
nicht loeschen lassen, weil die Route fehlt. Von allem, was Lite darueber
hinaus kann, haben wir nichts gebraucht.
Zwei Hostnamen, ein Prozess: unter FILES_HOST gibt es ausschliesslich
Dateien — kein Dashboard, keine API, nichts anzumelden. Das haelt die
oeffentliche Adresse frei von Angriffsflaeche und die URLs huebsch, ohne
/f/-Praefix. Die Vorlage im Handy bleibt {model}.webp.
Der Pfad ist der Schluessel: X-Path legt die Datei genau dort ab, wo der
Aufrufer sie erwartet. Bei Lite haetten wir die zurueckgegebenen Adressen in
einer urls.json mitschleppen muessen — vorhersagbare URLs sind fuer das
Fotostudio die ganze Voraussetzung.
storage.ts bereinigt Pfade NICHT, es lehnt sie ab. Ein zurechtgebogener Pfad
legt die Datei unter einem anderen Namen ab als dem erwarteten, und das faellt
erst auf, wenn das Bild fehlt.
Typprueft. Noch nicht gestartet, noch nicht getestet — das ist der naechste
Schritt und steht in ROADMAP.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|