Merge pull request #6414 from duplicati/feature/fix-serverutil-probing

Update ServerUtil probing
This commit is contained in:
Kenneth Skovhede
2025-07-11 11:05:12 +02:00
committed by GitHub
5 changed files with 81 additions and 68 deletions
+42 -38
View File
@@ -193,58 +193,62 @@ public class Connection
}
// If we can read the server database, try to create a signin token
try
// But don't try to look at the database if the password is already set.
if (string.IsNullOrWhiteSpace(settings.Password))
{
var opts = new Dictionary<string, string>();
if (settings.Key != null)
opts["settings-encryption-key"] = settings.Key.Key;
if (File.Exists(Path.Combine(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly), DataFolderManager.SERVER_DATABASE_FILENAME)))
try
{
string? cfg = null;
using (var connection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), opts, true))
var opts = new Dictionary<string, string>();
if (settings.Key != null)
opts["settings-encryption-key"] = settings.Key.Key;
if (File.Exists(Path.Combine(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly), DataFolderManager.SERVER_DATABASE_FILENAME)))
{
cfg = connection.ApplicationSettings.JWTConfig;
if (settings.HostUrl.Scheme == "https" && connection.ApplicationSettings.ServerSSLCertificate != null && trustedCertificateHashes.Count == 0)
string? cfg = null;
using (var connection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), opts, true, false))
{
var selfSignedCertHash = connection.ApplicationSettings.ServerSSLCertificate?.FirstOrDefault(x => x.HasPrivateKey)?.GetCertHashString();
if (!string.IsNullOrWhiteSpace(selfSignedCertHash))
trustedCertificateHashes.Add(selfSignedCertHash);
cfg = connection.ApplicationSettings.JWTConfig;
if (settings.HostUrl.Scheme == "https" && connection.ApplicationSettings.ServerSSLCertificate != null && trustedCertificateHashes.Count == 0)
{
var selfSignedCertHash = connection.ApplicationSettings.ServerSSLCertificate?.FirstOrDefault(x => x.HasPrivateKey)?.GetCertHashString();
if (!string.IsNullOrWhiteSpace(selfSignedCertHash))
trustedCertificateHashes.Add(selfSignedCertHash);
}
}
if (!string.IsNullOrWhiteSpace(cfg))
{
var signinjwt = new JWTTokenProvider(
JsonSerializer.Deserialize<JWTConfig>(cfg)
?? throw new InvalidOperationException("Failed to deserialize JWTConfig")
).CreateSigninToken("server-cli");
var responseTask = client.PostAsync("auth/signin", JsonContent.Create(new { SigninToken = signinjwt, RememberMe = obtainRefreshToken }));
var (accessToken, refreshToken) = await ParseAuthResponse(responseTask);
if (string.IsNullOrWhiteSpace(accessToken))
throw new InvalidOperationException("Failed to get access token");
if (!string.IsNullOrWhiteSpace(refreshToken))
(settings with { RefreshToken = refreshToken }).Save(console);
return CreateConnectionWithClient(client, accessToken);
}
}
if (!string.IsNullOrWhiteSpace(cfg))
else if (!string.IsNullOrWhiteSpace(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)))
{
var signinjwt = new JWTTokenProvider(
JsonSerializer.Deserialize<JWTConfig>(cfg)
?? throw new InvalidOperationException("Failed to deserialize JWTConfig")
).CreateSigninToken("server-cli");
var responseTask = client.PostAsync("auth/signin", JsonContent.Create(new { SigninToken = signinjwt, RememberMe = obtainRefreshToken }));
var (accessToken, refreshToken) = await ParseAuthResponse(responseTask);
if (string.IsNullOrWhiteSpace(accessToken))
throw new InvalidOperationException("Failed to get access token");
if (!string.IsNullOrWhiteSpace(refreshToken))
(settings with { RefreshToken = refreshToken }).Save(console);
return CreateConnectionWithClient(client, accessToken);
if (console != null)
console.AppendConsoleMessage($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}");
else
Console.WriteLine($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}");
}
}
else if (!string.IsNullOrWhiteSpace(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)))
catch (Exception ex)
{
if (console != null)
console.AppendConsoleMessage($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}");
console.AppendConsoleMessage($"Failed to obtain a signin token: {ex.Message}");
else
Console.WriteLine($"No database found in {DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)}");
Console.WriteLine($"Failed to obtain a signin token: {ex.Message}");
}
}
catch (Exception ex)
{
if (console != null)
console.AppendConsoleMessage($"Failed to obtain a signin token: {ex.Message}");
else
Console.WriteLine($"Failed to obtain a signin token: {ex.Message}");
}
// Otherwise, we need a password to log in
try
@@ -192,7 +192,7 @@ namespace Duplicati.GUI.TrayIcon
if (File.Exists(Path.Combine(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ReadWritePermissionSet), DataFolderManager.SERVER_DATABASE_FILENAME)))
{
passwordSource = PasswordSource.Database;
databaseConnection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), options, true);
databaseConnection = Server.Program.GetDatabaseConnection(new ApplicationSettings(), options, true, false);
if (databaseConnection != null)
{
@@ -25,6 +25,7 @@ using System;
using System.IO;
using System.Linq;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Interface;
using Duplicati.Library.Utility;
namespace Duplicati.Library.AutoUpdater;
@@ -95,9 +96,9 @@ public static class DataFolderManager
{
// Trigger portable mode, if the flag is set
PORTABLE_MODE = ParseBoolSlim(ExtractOptionSlim(PORTABLE_MODE_OPTION));
string dataFolder = string.Empty;
// The environment variable is a legacy setting
var envOverride = Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME);
@@ -142,17 +143,23 @@ public static class DataFolderManager
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", dataFolder, ex.Message);
}
}
else
else if (mode == AccessMode.ReadWritePermissionSet)
{
Directory.CreateDirectory(dataFolder);
try
{
if (mode == AccessMode.ReadWritePermissionSet)
Directory.CreateDirectory(dataFolder);
try
{
SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(dataFolder);
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", dataFolder, ex.Message);
}
}
catch (Exception ex)
{
Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", dataFolder, ex.Message);
throw new UserInformationException($"Failed to create data folder {dataFolder}", "FailedToCreateDataFolder", ex);
}
}
@@ -167,7 +174,7 @@ public static class DataFolderManager
if (mode == AccessMode.ReadWritePermissionSet && !File.Exists(Path.Combine(dataFolder, MACHINE_FILE)))
File.WriteAllText(Path.Combine(dataFolder, MACHINE_FILE), AutoUpdateSettings.UpdateMachineFileText(InstallID));
return dataFolder;
}
@@ -227,7 +234,7 @@ public static class DataFolderManager
&& !value.Equals("no", StringComparison.OrdinalIgnoreCase)
&& !value.Equals("off", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// The unique machine installation ID
/// </summary>
+22 -20
View File
@@ -197,7 +197,7 @@ namespace Duplicati.Server
ISchedulerService scheduler = null;
try
{
var connection = GetDatabaseConnection(applicationSettings, commandlineOptions, silentConsole);
var connection = GetDatabaseConnection(applicationSettings, commandlineOptions, silentConsole, true);
if (!connection.ApplicationSettings.FixedInvalidBackupId)
connection.FixInvalidBackupId();
@@ -736,7 +736,7 @@ namespace Duplicati.Server
throw new Exception("Server invoked with --help");
}
public static Connection GetDatabaseConnection(IApplicationSettings applicationSettings, Dictionary<string, string> commandlineOptions, bool silentConsole)
public static Connection GetDatabaseConnection(IApplicationSettings applicationSettings, Dictionary<string, string> commandlineOptions, bool silentConsole, bool changeDbEncryption)
{
// Emit a warning if the database is stored in the Windows folder
if (Util.IsPathUnderWindowsFolder(applicationSettings.DataFolder))
@@ -786,29 +786,26 @@ namespace Duplicati.Server
if (requireDbEncryptionKey && !(hasValidEncryptionKey || disableDbEncryption))
throw new UserInformationException(Strings.Program.DatabaseEncryptionKeyRequired(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION), "RequireDbEncryptionKey");
if (!hasValidEncryptionKey)
var hasEncryptedFields = false;
try
{
try
{
var hasEncryptedFields = false;
using (var cmd = con.CreateCommand(@$"SELECT ""Value"" FROM ""Option"" WHERE ""Name"" = @Name AND ""BackupID"" = @BackupId"))
hasEncryptedFields = Library.Utility.Utility.ParseBool(cmd
.SetParameterValue("@Name", Database.ServerSettings.CONST.ENCRYPTED_FIELDS)
.SetParameterValue("@BackupId", Connection.SERVER_SETTINGS_ID).ExecuteScalar()?.ToString(), false);
using (var cmd = con.CreateCommand(@$"SELECT ""Value"" FROM ""Option"" WHERE ""Name"" = @Name AND ""BackupID"" = @BackupId"))
hasEncryptedFields = Library.Utility.Utility.ParseBool(cmd
.SetParameterValue("@Name", Database.ServerSettings.CONST.ENCRYPTED_FIELDS)
.SetParameterValue("@BackupId", Connection.SERVER_SETTINGS_ID).ExecuteScalar()?.ToString(), false);
if (hasEncryptedFields)
{
Log.WriteWarningMessage(LOGTAG, "EncryptionKeyMissing", null, Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME));
if (!silentConsole)
Console.WriteLine(Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME));
}
}
catch
if (hasEncryptedFields && !hasValidEncryptionKey)
{
// Ignore errors here, as we are just checking for a potential issue
// Only negative effect is that we do not show a potentially helpful warning
Log.WriteWarningMessage(LOGTAG, "EncryptionKeyMissing", null, Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME));
if (!silentConsole)
Console.WriteLine(Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME));
}
}
catch
{
// Ignore errors here, as we are just checking for a potential issue
// Only negative effect is that we do not show a potentially helpful warning
}
if (!hasValidEncryptionKey && !disableDbEncryption)
{
@@ -826,6 +823,11 @@ namespace Duplicati.Server
Console.WriteLine(Strings.Program.BlacklistedEncryptionKey(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION));
}
// If the database is not encrypted, and we are not changing the encryption
// don't pass the key, as that would cause the database to be encrypted
if (!hasEncryptedFields && !changeDbEncryption && hasValidEncryptionKey)
encKey = null;
return new Connection(con, disableDbEncryption, encKey, applicationSettings.DataFolder, applicationSettings.StartOrStopUsageReporter);
}
+1 -1
View File
@@ -109,7 +109,7 @@ namespace Duplicati.UnitTest
}
byte[] jsonByteArray;
using (var con = Program.GetDatabaseConnection(new ApplicationSettings(), advancedOptions, true))
using (var con = Program.GetDatabaseConnection(new ApplicationSettings(), advancedOptions, true, false))
jsonByteArray = BackupImportExportHandler.ExportToJSON(con, backup, null);
// The username should not have the '%40' converted to '@' since the import code