Add temp disk space check endpoint

Add a new V2 API endpoint POST `/system/temp-disk-space` that reports
free and total disk space for the configured temporary folder. The
endpoint accepts an optional backup ID to resolve backup-specific
tempdir overrides, and returns the resolved temp path, free/total
space, `dblock-size`, and `restore-cache-max values`.
This commit is contained in:
Kenneth Skovhede
2026-02-19 14:23:51 +01:00
parent 6cfe010599
commit 8cebbd4f3c
4 changed files with 280 additions and 0 deletions
@@ -0,0 +1,32 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.WebserverCore.Dto.V2;
/// <summary>
/// The temporary disk space request DTO
/// </summary>
public sealed record TempDiskSpaceRequestDto
{
/// <summary>
/// The backup ID, if known. If provided, the system will check for a temp folder override in the backup settings.
/// </summary>
public string? BackupId { get; init; }
}
@@ -0,0 +1,100 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace Duplicati.WebserverCore.Dto.V2;
/// <summary>
/// The temporary disk space response DTO
/// </summary>
public sealed record TempDiskSpaceResponseDto : ResponseEnvelope<TempDiskSpaceResult>
{
/// <summary>
/// Creates a success response with the temp disk space information
/// </summary>
/// <param name="tempPath">The path to the temporary folder</param>
/// <param name="freeSpace">The free space in bytes</param>
/// <param name="totalSpace">The total space in bytes</param>
/// <returns>The response DTO</returns>
public static TempDiskSpaceResponseDto Create(string tempPath, long? freeSpace, long? totalSpace, long dblockSize, long restoreCacheMax)
{
return new TempDiskSpaceResponseDto()
{
Success = true,
Error = null,
StatusCode = "OK",
Data = new TempDiskSpaceResult()
{
TempPath = tempPath,
FreeSpace = freeSpace,
TotalSpace = totalSpace,
DblockSize = dblockSize,
RestoreCacheMax = restoreCacheMax
}
};
}
/// <summary>
/// Creates a failure response
/// </summary>
/// <param name="error">The error message</param>
/// <param name="statusCode">The status code</param>
/// <returns>The response DTO</returns>
public static new TempDiskSpaceResponseDto Failure(string error, string statusCode)
{
return new TempDiskSpaceResponseDto()
{
Success = false,
Error = error,
StatusCode = statusCode,
Data = null
};
}
}
/// <summary>
/// The temporary disk space result
/// </summary>
public sealed record TempDiskSpaceResult
{
/// <summary>
/// The path to the temporary folder that was checked
/// </summary>
public required string TempPath { get; init; }
/// <summary>
/// The free space available in bytes, or null if it could not be determined
/// </summary>
public required long? FreeSpace { get; init; }
/// <summary>
/// The total space of the drive in bytes, or null if it could not be determined
/// </summary>
public required long? TotalSpace { get; init; }
/// <summary>
/// The dblock-size setting in bytes
/// </summary>
public required long DblockSize { get; init; }
/// <summary>
/// The restore-cache-max setting in bytes
/// </summary>
public required long RestoreCacheMax { get; init; }
}
@@ -0,0 +1,147 @@
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using Duplicati.Library.Utility;
using Duplicati.Server.Database;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Dto.V2;
using Microsoft.AspNetCore.Mvc;
namespace Duplicati.WebserverCore.Endpoints.V2;
/// <summary>
/// Endpoint for checking temporary disk space
/// </summary>
public class TempDiskSpace : IEndpointV2
{
/// <summary>
/// Maps the endpoint routes
/// </summary>
/// <param name="group">The route group builder</param>
public static void Map(RouteGroupBuilder group)
{
group.MapPost("/system/temp-disk-space", ([FromServices] Connection connection, [FromBody] TempDiskSpaceRequestDto input)
=> Execute(connection, input))
.RequireAuthorization();
}
/// <summary>
/// Executes the temp disk space check
/// </summary>
/// <param name="connection">The database connection</param>
/// <param name="input">The request input</param>
/// <returns>The response DTO</returns>
private static TempDiskSpaceResponseDto Execute(Connection connection, TempDiskSpaceRequestDto input)
{
try
{
// Get the merged options for the backup (or defaults)
var options = GetMergedOptions(connection, input.BackupId);
// Determine the temp folder path from the merged options
var tempPath = GetTempPath(options);
// Get the free space for the temp path
var spaceInfo = Utility.GetFreeSpaceForPath(tempPath);
// Get the default options
var defaultOptions = new Library.Main.Options(new Dictionary<string, string?>());
// Get dblock-size and restore-cache-max from the options
var dblockSize = Sizeparser.ParseSize(
options.TryGetValue("dblock-size", out var dblockVal) && !string.IsNullOrWhiteSpace(dblockVal)
? dblockVal
: $"{defaultOptions.VolumeSize}b",
"mb");
var restoreCacheMax = Sizeparser.ParseSize(
options.TryGetValue("restore-cache-max", out var cacheVal) && !string.IsNullOrWhiteSpace(cacheVal)
? cacheVal
: $"{defaultOptions.RestoreCacheMax}b",
"mb");
return TempDiskSpaceResponseDto.Create(
tempPath: tempPath,
freeSpace: spaceInfo?.FreeSpace,
totalSpace: spaceInfo?.TotalSpace,
dblockSize: dblockSize,
restoreCacheMax: restoreCacheMax
);
}
catch (Exception ex)
{
return TempDiskSpaceResponseDto.Failure(
error: ex.Message,
statusCode: "error-checking-temp-space"
);
}
}
/// <summary>
/// Gets the merged options dictionary, combining application-level settings
/// with backup-specific settings (if a backupId is provided).
/// </summary>
/// <param name="connection">The database connection</param>
/// <param name="backupId">The optional backup ID</param>
/// <returns>The merged options dictionary</returns>
private static Dictionary<string, string?> GetMergedOptions(Connection connection, string? backupId)
{
// Start with application-level settings (ANY_BACKUP_ID)
var options = connection.Settings
.GroupBy(
k => k.Name.StartsWith("--", StringComparison.Ordinal) ? k.Name.Substring(2) : k.Name,
k => (string?)k.Value
)
.ToDictionary(g => g.Key, g => g.FirstOrDefault());
// If a backupId is provided, overlay the backup-specific settings
if (!string.IsNullOrWhiteSpace(backupId))
{
var backup = connection.GetBackup(backupId);
if (backup?.Settings != null)
{
foreach (var setting in backup.Settings)
{
var name = setting.Name.StartsWith("--", StringComparison.Ordinal)
? setting.Name.Substring(2)
: setting.Name;
options[name] = setting.Value;
}
}
}
return options;
}
/// <summary>
/// Gets the temporary folder path from the merged options.
/// Falls back to the system default temp path if no override is configured.
/// </summary>
/// <param name="options">The merged options dictionary</param>
/// <returns>The temp folder path</returns>
private static string GetTempPath(Dictionary<string, string?> options)
{
if (options.TryGetValue("tempdir", out var tempDir) && !string.IsNullOrWhiteSpace(tempDir))
return tempDir;
return TempFolder.SystemTempPath;
}
}
@@ -56,6 +56,7 @@ public class SystemInfoProvider(IApplicationSettings applicationSettings, Connec
"v1:subscribe:taskqueue",
"v1:subscribe:taskcompleted",
"v1:subscribe:notifications",
"v2:system:temp-disk-space",
// "v1:subscribe:scheduler",
];