using System.Text.Json; using Duplicati.Library.Encryption; using Duplicati.Library.Main; namespace Duplicati.CommandLine.ServerUtil; /// /// Settings instance for the server utility. /// /// The commandline password /// The saved refresh token /// The host url to connect to /// The server datafolder for password-free connections /// The settings file where data is loaded/saved /// Whether to disable TLS/SSL certificate trust check public sealed record Settings( string? Password, string? RefreshToken, Uri HostUrl, string? ServerDatafolder, string SettingsFile, bool Insecure ) { /// /// The JSON serialized settings for a single host /// /// Encrypted refresh token /// The host url to connect to /// The server datafolder, if any private sealed record PersistedSettings( string? RefreshToken, Uri HostUrl, string? ServerDatafolder ); private static string GetDefaultStorageFolder(string filename) { var folder = DatabaseLocator.GetDefaultStorageFolderWithDebugSupport(filename); if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); return folder; } /// /// Loads the settings from the settings file /// /// The password to use /// The host URL to use /// The server data folder to use /// The settings file to use /// Whether to disable TLS/SSL certificate trust check /// The loaded settings public static Settings Load(string? password, Uri? hostUrl, string? serverDataFolder, string settingsFile, bool insecure) { hostUrl ??= new Uri("http://localhost:8200"); if (string.IsNullOrWhiteSpace(serverDataFolder)) serverDataFolder = GetDefaultStorageFolder("Duplicati-server.sqlite"); if (!string.IsNullOrWhiteSpace(settingsFile) && !Path.IsPathRooted(settingsFile)) settingsFile = Path.Combine(GetDefaultStorageFolder(settingsFile), settingsFile); var persistedSettings = LoadSettings(settingsFile) .FirstOrDefault(x => x.HostUrl == hostUrl); return new Settings( password, persistedSettings?.RefreshToken, hostUrl, serverDataFolder, settingsFile, insecure ); } /// /// Saves the settings to the settings file /// public void Save() { if (!string.IsNullOrWhiteSpace(RefreshToken)) { if (!EncryptedFieldHelper.HasValidDefaultKey) Console.WriteLine("Warning: The encryption key is missing, saving login token without encryption"); else if (EncryptedFieldHelper.IsDefaultKeyBlacklisted) Console.WriteLine("Warning: The current encryption key is blacklisted and cannot be used, saving login token without encryption"); } File.WriteAllText(SettingsFile, JsonSerializer.Serialize(LoadSettings(SettingsFile) .Where(x => x.HostUrl != HostUrl) .Append(new PersistedSettings(RefreshToken, HostUrl, ServerDatafolder)) .Select(x => x with { RefreshToken = string.IsNullOrWhiteSpace(x.RefreshToken) || EncryptedFieldHelper.IsDefaultKeyBlacklisted || !EncryptedFieldHelper.HasValidDefaultKey ? x.RefreshToken : EncryptedFieldHelper.Encrypt(x.RefreshToken) }) )); } /// /// Gets a connection to the server /// /// The connection public Task GetConnection() { return Connection.Connect(this); } /// /// Loads the settings from the settings file /// /// The filename to load /// The loaded settings private static List LoadSettings(string filename) { if (File.Exists(filename)) return (JsonSerializer.Deserialize>(File.ReadAllText(filename)) ?? []) .Select(x => x with { RefreshToken = EncryptedFieldHelper.Decrypt(x.RefreshToken) }) .ToList(); return []; } }