Files
gott/Endpoints/BackupEndpoint.cs
T
Meleki 0c4d32de0f fix: Backup-Download (0 Bytes + fehlender Dateiname)
- api.js: credentials: 'include' hinzugefügt (Auth-Cookie)
- BackupEndpoint.cs: Task<IResult> → Task mit direktem Response-Handling
  (ASP.NET hat IResult nicht korrekt verarbeitet → leerer Body)
2026-03-03 10:01:30 +01:00

90 lines
2.7 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Gott.Extensions;
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 Task HandleBackup(HttpContext ctx)
{
if (!ctx.IsAdmin())
{
ctx.Response.StatusCode = 403;
return;
}
var dataDir = Path.Combine(AppContext.BaseDirectory, "data");
if (!Directory.Exists(dataDir))
{
ctx.Response.StatusCode = 404;
ctx.Response.ContentType = "application/json";
await ctx.Response.WriteAsync("{\"error\":\"Data-Ordner nicht gefunden\"}");
return;
}
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 entries = Directory.GetFileSystemEntries(dataDir)
.Select(e => Path.GetFileName(e))
.Where(e => e != "backup")
.ToArray();
var psi = new ProcessStartInfo
{
FileName = "tar",
RedirectStandardError = true,
UseShellExecute = false
};
psi.ArgumentList.Add("-czf");
psi.ArgumentList.Add(archivePath);
psi.ArgumentList.Add("-C");
psi.ArgumentList.Add(dataDir);
foreach (var entry in entries)
psi.ArgumentList.Add(entry);
using (var process = Process.Start(psi))
{
if (process == null)
{
ctx.Response.StatusCode = 500;
return;
}
var stderr = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
ctx.Response.StatusCode = 500;
ctx.Response.ContentType = "application/json";
await ctx.Response.WriteAsync($"{{\"error\":\"tar fehlgeschlagen: {stderr}\"}}");
return;
}
}
var bytes = await File.ReadAllBytesAsync(archivePath);
ctx.Response.StatusCode = 200;
ctx.Response.ContentType = "application/gzip";
ctx.Response.Headers["Content-Disposition"] = $"attachment; filename=\"{archiveName}\"";
await ctx.Response.Body.WriteAsync(bytes);
}
}