From 06e924daf52f161c3dc68bbdf3dd3240ee01fd25 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 11 Aug 2026 23:11:38 +0200 Subject: [PATCH] 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 --- README.md | 14 +++++++++---- server/src/routes/dash.ts | 33 +++++++++++++++++++++++++++--- server/ui/src/api.ts | 6 ++++++ server/ui/src/pages/Papierkorb.tsx | 29 +++++++++++++++++++++++--- tools/sichern.ps1 | 27 +++++++++++++++++++----- 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index cab2d3a..e2debe1 100644 --- a/README.md +++ b/README.md @@ -178,10 +178,16 @@ Wiki die Kopie. Wer dort tippt, verliert es beim nächsten Lauf. tar xzf d4rk_media-JJJJ-MM-TT-HHMM.tar.gz ``` -Ergibt `media.db` und `files/`. Beides ins Volume legen, während der Container -steht. Vorschaubilder und WebP-Fassungen sind **nicht** im Archiv — beides ist -abgeleitet und wird mit zwei Knöpfen unter *Speicher → Wartung* neu gerechnet. -Der Papierkorb ist ebenfalls nicht drin: was dort liegt, ist gelöscht. +Ergibt `media.db`, `files/` und `papierkorb/`. Alles drei ins Volume legen, +während der Container steht. Vorschaubilder und WebP-Fassungen sind **nicht** im +Archiv — beides ist abgeleitet und wird mit zwei Knöpfen unter +*Speicher → Wartung* neu gerechnet. + +> Der Papierkorb war anfangs **nicht** dabei, und das war ein Fehler: seine +> Einträge stehen in `media.db` und waren damit gesichert, die Dateien lagen +> daneben und waren es nicht. Nach einem Zurückspielen hätte der Papierkorb +> Zeilen gezeigt, deren „zurückholen" ins Leere greift. Er kostet fast nichts — +> was darin liegt, ist höchstens 30 Tage alt. --- diff --git a/server/src/routes/dash.ts b/server/src/routes/dash.ts index db72ff6..e6caad1 100644 --- a/server/src/routes/dash.ts +++ b/server/src/routes/dash.ts @@ -1746,14 +1746,41 @@ export async function papierkorbAusleeren(): Promise { return alt.length } -dashRoutes.get('/papierkorb', (c) => { - const items = db +dashRoutes.get('/papierkorb', async (c) => { + const roh = db .prepare('SELECT * FROM papierkorb ORDER BY geloescht DESC LIMIT 500') .all() as PapierkorbEintrag[] + + // OB DIE DATEI WIRKLICH NOCH DA IST -- ein stat je Zeile, hoechstens 500. + // + // Der Eintrag steht in media.db, die Datei liegt daneben auf der Platte: + // zwei Wahrheiten, die auseinanderlaufen koennen. Genau das ist passiert, + // solange die Sicherung /data/papierkorb ausliess -- nach einem + // Zurueckspielen zeigte der Papierkorb Zeilen, deren "zurueckholen" ins + // Leere griff. Die Sicherung nimmt ihn inzwischen mit; DIESE Pruefung + // bleibt trotzdem, weil ein Papierkorb, der etwas verspricht, es auch + // halten koennen muss -- und wenn nicht, es sagen soll, statt es erst beim + // Druecken herauszufinden. + const items = await Promise.all( + roh.map(async (e) => { + try { + await stat(papierkorbPfad(e.ablage)) + return { ...e, dateiDa: true } + } catch { + return { ...e, dateiDa: false } + } + }), + ) + const summe = db .prepare('SELECT COUNT(*) AS n, COALESCE(SUM(size),0) AS bytes FROM papierkorb') .get() as { n: number; bytes: number } - return c.json({ items, ...summe, haltbarTage: PAPIERKORB_TAGE }) + return c.json({ + items, + ...summe, + haltbarTage: PAPIERKORB_TAGE, + verwaist: items.filter((e) => !e.dateiDa).length, + }) }) /** Zurueck an ihren Platz. diff --git a/server/ui/src/api.ts b/server/ui/src/api.ts index d12608d..b313445 100644 --- a/server/ui/src/api.ts +++ b/server/ui/src/api.ts @@ -140,6 +140,10 @@ export type PapierkorbEintrag = { mime: string geloescht: number von: string + /** Liegt die Datei wirklich noch im Papierkorb? Der Eintrag steht in der + * Datenbank, die Datei daneben auf der Platte — zwei Wahrheiten, die + * auseinanderlaufen koennen. */ + dateiDa: boolean } export type PapierkorbSeite = { @@ -147,6 +151,8 @@ export type PapierkorbSeite = { n: number bytes: number haltbarTage: number + /** Eintraege ohne Datei. Sollte 0 sein. */ + verwaist: number } export type Speicher = { diff --git a/server/ui/src/pages/Papierkorb.tsx b/server/ui/src/pages/Papierkorb.tsx index 4a69fbd..6fdf1d4 100644 --- a/server/ui/src/pages/Papierkorb.tsx +++ b/server/ui/src/pages/Papierkorb.tsx @@ -105,6 +105,20 @@ export function Papierkorb() { )} + {daten.verwaist > 0 && ( +

+ + + Bei {daten.verwaist} {daten.verwaist === 1 ? 'Eintrag' : 'Einträgen'}{' '} + fehlt die Datei auf der Platte — zurückholen geht dort nicht. Das kommt + vor, wenn eine Sicherung zurückgespielt wurde, die den Papierkorb nicht + enthielt. Sie stehen unten durchgestrichen; „endgültig löschen" räumt sie + auf. + +

+ )} + {items.length === 0 ? ( {items.map((e) => ( - + {e.path} diff --git a/tools/sichern.ps1 b/tools/sichern.ps1 index 80ad498..578ebb4 100644 --- a/tools/sichern.ps1 +++ b/tools/sichern.ps1 @@ -114,13 +114,29 @@ $erwartet = if ($ausgabe -match '(\d+) Medieneintraege') { [int]$Matches[1] } el # -- 2. Alles in ein Archiv ---------------------------------------------------- Schritt 'Bilder und Datenbank packen' -# Zwei -C: die Bilder kommen als files/... in das Archiv, die Aufnahme als -# media.db daneben. Bewusst das eigene Abbild statt alpine -- dort ist GNU tar -# drin, und das kann mehrere -C. +# Drei -C: die Bilder kommen als files/... in das Archiv, die Aufnahme als +# media.db daneben, und der Papierkorb als papierkorb/. Bewusst das eigene +# Abbild statt alpine -- dort ist GNU tar drin, und das kann mehrere -C. +# +# DER PAPIERKORB MUSS MIT, und das ist nachgerechnet und nicht Vorsicht: seine +# EINTRAEGE 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. Ein Papierkorb, der ein Versprechen gibt, das er nach einer +# Wiederherstellung nicht halten kann, ist schlimmer als keiner. +# +# Er kostet fast nichts: was darin liegt, ist hoechstens 30 Tage alt, danach +# raeumt der Dienst selbst auf. +# +# Die Abfrage davor ist noetig: hat noch nie jemand etwas geloescht, gibt es den +# Ordner gar nicht, und tar braeche mit "Cannot stat" ab. Eine Sicherung, die +# daran scheitert, dass nichts im Papierkorb liegt, waere absurd. Das Volume +# haengt NUR LESEND -- ein `mkdir` als Ausweg ginge hier also ohnehin nicht. +$packen = "if [ -d /data/papierkorb ]; then tar czf '/out/$name' -C /data files -C /data/.sicherung media.db -C /data papierkorb; else tar czf '/out/$name' -C /data files -C /data/.sicherung media.db; fi" docker run --rm ` -v "${Volume}:/data:ro" ` -v "${Ziel}:/out" ` - --entrypoint sh $Abbild -c "tar czf '/out/$name' -C /data files -C /data/.sicherung media.db" + --entrypoint sh $Abbild -c $packen if ($LASTEXITCODE -ne 0) { Schlecht 'Packen fehlgeschlagen.'; exit 1 } # Die Zwischenaufnahme im Volume wieder weg: sie gehoert nicht zum Bestand und @@ -315,5 +331,6 @@ Melde $true $zahlen.media $groesse $inDerWolke $null Write-Host '' Gut "Fertig: $archiv" -Write-Host " Zurueckspielen: tar xzf , dann media.db und files/ ins Volume legen," -ForegroundColor DarkGray +Write-Host " Zurueckspielen: tar xzf , dann media.db, files/ und papierkorb/" -ForegroundColor DarkGray +Write-Host " ins Volume legen," -ForegroundColor DarkGray Write-Host " waehrend der Container steht. Siehe ROADMAP." -ForegroundColor DarkGray