backup: die Sicherung verlaesst das Volume -- tools/abholen.ps1 plus Wachhund
Befund 1 aus docs/befunde-2026-08-12.md, der wichtigste: Datenbank und alle
vierzehn Sicherungen lagen im selben Volume. Geht das Volume verloren, geht
beides zusammen verloren -- das ist keine Sicherung, das ist eine Kopie.
tools/abholen.ps1 SICHERT NICHT. Das Archiv baut der Bot selbst; hier wird nur
abgeholt, und zwar so, dass am Ende feststeht, dass die Kopie heil ist und
nicht nur, dass ein Kopierbefehl keinen Fehler geworfen hat:
1. neuestes Archiv im Container finden (und meckern, wenn es aelter als
24 Stunden ist -- ein Skript, das treu das Archiv von vorletzter Woche
abholt und "fertig" sagt, ist schlimmer als eines, das gar nicht laeuft)
2. docker cp heraus
3. Pruefsumme IM CONTAINER gegen die Kopie hier -- nicht die Dateigroesse:
eine abgebrochene Kopie auf eine volle Platte hat oft genau die richtige
Laenge und trotzdem Nullen am Ende
4. auspacken und die Datenbank darin oeffnen, mit dem Bot-Abbild, weil dort
better-sqlite3 schon liegt; integrity_check plus Tabellen zaehlen
5. Bericht als last_zweitziel zurueck in die Einstellungen
6. ausduennen
Und es warnt, wenn es DIESELBE PLATTE ist. Verglichen wird die physische Platte
und nicht der Laufwerksbuchstabe: zwei Partitionen derselben NVMe sterben
zusammen.
Echter Lauf, kein Trockentest:
d4rkbot-2026-08-12.db.gz 993.332 Bytes
andere Platte: Nr. 1 statt Nr. 0
identisch (ff08516d293d...)
in der Sicherung: 55 Tabellen, 86 Einstellungen
DAZU EIN ZWEITER WACHHUND, pruefeZweitziel(). Ein Skript auf dem Wirt, das
still aufhoert zu laufen -- Aufgabe deaktiviert, Laufwerk weg, Pfad geaendert --
waere derselbe Schaden noch einmal, nur eine Ebene weiter aussen. Er meldet nur,
wenn die Abholung schon einmal lief: fehlt der Eintrag ganz, ist sie nicht
eingerichtet, und daraus taeglich eine Meldung zu machen waere Naergelei.
Gegen eine Kopie der Datenbank durchgespielt:
40 h alt -> "alt:17863965"
nochmal -> unveraendert (keine Wiederholung)
Fehlschlag -> "fehler:1786540570974"
wieder gut -> "" (Entwarnung)
Nebenbei gelernt, zum zweiten Mal in diesem Projekt: die Kopie der Datenbank
ohne -wal war leer an der Stelle, die zaehlte -- last_zweitziel stand noch im
Write-Ahead-Log. Dieselbe Falle wie beim Zurueckspielen von d4rk_media.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -222,11 +222,62 @@ export async function pruefeSicherung(client) {
|
||||
// später erneut gemeldet werden kann.
|
||||
await dmAdminEinmalig(client, 'backup_warn', null);
|
||||
}
|
||||
|
||||
await pruefeZweitziel(client);
|
||||
} catch (error) {
|
||||
console.error('[backup] Wachhund:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wacht ueber die ABHOLUNG auf die zweite Platte (tools/abholen.ps1).
|
||||
*
|
||||
* WOZU EIN ZWEITER WACHHUND. Die Sicherung des Bots liegt im selben Volume wie
|
||||
* seine Datenbank; erst das Abholen bringt sie dort heraus. Ein Skript auf dem
|
||||
* Wirt, das still aufhoert zu laufen -- geplante Aufgabe deaktiviert, Laufwerk
|
||||
* nicht da, Pfad geaendert --, waere also genau derselbe Schaden noch einmal,
|
||||
* nur eine Ebene weiter aussen.
|
||||
*
|
||||
* NUR WENN ES SCHON EINMAL LIEF. Fehlt der Eintrag ganz, ist die Abholung
|
||||
* nicht eingerichtet, und daraus jeden Tag eine Meldung zu machen waere
|
||||
* Naergelei -- wer sie nicht will, soll nicht belaestigt werden.
|
||||
*/
|
||||
async function pruefeZweitziel(client) {
|
||||
const roh = getSetting('last_zweitziel');
|
||||
if (!roh) return;
|
||||
|
||||
let letzte;
|
||||
try { letzte = JSON.parse(roh); } catch { return; }
|
||||
|
||||
const stunden = (Date.now() - letzte.at) / 3_600_000;
|
||||
|
||||
if (letzte.ok === false) {
|
||||
await dmAdminEinmalig(client, 'zweitziel_warn', `fehler:${letzte.at}`, () =>
|
||||
brandEmbed(client, 'BACKUP', { secondary: true })
|
||||
.setTitle('🚨 Die Sicherung wurde nicht abgeholt')
|
||||
.setDescription(letzte.fehler || 'Kein Grund gemeldet.')
|
||||
.addFields({ name: 'Ziel', value: `\`${letzte.ziel ?? '?'}\``, inline: true }));
|
||||
} else if (stunden > UEBERFAELLIG_STUNDEN) {
|
||||
await dmAdminEinmalig(client, 'zweitziel_warn', `alt:${String(letzte.at).slice(0, 8)}`, () =>
|
||||
brandEmbed(client, 'BACKUP', { secondary: true })
|
||||
.setTitle('⚠️ Die Sicherung wird nicht mehr abgeholt')
|
||||
.setDescription(
|
||||
'Sie liegt damit nur noch **im selben Volume** wie die Datenbank. ' +
|
||||
'Läuft die geplante Aufgabe für `tools/abholen.ps1` noch?',
|
||||
)
|
||||
.addFields(
|
||||
{
|
||||
name: 'Zuletzt geholt',
|
||||
value: `<t:${Math.floor(letzte.at / 1000)}:R>`,
|
||||
inline: true,
|
||||
},
|
||||
{ name: 'Ziel', value: `\`${letzte.ziel ?? '?'}\``, inline: true },
|
||||
));
|
||||
} else {
|
||||
await dmAdminEinmalig(client, 'zweitziel_warn', null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Scheduler: täglich ab 03:00 (lokale TZ), einmal pro Tag — und er HOLT NACH.
|
||||
*
|
||||
* Vorher stand hier `if (now.getHours() !== 3) return`. Wer während dieser
|
||||
|
||||
Reference in New Issue
Block a user