- 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)
90 lines
2.7 KiB
C#
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);
|
|
}
|
|
}
|