Automatische DB-Backups: nächtlich, gzip, Rotation, optional Discord-Upload

- SQLite-Online-Backup (WAL-sicher) täglich 03:00 → data/backups/*.db.gz, 14 Tage Rotation
- Optional Offsite-Kopie als Upload in einen privaten Discord-Kanal (Setting)
- Setup-Seite: Toggle, Upload-Kanal, 'Backup jetzt'-Button; letztes Backup im Status-Panel

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 12:22:23 +02:00
co-authored by Claude Opus 4.8
parent db2e7987a0
commit 39f9fd4f67
4 changed files with 136 additions and 3 deletions
+21 -3
View File
@@ -147,6 +147,8 @@ ${rssItems}
watchdog_urls: getSetting('watchdog_urls') ?? '',
public_url: publicUrl(),
gitea_url: getSetting('gitea_url') ?? config.giteaUrl,
backup_enabled: getSetting('backup_enabled') !== '0',
backup_channel_id: getSetting('backup_channel_id') ?? '',
};
}
@@ -163,6 +165,7 @@ ${rssItems}
uptimeSeconds: Math.floor(process.uptime()),
guilds: client.guilds.cache.size,
giteaTokenConfigured: Boolean(config.giteaApiToken),
lastBackup: getSetting('last_backup'),
...stats,
},
};
@@ -173,11 +176,11 @@ ${rssItems}
const body = request.body ?? {};
// Kanäle: müssen existierende Textkanäle sein (release darf leer sein = deaktiviert)
for (const key of ['commit_channel_id', 'devlog_channel_id', 'release_channel_id']) {
// Kanäle: müssen existierende Textkanäle sein (release/backup dürfen leer sein = aus)
for (const key of ['commit_channel_id', 'devlog_channel_id', 'release_channel_id', 'backup_channel_id']) {
if (body[key] === undefined) continue;
const value = String(body[key]);
if (value === '' && key === 'release_channel_id') {
if (value === '' && ['release_channel_id', 'backup_channel_id'].includes(key)) {
setSetting(key, '');
continue;
}
@@ -209,6 +212,9 @@ ${rssItems}
if (body.devlog_threads_enabled !== undefined) {
setSetting('devlog_threads_enabled', body.devlog_threads_enabled ? '1' : '0');
}
if (body.backup_enabled !== undefined) {
setSetting('backup_enabled', body.backup_enabled ? '1' : '0');
}
for (const key of ['commit_branch_filter', 'ignored_repos', 'bug_report_repo', 'watchdog_urls']) {
if (body[key] !== undefined) {
setSetting(key, String(body[key]).trim());
@@ -275,6 +281,18 @@ ${rssItems}
return reply.code(502).send({ error: 'Senden fehlgeschlagen' });
}
}
// Sonderfall: Backup sofort erstellen
if (target === 'backup') {
try {
const { runBackup } = await import('../backup.js');
const result = await runBackup(client);
return { ok: true, sizeBytes: result.sizeBytes };
} catch (error) {
request.log.error({ err: error }, 'Backup-Test fehlgeschlagen');
return reply.code(502).send({ error: 'Backup fehlgeschlagen' });
}
}
const channelId =
target === 'commit' ? commitChannelId()
: target === 'devlog' ? devlogChannelId()