From d434c3ff2aed7bdcb9edeb41fd6fbce1c8ca0e15 Mon Sep 17 00:00:00 2001 From: Meleki Date: Sat, 28 Feb 2026 18:18:51 +0100 Subject: [PATCH] Datenbank sichern: Backup als .tar.gz mit Server-Ablage und Client-Download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neuer POST /api/backup Endpoint (BackupEndpoint.cs) - Packt gott.db in data/backup/gott-{timestamp}.tar.gz - Gibt Archiv gleichzeitig als Download an Client zurück - Button 'Datenbank sichern' im Header-Dropdown-Menü - Content-Disposition Header-Parsing mit filename* UTF-8 Support --- Endpoints/BackupEndpoint.cs | 55 +++++++++++++++++++++++++++++++++++ Program.cs | 1 + wwwroot/index.html | 1 + wwwroot/js/api.js | 18 ++++++++++++ wwwroot/js/pages/chat-page.js | 16 ++++++++++ 5 files changed, 91 insertions(+) create mode 100644 Endpoints/BackupEndpoint.cs diff --git a/Endpoints/BackupEndpoint.cs b/Endpoints/BackupEndpoint.cs new file mode 100644 index 0000000..4295d3f --- /dev/null +++ b/Endpoints/BackupEndpoint.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace Gott.Endpoints; + +public static class BackupEndpoint +{ + public static void MapBackupEndpoint(this IEndpointRouteBuilder app) + { + app.MapPost("/api/backup", HandleBackup); + } + + private static async System.Threading.Tasks.Task HandleBackup() + { + var dataDir = Path.Combine(AppContext.BaseDirectory, "data"); + var dbPath = Path.Combine(dataDir, "gott.db"); + + if (!File.Exists(dbPath)) + return Results.NotFound(new { error = "Datenbank nicht gefunden" }); + + var backupDir = Path.Combine(dataDir, "backup"); + Directory.CreateDirectory(backupDir); + + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var archiveName = $"gott-{timestamp}.tar.gz"; + var archivePath = Path.Combine(backupDir, archiveName); + + var psi = new ProcessStartInfo + { + FileName = "tar", + ArgumentList = { "-czf", archivePath, "-C", dataDir, "gott.db" }, + RedirectStandardError = true, + UseShellExecute = false + }; + + using (var process = Process.Start(psi)) + { + if (process == null) + return Results.StatusCode(500); + + var stderr = await process.StandardError.ReadToEndAsync(); + await process.WaitForExitAsync(); + + if (process.ExitCode != 0) + return Results.Problem($"tar fehlgeschlagen: {stderr}"); + } + + var bytes = await File.ReadAllBytesAsync(archivePath); + return Results.File(bytes, "application/gzip", archiveName); + } +} diff --git a/Program.cs b/Program.cs index b36bb24..112b7d7 100644 --- a/Program.cs +++ b/Program.cs @@ -25,5 +25,6 @@ app.MapSessionEndpoints(); app.MapAgentEndpoints(); app.MapWorkspaceEndpoint(); app.MapInfoEndpoint(); +app.MapBackupEndpoint(); app.Run("http://0.0.0.0:19000"); diff --git a/wwwroot/index.html b/wwwroot/index.html index 5a889ae..d841784 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -147,6 +147,7 @@ diff --git a/wwwroot/js/api.js b/wwwroot/js/api.js index d11b10a..6560c8e 100644 --- a/wwwroot/js/api.js +++ b/wwwroot/js/api.js @@ -128,3 +128,21 @@ async function apiGetChain(chainId) { if (!resp.ok) return null; return resp.json(); } + +async function apiBackupDb() { + const resp = await fetch('/api/backup', { method: 'POST' }); + if (!resp.ok) throw new Error('Backup fehlgeschlagen'); + const blob = await resp.blob(); + const cd = resp.headers.get('content-disposition') || ''; + const filename = cd.match(/filename\*=UTF-8''(.+?)(?:;|$)/i)?.[1] + || cd.match(/filename="?([^";]+)"?/)?.[1] + || 'gott-backup.tar.gz'; + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); +} diff --git a/wwwroot/js/pages/chat-page.js b/wwwroot/js/pages/chat-page.js index 6236640..b10f92d 100644 --- a/wwwroot/js/pages/chat-page.js +++ b/wwwroot/js/pages/chat-page.js @@ -190,6 +190,22 @@ registerPage('chat', { clearChat(); }); + // Datenbank sichern + const backupDbBtn = document.getElementById('backupDbBtn'); + if (backupDbBtn) backupDbBtn.addEventListener('click', async () => { + if (chatMenuDropdown) chatMenuDropdown.hidden = true; + backupDbBtn.disabled = true; + try { + await apiBackupDb(); + setMeta('✅ Backup erstellt und heruntergeladen'); + } catch (e) { + console.error('Backup fehlgeschlagen:', e); + setMeta('❌ Backup fehlgeschlagen'); + } finally { + backupDbBtn.disabled = false; + } + }); + // State zurücksetzen und History laden messages.length = 0; lastKnownMessageId = 0;