2026-10-07 11:40:06 +02:00
|
|
|
using System.Text.Json;
|
|
|
|
|
|
|
|
|
|
namespace PrivaPub.Infrastructure.Backup
|
|
|
|
|
{
|
|
|
|
|
// A restore asked for (by the administrator's page or the CLI) and carried out at the next boot, before anything
|
|
|
|
|
// else (Program, MaintenanceGate): restore.json in the backups' directory, written whole or not at all.
|
|
|
|
|
public sealed class RestoreMarker
|
|
|
|
|
{
|
|
|
|
|
public const string FileName = "restore.json";
|
|
|
|
|
public const int MaxAttempts = 3;
|
|
|
|
|
|
|
|
|
|
public string Backup { get; set; }//the backup restored
|
|
|
|
|
public string PreRestore { get; set; }//the backup taken just before, what the protective merge reads
|
|
|
|
|
public string State { get; set; } = "pending";//pending, then running
|
2026-10-07 11:55:17 +02:00
|
|
|
public string RequestedBy { get; set; }//who asked: a root's name, or "cli"
|
2026-10-07 11:40:06 +02:00
|
|
|
public int Attempts { get; set; }
|
|
|
|
|
public DateTime RequestedAt { get; set; } = DateTime.UtcNow;
|
|
|
|
|
public string Error { get; set; }
|
|
|
|
|
|
|
|
|
|
public static string PathIn(string backupsRoot) => Path.Combine(backupsRoot, FileName);
|
|
|
|
|
|
|
|
|
|
public static RestoreMarker Read(string backupsRoot)
|
|
|
|
|
{
|
|
|
|
|
var path = PathIn(backupsRoot);
|
|
|
|
|
if (!File.Exists(path))
|
|
|
|
|
return default;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return JsonSerializer.Deserialize<RestoreMarker>(File.ReadAllText(path));
|
|
|
|
|
}
|
|
|
|
|
catch (JsonException)
|
|
|
|
|
{
|
2026-10-07 11:55:17 +02:00
|
|
|
//never written half (Write is atomic): someone's hand; the server won't guess what was meant
|
|
|
|
|
return new RestoreMarker { State = "unreadable", Attempts = MaxAttempts, Error = $"{FileName} can't be read" };
|
2026-10-07 11:40:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write(string backupsRoot)
|
|
|
|
|
{
|
|
|
|
|
Directory.CreateDirectory(backupsRoot);
|
|
|
|
|
var path = PathIn(backupsRoot);
|
|
|
|
|
var part = path + ".part";
|
|
|
|
|
using (var file = new FileStream(part, FileMode.Create, FileAccess.Write))
|
|
|
|
|
{
|
|
|
|
|
JsonSerializer.Serialize(file, this);
|
|
|
|
|
file.Flush(flushToDisk: true);
|
|
|
|
|
}
|
|
|
|
|
File.Move(part, path, overwrite: true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void Clear(string backupsRoot)
|
|
|
|
|
{
|
|
|
|
|
var path = PathIn(backupsRoot);
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
File.Delete(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|