Files
duplicati/Duplicati/WebserverCore/Endpoints/V1/Backups.cs
T

236 lines
9.7 KiB
C#

using System.Text.Json;
using Duplicati.Library.RestAPI;
using Duplicati.Server.Database;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Exceptions;
using Microsoft.AspNetCore.Mvc;
namespace Duplicati.WebserverCore.Endpoints.V1;
public class Backups : IEndpointV1
{
public static void Map(RouteGroupBuilder group)
{
group.MapGet("/backups", ([FromServices] Connection connection)
=> ExecuteGet(connection))
.RequireAuthorization();
// TODO: Figure out why the JSON deserialization is not working here
// group.MapPost("/backups", ([FromServices] Connection connection, [FromBody] Dto.BackupAndScheduleInputDto input, [FromQuery] bool? temporary, [FromQuery] bool? existingdb)
// => ExecuteAdd(connection, input, temporary ?? false, existingdb ?? false)).RequireAuthorization();
group.MapPost("/backups", async ([FromServices] Connection connection, [FromQuery] bool? temporary, [FromQuery] bool? existingdb, [FromServices] IHttpContextAccessor httpContextAccessor) =>
{
var opts = new JsonSerializerOptions()
{
Converters = { new DayOfWeekStringEnumConverter() }
};
var input = await JsonSerializer.DeserializeAsync<Dto.BackupAndScheduleInputDto>(httpContextAccessor.HttpContext!.Request.Body, opts)
?? throw new BadRequestException("No data found in request body");
return ExecuteAdd(connection, input, temporary ?? false, existingdb ?? false);
})
.RequireAuthorization();
group.MapPost("/backups/import", ([FromBody] Dto.ImportBackupInputDto input, [FromServices] IJWTTokenProvider jWTTokenProvider, [FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor) =>
{
using var tempfile = new Library.Utility.TempFile();
File.WriteAllBytes(tempfile, Convert.FromBase64String(input.config));
var html = ExecuteImport(connection, input.cmdline ?? false, input.import_metadata ?? false, input.direct ?? false, input.passphrase ?? "", tempfile);
httpContextAccessor.HttpContext!.Response.ContentType = "text/html";
return html;
}).RequireAuthorization();
}
private static IEnumerable<Dto.BackupAndScheduleOutputDto> ExecuteGet(Connection connection)
{
var schedules = connection.Schedules;
var backups = connection.Backups;
var all = backups.Select(n => new
{
IsUnencryptedOrPassphraseStored = connection.IsUnencryptedOrPassphraseStored(long.Parse(n.ID)),
Backup = n,
Schedule = schedules.FirstOrDefault(x => x.Tags != null && x.Tags.Contains("ID=" + n.ID))
});
return all.Select(x => new Dto.BackupAndScheduleOutputDto()
{
Backup = new Dto.BackupDto()
{
ID = x.Backup.ID,
Name = x.Backup.Name,
Description = x.Backup.Description,
IsTemporary = x.Backup.IsTemporary,
IsUnencryptedOrPassphraseStored = x.IsUnencryptedOrPassphraseStored,
Metadata = x.Backup.Metadata,
Sources = x.Backup.Sources,
Settings = x.Backup.Settings?.Select(y => new Dto.SettingDto()
{
Name = y.Name,
Value = y.Value,
Filter = y.Filter,
Argument = y.Argument
}),
Filters = x.Backup.Filters?.Select(y => new Dto.FilterDto()
{
Order = y.Order,
Include = y.Include,
Expression = y.Expression,
}),
TargetURL = x.Backup.TargetURL,
DBPath = x.Backup.DBPath,
Tags = x.Backup.Tags
},
Schedule = x.Schedule == null ? null : new Dto.ScheduleDto()
{
ID = x.Schedule.ID,
Tags = x.Schedule.Tags,
Time = x.Schedule.Time,
Repeat = x.Schedule.Repeat,
LastRun = x.Schedule.LastRun,
Rule = x.Schedule.Rule,
AllowedDays = x.Schedule.AllowedDays
}
});
}
private static Dto.ImportBackupOutputDto ExecuteImport(Connection connection, bool cmdline, bool import_metadata, bool direct, string passphrase, string tempfile)
{
try
{
if (cmdline)
throw new BadRequestException("Import from commandline not yet implemented");
var ipx = BackupImportExportHandler.LoadConfiguration(tempfile, import_metadata, () => passphrase);
if (direct)
{
lock (connection.m_lock)
{
var basename = ipx.Backup.Name;
var c = 0;
while (c++ < 100 && connection.Backups.Any(x => x.Name.Equals(ipx.Backup.Name, StringComparison.OrdinalIgnoreCase)))
ipx.Backup.Name = basename + " (" + c.ToString() + ")";
if (connection.Backups.Any(x => x.Name.Equals(ipx.Backup.Name, StringComparison.OrdinalIgnoreCase)))
throw new BadRequestException("There already exists a backup with that name");
var err = connection.ValidateBackup(ipx.Backup, ipx.Schedule);
if (!string.IsNullOrWhiteSpace(err))
throw new BadRequestException(err);
connection.AddOrUpdateBackupAndSchedule(ipx.Backup, ipx.Schedule);
}
return new Dto.ImportBackupOutputDto(ipx.Backup.ID, null);
}
else
{
return new Dto.ImportBackupOutputDto(null, ipx);
}
}
catch (Exception ex)
{
connection.LogError("", "Failed to import backup", ex);
throw new ServerErrorException($"Failed to import backup: {ex.Message}");
}
}
private class WrappedBackup : Server.Database.Backup
{
public string? DBPathSetter
{
get => DBPath;
set => SetDBPath(value);
}
}
private static Dto.CreateBackupDto ExecuteAdd(Connection connection, Dto.BackupAndScheduleInputDto data, bool temporary, bool existingDb)
{
try
{
if (data.Backup == null)
throw new BadRequestException("Data object had no backup entry");
var filters = data.Backup.Filters ?? Array.Empty<Dto.BackupAndScheduleInputDto.FilterInputDto>();
var settings = data.Backup.Settings ?? Array.Empty<Dto.BackupAndScheduleInputDto.SettingInputDto>();
var backup = new WrappedBackup()
{
ID = null,
Name = data.Backup.Name,
Description = data.Backup.Description,
Tags = data.Backup.Tags,
TargetURL = data.Backup.TargetURL,
DBPathSetter = null,
Sources = data.Backup.Sources,
Settings = settings.Select(x => new Setting()
{
Name = x.Name,
Value = x.Value,
Filter = x.Filter
}).ToArray(),
Filters = filters.Select(x => new Filter()
{
Order = x.Order,
Include = x.Include,
Expression = x.Expression
}).ToArray(),
Metadata = data.Backup.Metadata ?? new Dictionary<string, string>()
};
var schedule = data.Schedule == null ? null : new Schedule()
{
ID = data.Schedule.ID,
Tags = data.Schedule.Tags,
Time = data.Schedule.Time ?? new DateTime(0),
Repeat = data.Schedule.Repeat,
LastRun = data.Schedule.LastRun ?? new DateTime(0),
Rule = data.Schedule.Rule,
AllowedDays = data.Schedule.AllowedDays
};
if (temporary)
{
using (var tf = new Library.Utility.TempFile())
backup.DBPathSetter = tf;
connection.RegisterTemporaryBackup(backup);
}
else
{
if (existingDb)
{
backup.DBPathSetter = Library.Main.DatabaseLocator.GetDatabasePath(data.Backup.TargetURL, null, false, false);
if (string.IsNullOrWhiteSpace(data.Backup.DBPath))
throw new Exception("Unable to find remote db path?");
}
lock (connection.m_lock)
{
if (connection.Backups.Any(x => x.Name.Equals(data.Backup.Name, StringComparison.OrdinalIgnoreCase)))
throw new ConflictException($"There already exists a backup with the name: {data.Backup.Name}");
var err = connection.ValidateBackup(backup, schedule);
if (!string.IsNullOrWhiteSpace(err))
throw new BadRequestException(err);
connection.AddOrUpdateBackupAndSchedule(backup, schedule);
}
}
return new Dto.CreateBackupDto(backup.ID, backup.IsTemporary);
}
catch (Exception ex)
{
if (data == null)
throw new BadRequestException($"Data object was null: {ex.Message}");
if (ex is UserReportedHttpException)
throw;
throw new ServerErrorException($"Unable to save schedule or backup object: {ex.Message}");
}
}
}