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>
49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
// Eine Sicherung der Datenbank, die auch stimmt, waehrend geschrieben wird.
|
|
//
|
|
// WARUM NICHT EINFACH KOPIEREN. media.db laeuft im WAL-Modus. Was zuletzt
|
|
// hochgeladen wurde, steht dann noch in media.db-wal und nicht in media.db —
|
|
// eine Kopie nur der einen Datei ist deshalb still unvollstaendig. Beim ersten
|
|
// Nachsehen im Container war das WAL nach wenigen Uploads 152 KB gross.
|
|
//
|
|
// Und selbst alle drei Dateien zu kopieren reicht nicht sicher: waehrend des
|
|
// Kopierens kann geschrieben werden, und dann passen sie nicht mehr zueinander.
|
|
//
|
|
// Deshalb SQLites eigene Sicherungsschnittstelle. Sie laeuft auf der offenen
|
|
// Datenbank, nimmt eine in sich stimmige Aufnahme und faengt von vorn an, wenn
|
|
// dazwischen geschrieben wurde. Das Ergebnis ist EINE Datei ohne WAL daneben.
|
|
//
|
|
// Aufruf im Container:
|
|
//
|
|
// node dist/backup.js /data/.sicherung/media.db
|
|
//
|
|
// Der Rest — Bilder dazupacken, herausholen, pruefen — steht in
|
|
// tools/sichern.ps1; hier bleibt nur der Teil, der die Datenbank kennt.
|
|
|
|
import { mkdirSync } from 'node:fs'
|
|
import { dirname } from 'node:path'
|
|
import { db } from './db.js'
|
|
|
|
const ziel = process.argv[2]
|
|
|
|
if (!ziel) {
|
|
console.error('[sicherung] Kein Ziel angegeben. Aufruf: node dist/backup.js <pfad>')
|
|
process.exit(2)
|
|
}
|
|
|
|
mkdirSync(dirname(ziel), { recursive: true })
|
|
|
|
const zeilen = db.prepare('SELECT COUNT(*) AS n FROM media').get() as { n: number }
|
|
|
|
db.backup(ziel)
|
|
.then(() => {
|
|
// Die Zeilenzahl mit ausgeben: sie ist der Wert, gegen den die
|
|
// Sicherung hinterher geprueft wird. Eine Sicherung, die niemand
|
|
// nachzaehlt, ist eine Vermutung.
|
|
console.log(`[sicherung] ${ziel} geschrieben, ${zeilen.n} Medieneintraege`)
|
|
process.exit(0)
|
|
})
|
|
.catch((err: unknown) => {
|
|
console.error(`[sicherung] fehlgeschlagen: ${err instanceof Error ? err.message : err}`)
|
|
process.exit(1)
|
|
})
|