2025-01-14 14:03:48 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
2025-06-27 11:19:24 -03:00
//
// 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
2025-01-14 14:03:48 +01:00
// Software is furnished to do so, subject to the following conditions:
2025-06-27 11:19:24 -03:00
//
// The above copyright notice and this permission notice shall be included in
2025-01-14 14:03:48 +01:00
// all copies or substantial portions of the Software.
2025-06-27 11:19:24 -03:00
//
// 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
2024-03-05 08:55:13 +01:00
// DEALINGS IN THE SOFTWARE.
2024-09-18 11:07:59 +02:00
2024-02-28 15:45:30 +01:00
using System ;
2013-02-12 21:43:14 +00:00
using System.Collections.Generic ;
using System.Linq ;
2024-10-24 15:56:39 +02:00
using System.Threading ;
2024-06-07 15:56:43 +02:00
using System.Threading.Tasks ;
2024-11-25 17:27:50 +01:00
using Duplicati.Library.AutoUpdater ;
2019-02-14 21:50:28 +01:00
using Duplicati.Library.Common.IO ;
2024-11-21 20:23:42 +01:00
using Duplicati.Library.Crashlog ;
2024-08-30 12:45:39 +02:00
using Duplicati.Library.Encryption ;
2024-08-28 22:25:23 +02:00
using Duplicati.Library.Interface ;
2024-12-18 08:27:59 +01:00
using Duplicati.Library.Logging ;
2024-08-23 17:53:17 +02:00
using Duplicati.Library.Main ;
2024-06-19 13:54:10 +02:00
using Duplicati.Library.Main.Database ;
2024-11-05 21:26:30 +01:00
using Duplicati.Library.Utility ;
2024-06-09 22:00:34 +02:00
using Duplicati.Server.Database ;
2022-12-30 10:59:29 -08:00
using Duplicati.WebserverCore ;
2024-03-15 16:51:01 +01:00
using Duplicati.WebserverCore.Abstractions ;
2025-05-05 16:13:46 +02:00
using Duplicati.WebserverCore.Services ;
2024-03-15 16:51:01 +01:00
using Microsoft.Extensions.DependencyInjection ;
2019-02-14 21:50:28 +01:00
2013-02-12 21:43:14 +00:00
namespace Duplicati.Server
{
public class Program
{
2025-03-25 11:13:57 +01:00
private const string PARAMETERS_FILE_OPTION = "parameters-file" ;
private static readonly string [] PARAMETERS_FILE_OPTION_EXTRAS = [ "parameterfile" ];
2024-08-27 16:24:33 +02:00
private const string PING_PONG_KEEPALIVE_OPTION = "ping-pong-keepalive" ;
private const string WINDOWS_EVENTLOG_OPTION = "windows-eventlog" ;
private const string WINDOWS_EVENTLOG_LEVEL_OPTION = "windows-eventlog-level" ;
private const string DISABLE_DB_ENCRYPTION_OPTION = "disable-db-encryption" ;
2024-08-30 12:45:39 +02:00
private const string REQUIRE_DB_ENCRYPTION_KEY_OPTION = "require-db-encryption-key" ;
2024-10-24 15:56:39 +02:00
private const string SETTINGS_ENCRYPTION_KEY_OPTION = "settings-encryption-key" ;
2025-02-06 11:50:38 +01:00
private const string DISABLE_UPDATE_CHECK_OPTION = "disable-update-check" ;
2025-05-16 11:42:50 +02:00
private const string REGISTER_REMOTE_CONTROL_OPTION = "register-remote-control" ;
private const string REGISTER_REMOTE_CONTROL_REREGISTER_OPTION = "register-remote-control-force" ;
2025-10-17 10:01:31 +02:00
private const string ALLOWED_BACKEND_MODULES = "allowed-backend-modules" ;
private const string ALLOWED_ENCRYPTION_MODULES = "allowed-encryption-modules" ;
private const string ALLOWED_COMPRESSION_MODULES = "allowed-compression-modules" ;
2025-02-09 10:28:56 +01:00
private const string LOG_FILE_OPTION = "log-file" ;
private const string LOG_LEVEL_OPTION = "log-level" ;
private const string LOG_CONSOLE_OPTION = "log-console" ;
2025-03-25 11:13:57 +01:00
private const string TEMPDIR_OPTION = "tempdir" ;
private const string LOG_RETENTION_OPTION = "log-retention" ;
private const string HELP_OPTION = "help" ;
2025-02-09 10:28:56 +01:00
2024-08-27 16:24:33 +02:00
2024-08-13 12:36:18 +02:00
#if DEBUG
private const bool DEBUG_MODE = true ;
#else
private const bool DEBUG_MODE = false ;
#endif
2019-02-09 16:06:40 +01:00
2025-03-25 11:36:44 +01:00
/// <summary>
/// Options to be ignored when validating the command line
/// </summary>
public static readonly HashSet < string > ValidationIgnoredOptions = new HashSet < string >( StringComparer . OrdinalIgnoreCase );
2018-03-12 14:07:11 +01:00
/// <summary>
/// The log tag for messages from this class
/// </summary>
2019-02-28 17:58:26 +01:00
private static readonly string LOGTAG = Library . Logging . Log . LogTagFromType < Program >();
2013-02-12 21:43:14 +00:00
/// <summary>
/// The path to the directory that contains the main executable
/// </summary>
2024-03-15 14:18:56 +01:00
public static readonly string StartupPath = Duplicati . Library . AutoUpdater . UpdaterManager . INSTALLATIONDIR ;
2013-02-12 21:43:14 +00:00
2024-08-14 21:20:49 +02:00
/// <summary>
/// The environment variable prefix
/// </summary>
2025-01-28 13:01:35 +01:00
private static readonly string ENV_NAME_PREFIX = AutoUpdateSettings . AppName . ToUpperInvariant ();
2013-02-12 21:43:14 +00:00
2014-03-21 10:34:34 +01:00
/// <summary>
/// The single instance
/// </summary>
2019-02-10 19:58:56 +01:00
public static SingleInstance ApplicationInstance = null ;
2018-05-22 21:49:48 +02:00
2014-07-25 14:09:26 +02:00
/// <summary>
/// The thread running the ping-pong handler
/// </summary>
2025-05-05 16:13:46 +02:00
private static Thread PingPongThread ;
2013-02-12 21:43:14 +00:00
/// <summary>
/// The controller interface for pause/resume and throttle options
/// </summary>
2024-06-07 15:56:43 +02:00
public static LiveControls LiveControl { get => DuplicatiWebserver . Provider . GetRequiredService < LiveControls >(); }
2013-02-12 21:43:14 +00:00
2024-03-15 16:51:01 +01:00
/// <summary>
/// Duplicati webserver instance
/// </summary>
public static DuplicatiWebserver DuplicatiWebserver { get ; set ; }
2022-12-30 10:59:29 -08:00
/// <summary>
/// Callback to shutdown the modern webserver
/// </summary>
2024-03-15 16:51:01 +01:00
private static void ShutdownModernWebserver ()
{
DuplicatiWebserver . Stop (). GetAwaiter (). GetResult ();
}
2022-12-30 10:59:29 -08:00
2013-02-12 21:43:14 +00:00
/// <summary>
/// An event that is set once the server is ready to respond to requests
/// </summary>
2025-05-03 15:54:23 +02:00
public static readonly ManualResetEvent ServerStartedEvent = new ManualResetEvent ( false );
2018-05-22 21:49:48 +02:00
2025-05-05 16:13:46 +02:00
/// <summary>
/// Timer for purging temp files and log data
/// </summary>
2014-08-06 10:30:46 +02:00
private static System . Threading . Timer PurgeTempFilesTimer = null ;
2013-02-12 21:43:14 +00:00
/// <summary>
/// The main entry point for the application.
/// </summary>
2025-05-05 16:13:46 +02:00
/// <param name="_args"> The command line arguments</param>
2013-02-12 21:43:14 +00:00
[STAThread]
2024-03-15 14:18:56 +01:00
public static int Main ( string [] _args )
2025-05-05 16:13:46 +02:00
=> Main ( null , _args );
/// <summary>
/// The main entry point for the application.
/// </summary>
/// <param name="applicationSettings"> The application settings</param>
/// <param name="_args"> The command line arguments</param>
[STAThread]
public static int Main ( IApplicationSettings applicationSettings , string [] _args )
2014-06-26 22:37:58 +02:00
{
2025-03-25 11:13:57 +01:00
PreloadSettingsLoader . ConfigurePreloadSettings ( ref _args , PackageHelper . NamedExecutable . Server , out var preloadDbSettings );
2024-08-28 00:18:55 +02:00
2025-05-05 16:13:46 +02:00
applicationSettings ??= new ApplicationSettings ();
2013-02-27 14:25:14 +00:00
//If this executable is invoked directly, write to console, otherwise throw exceptions
2025-05-05 16:13:46 +02:00
var writeToConsoleOnException = applicationSettings . Origin == "Server" ;
2024-09-09 11:25:39 +02:00
// Prepared for the future, where we might want to have a silent console mode
var silentConsole = false ;
var logMessageToConsole = ( string message ) => { if (! silentConsole ) Console . WriteLine ( message ); };
2018-02-01 09:48:00 +01:00
2013-02-12 21:43:14 +00:00
//Find commandline options here for handling special startup cases
2018-02-01 09:48:00 +01:00
var args = new List < string >( _args );
2025-03-25 11:13:57 +01:00
var optionsWithFilter = FilterCollector . ExtractOptions ( new List < string >( args ));
2019-02-25 12:34:32 +01:00
var commandlineOptions = optionsWithFilter . Item1 ;
var filter = optionsWithFilter . Item2 ;
2013-02-12 21:43:14 +00:00
2025-01-21 12:45:53 -03:00
if ( HelpOptionExtensions . IsArgumentAnyHelpString ( args ))
2019-02-07 21:57:36 +01:00
{
2024-09-09 11:25:39 +02:00
return ShowHelp ( writeToConsoleOnException );
2019-02-07 21:57:36 +01:00
}
2018-05-22 21:49:48 +02:00
if ( commandlineOptions . ContainsKey ( "tempdir" ) && ! string . IsNullOrEmpty ( commandlineOptions [ "tempdir" ]))
2019-02-09 16:06:40 +01:00
{
2025-03-25 11:13:57 +01:00
SystemContextSettings . DefaultTempPath = commandlineOptions [ "tempdir" ];
2019-02-09 16:06:40 +01:00
}
2019-02-09 16:39:40 +01:00
2025-03-25 11:13:57 +01:00
SystemContextSettings . StartSession ();
2018-05-22 21:49:48 +02:00
2024-08-14 21:20:49 +02:00
ApplyEnvironmentVariables ( commandlineOptions );
2025-05-05 16:13:46 +02:00
ApplySecretProvider ( applicationSettings , commandlineOptions , CancellationToken . None ). Await ();
2024-08-14 21:20:49 +02:00
2025-03-25 11:13:57 +01:00
var parameterFileOption = PARAMETERS_FILE_OPTION_EXTRAS . Prepend ( PARAMETERS_FILE_OPTION )
. FirstOrDefault ( x => commandlineOptions . ContainsKey ( x ));
2019-02-09 16:06:40 +01:00
if ( parameterFileOption != null && ! string . IsNullOrEmpty ( commandlineOptions [ parameterFileOption ]))
2013-02-12 21:43:14 +00:00
{
2019-05-11 11:02:37 -07:00
string filename = commandlineOptions [ parameterFileOption ];
2019-02-09 16:06:40 +01:00
commandlineOptions . Remove ( parameterFileOption );
2019-05-11 11:02:37 -07:00
if (! ReadOptionsFromFile ( filename , ref filter , args , commandlineOptions ))
2019-02-09 16:06:40 +01:00
return 100 ;
2013-02-12 21:43:14 +00:00
}
2025-05-05 13:41:28 +02:00
var logHandler = new LogWriteHandler ();
using var logScope = ConfigureLogging ( logHandler , commandlineOptions );
2018-05-22 21:49:48 +02:00
2025-03-25 11:13:57 +01:00
// Validate after logging is configured
2025-03-25 11:36:44 +01:00
CommandLineArgumentValidator . ValidateArguments ( SupportedCommands , commandlineOptions , KnownDuplicateOptions , ValidationIgnoredOptions );
2025-03-25 11:13:57 +01:00
2025-10-17 10:01:31 +02:00
// Unload any modules that are not allowed
Library . DynamicLoader . BackendLoader . OnlyAllowBackends ( commandlineOptions . GetValueOrDefault ( ALLOWED_BACKEND_MODULES ));
Library . DynamicLoader . EncryptionLoader . OnlyAllowModules ( commandlineOptions . GetValueOrDefault ( ALLOWED_ENCRYPTION_MODULES ));
Library . DynamicLoader . CompressionLoader . OnlyAllowModules ( commandlineOptions . GetValueOrDefault ( ALLOWED_COMPRESSION_MODULES ));
2024-08-20 21:11:59 +02:00
var crashed = false ;
2024-08-27 11:33:52 +02:00
var terminated = false ;
2025-05-03 14:58:07 +02:00
IQueueRunnerService queueRunner = null ;
2025-05-03 15:54:23 +02:00
UpdatePollThread updatePollThread = null ;
EventPollNotify eventPollNotify = null ;
ISchedulerService scheduler = null ;
2013-02-12 21:43:14 +00:00
try
{
2025-07-10 21:03:16 +02:00
var connection = GetDatabaseConnection ( applicationSettings , commandlineOptions , silentConsole , true );
2013-02-12 21:43:14 +00:00
2025-05-05 16:13:46 +02:00
if (! connection . ApplicationSettings . FixedInvalidBackupId )
connection . FixInvalidBackupId ();
2013-02-12 21:43:14 +00:00
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . UpgradePasswordToKBDF ();
CreateApplicationInstance ( applicationSettings . DataFolder , writeToConsoleOnException );
2015-02-15 22:54:49 +01:00
2025-05-05 16:13:46 +02:00
applicationSettings . StartOrStopUsageReporter = () => StartOrStopUsageReporter ( connection );
applicationSettings . StartOrStopUsageReporter ?. Invoke ();
2016-03-12 00:29:51 +01:00
2025-05-05 16:13:46 +02:00
AdjustApplicationSettings ( connection , commandlineOptions );
2018-05-22 21:49:48 +02:00
2025-03-25 11:13:57 +01:00
UpdaterManager . OnError += obj =>
2014-07-25 14:09:26 +02:00
{
2025-05-05 16:13:46 +02:00
connection . LogError ( null , "Error in updater" , obj );
2014-07-01 23:59:30 +02:00
};
2024-06-07 15:56:43 +02:00
2025-05-05 16:13:46 +02:00
DuplicatiWebserver = StartWebServer ( commandlineOptions , connection , logHandler , applicationSettings ). Await ();
2024-06-07 15:56:43 +02:00
2025-05-05 16:13:46 +02:00
connection . SetServiceProvider ( DuplicatiWebserver . Provider );
2025-05-03 15:54:23 +02:00
queueRunner = DuplicatiWebserver . Provider . GetRequiredService < IQueueRunnerService >();
updatePollThread = DuplicatiWebserver . Provider . GetRequiredService < UpdatePollThread >();
eventPollNotify = DuplicatiWebserver . Provider . GetRequiredService < EventPollNotify >();
scheduler = DuplicatiWebserver . Provider . GetRequiredService < ISchedulerService >();
2025-05-03 14:58:07 +02:00
2025-05-03 15:54:23 +02:00
updatePollThread . Init ( Library . Utility . Utility . ParseBoolOption ( commandlineOptions , DISABLE_UPDATE_CHECK_OPTION ));
2019-08-31 13:22:45 -04:00
2025-05-05 16:13:46 +02:00
SetPurgeTempFilesTimer ( connection , commandlineOptions );
2018-05-22 21:49:48 +02:00
2025-10-15 17:58:15 +02:00
LiveControl . StateChanged = ( e ) => { LiveControl_StateChanged ( queueRunner , connection , eventPollNotify , scheduler , e ); };
2013-02-12 21:43:14 +00:00
2024-08-27 16:24:33 +02:00
if ( Library . Utility . Utility . ParseBoolOption ( commandlineOptions , PING_PONG_KEEPALIVE_OPTION ))
2014-07-25 14:09:26 +02:00
{
2025-05-05 16:13:46 +02:00
PingPongThread = new Thread (() => PingPongMethod ( applicationSettings )) { IsBackground = true };
2019-02-28 17:51:52 +01:00
PingPongThread . Start ();
2014-07-25 14:09:26 +02:00
}
2025-05-05 16:13:46 +02:00
connection . ReWriteAllFieldsIfEncryptionChanged ();
connection . SetPreloadSettingsIfChanged ( preloadDbSettings );
EmitWarningsForConfigurationIssues ( connection , applicationSettings , commandlineOptions );
2024-08-20 17:10:34 +02:00
2025-03-25 11:13:57 +01:00
Log . WriteInformationMessage ( LOGTAG , "ServerStarted" , Strings . Program . ServerStarted ( DuplicatiWebserver . Interface , DuplicatiWebserver . Port ));
2025-02-14 11:28:22 +01:00
logMessageToConsole ( Strings . Program . ServerStarted ( DuplicatiWebserver . Interface , DuplicatiWebserver . Port ));
2024-08-27 16:24:33 +02:00
2025-05-16 11:42:50 +02:00
var remoteControlUrl = commandlineOptions . GetValueOrDefault ( REGISTER_REMOTE_CONTROL_OPTION );
if (! string . IsNullOrWhiteSpace ( remoteControlUrl ))
2025-05-16 13:31:34 +02:00
RegisterForRemoteControl ( DuplicatiWebserver . Provider . GetRequiredService < IRemoteControllerRegistration >(), DuplicatiWebserver . Provider . GetRequiredService < IRemoteController >(), remoteControlUrl , Library . Utility . Utility . ParseBoolOption ( commandlineOptions , REGISTER_REMOTE_CONTROL_REREGISTER_OPTION ), logMessageToConsole );
2025-05-16 11:42:50 +02:00
2025-05-05 16:13:46 +02:00
if ( applicationSettings . Origin == "Server" && connection . ApplicationSettings . AutogeneratedPassphrase )
2024-08-27 16:24:33 +02:00
{
var signinToken = DuplicatiWebserver . Provider . GetRequiredService < IJWTTokenProvider >(). CreateSigninToken ( "server-cli" );
2025-05-05 16:13:46 +02:00
var hostname = ( connection . ApplicationSettings . AllowedHostnames ?? string . Empty ). Split ( new char [] { ';' }, StringSplitOptions . RemoveEmptyEntries ). FirstOrDefault ( x => x != "*" ) ?? "localhost" ;
var scheme = connection . ApplicationSettings . UseHTTPS ? "https" : "http" ;
2024-08-27 16:24:33 +02:00
2024-10-29 11:15:06 +01:00
var url = $"{scheme}://{hostname}:{DuplicatiWebserver.Port}/signin.html?token={signinToken}" ;
2025-03-25 11:13:57 +01:00
Log . WriteWarningMessage ( LOGTAG , "ServerStartedSignin" , null , Strings . Program . ServerStartedSignin ( url ));
2024-09-09 11:25:39 +02:00
logMessageToConsole ( Strings . Program . ServerStartedSignin ( url ));
2024-08-27 16:24:33 +02:00
}
2024-08-20 17:10:34 +02:00
2024-08-27 11:33:52 +02:00
DuplicatiWebserver . TerminationTask . ContinueWith (( t ) =>
{
if ( t . Exception != null )
2024-08-27 18:22:53 +02:00
{
2025-03-25 11:13:57 +01:00
Log . WriteWarningMessage ( LOGTAG , "ServerCrashed" , t . Exception , Strings . Program . ServerCrashed ( t . Exception . Message ));
2024-09-09 11:25:39 +02:00
logMessageToConsole ( Strings . Program . ServerStartedSignin ( Strings . Program . ServerCrashed ( t . Exception . ToString ())));
2024-08-27 18:22:53 +02:00
}
2024-08-27 11:33:52 +02:00
terminated = true ;
2025-08-07 11:50:01 +02:00
applicationSettings . SignalApplicationExit ();
2024-08-27 11:33:52 +02:00
});
2025-02-09 10:28:44 +01:00
var stopCounter = 0 ;
Console . CancelKeyPress += ( sender , e ) =>
{
if ( Interlocked . Increment ( ref stopCounter ) <= 1 )
{
Log . WriteInformationMessage ( LOGTAG , "CancelKeyPressed" , "Cancel key pressed, stopping server" );
Task . Run (() => DuplicatiWebserver ?. Stop ());
}
else
{
Log . WriteWarningMessage ( LOGTAG , "CancelKeyPressed" , null , "Cancel key pressed twice, terminating now" );
2025-02-09 11:01:21 +01:00
if ( OperatingSystem . IsWindows ())
Environment . FailFast ( "Cancel key pressed twice, terminating now" );
else
Environment . Exit ( 0 );
2025-02-09 10:28:44 +01:00
}
};
2013-02-12 21:43:14 +00:00
ServerStartedEvent . Set ();
2025-08-07 11:50:01 +02:00
applicationSettings . ApplicationExit . WaitHandle . WaitOne ();
2013-02-12 21:43:14 +00:00
}
2014-03-21 10:34:34 +01:00
catch ( SingleInstance . MultipleInstanceException mex )
{
2024-08-20 21:11:59 +02:00
crashed = true ;
2025-01-27 22:42:40 +01:00
Log . WriteErrorMessage ( LOGTAG , "MultipleInstanceError" , mex , Strings . Program . ServerCrashed ( mex . Message ));
2015-01-20 21:07:24 +01:00
System . Diagnostics . Trace . WriteLine ( Strings . Program . SeriousError ( mex . ToString ()));
2024-09-09 11:25:39 +02:00
if (! writeToConsoleOnException ) throw ;
2024-06-07 15:56:43 +02:00
2019-02-25 12:34:32 +01:00
Console . WriteLine ( Strings . Program . SeriousError ( mex . ToString ()));
return 100 ;
2014-03-21 10:34:34 +01:00
}
2013-02-12 21:43:14 +00:00
catch ( Exception ex )
{
2024-08-20 21:11:59 +02:00
crashed = true ;
2025-01-27 22:42:40 +01:00
Log . WriteErrorMessage ( LOGTAG , "ServerCrashed" , ex , Strings . Program . ServerCrashed ( ex . Message ));
2015-01-20 21:07:24 +01:00
System . Diagnostics . Trace . WriteLine ( Strings . Program . SeriousError ( ex . ToString ()));
2024-09-09 11:25:39 +02:00
if ( writeToConsoleOnException )
2017-10-11 12:49:14 +02:00
{
2015-01-20 21:07:24 +01:00
Console . WriteLine ( Strings . Program . SeriousError ( ex . ToString ()));
2017-10-11 12:49:14 +02:00
return 100 ;
}
2013-02-27 14:25:14 +00:00
else
2015-01-20 21:07:24 +01:00
throw new Exception ( Strings . Program . SeriousError ( ex . ToString ()), ex );
2013-02-12 21:43:14 +00:00
}
2013-02-27 14:25:14 +00:00
finally
{
2025-01-27 22:42:40 +01:00
Log . WriteInformationMessage ( LOGTAG , "ServerStopping" , Strings . Program . ServerStopping );
2024-12-20 16:09:42 +01:00
2024-08-20 21:11:59 +02:00
var steps = new Action [] {
2025-05-03 15:54:23 +02:00
() => eventPollNotify ?. SignalNewEvent (),
() => ShutdownModernWebserver (),
() => updatePollThread ?. Terminate (),
() => scheduler ?. Terminate ( true ),
2025-05-03 14:58:07 +02:00
() => queueRunner ?. Terminate ( true ),
2024-08-20 21:11:59 +02:00
() => ApplicationInstance ?. Dispose (),
() => PurgeTempFilesTimer ?. Dispose (),
() => Library . UsageReporter . Reporter . ShutDown (),
() => PingPongThread ?. Interrupt (),
2024-12-20 16:09:42 +01:00
() =>
{
2025-05-05 13:41:28 +02:00
Log . WriteInformationMessage ( LOGTAG , "ServerStopped" , Strings . Program . ServerStopped );
logHandler ?. Dispose ();
2024-12-20 16:09:42 +01:00
}
2024-08-20 21:11:59 +02:00
};
2025-06-27 11:19:24 -03:00
2024-08-20 21:11:59 +02:00
foreach ( var teardownStep in steps )
{
try
{
teardownStep ();
}
catch ( Exception ex )
{
// If the server is already crashed, that is the main error
// If the server crashes during teardown, we log that as an error
2024-08-27 11:33:52 +02:00
if (!( crashed || terminated ))
2024-08-20 21:11:59 +02:00
{
System . Diagnostics . Trace . WriteLine ( Strings . Program . TearDownError ( ex . ToString ()));
2024-09-09 11:25:39 +02:00
logMessageToConsole ( Strings . Program . TearDownError ( ex . ToString ()));
2024-08-20 21:11:59 +02:00
}
}
}
2013-02-27 14:25:14 +00:00
}
2017-10-11 12:49:14 +02:00
return 0 ;
2013-02-12 21:43:14 +00:00
}
2025-05-05 16:13:46 +02:00
private static async Task < DuplicatiWebserver > StartWebServer ( IReadOnlyDictionary < string , string > options , Connection connection , ILogWriteHandler logWriteHandler , IApplicationSettings applicationSettings )
2019-02-28 17:51:52 +01:00
{
2024-06-09 22:00:34 +02:00
var server = await WebServerLoader . TryRunServer ( options , connection , async parsedOptions =>
2024-06-07 15:56:43 +02:00
{
var mappedSettings = new DuplicatiWebserver . InitSettings (
parsedOptions . WebRoot ,
parsedOptions . Port ,
parsedOptions . Interface ,
2024-10-29 13:55:12 +01:00
parsedOptions . Certificate ,
2024-09-18 11:07:59 +02:00
parsedOptions . AllowedHostnames ,
2024-10-03 14:49:07 +02:00
parsedOptions . DisableStaticFiles ,
2025-06-29 16:57:47 +02:00
parsedOptions . TokenLifetimeInMinutes ,
2025-01-18 11:03:52 +01:00
parsedOptions . SPAPaths ,
2025-03-28 13:47:27 +01:00
parsedOptions . CorsOrigins ,
parsedOptions . PreAuthTokens
2024-10-29 13:55:12 +01:00
);
2024-06-07 15:56:43 +02:00
2025-05-05 16:13:46 +02:00
var server = DuplicatiWebserver . CreateWebServer ( mappedSettings , connection , logWriteHandler , applicationSettings );
2024-06-20 14:27:09 +02:00
// Start the server, but catch any configuration issues
2025-01-14 14:03:48 +01:00
var task = server . Start ();
2024-06-20 14:27:09 +02:00
await Task . WhenAny ( task , Task . Delay ( 500 ));
if ( task . IsCompleted )
await task ;
2024-06-07 15:56:43 +02:00
return server ;
}). ConfigureAwait ( false );
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . ServerPortChanged |= server . Port != connection . ApplicationSettings . LastWebserverPort ;
connection . ApplicationSettings . LastWebserverPort = server . Port ;
2024-06-07 15:56:43 +02:00
return server ;
2019-02-28 17:51:52 +01:00
}
2025-05-05 16:13:46 +02:00
private static void SetPurgeTempFilesTimer ( Connection connection , Dictionary < string , string > commandlineOptions )
2019-02-14 21:50:28 +01:00
{
var lastPurge = new DateTime ( 0 );
2025-03-25 11:13:57 +01:00
TimerCallback purgeTempFilesCallback = ( x ) =>
2019-02-14 21:50:28 +01:00
{
try
{
2024-08-28 12:52:33 +02:00
if ( Math . Abs (( DateTime . Now - lastPurge ). TotalHours ) < ( DEBUG_MODE ? 1 : 23 ))
2019-08-31 13:22:45 -04:00
{
return ;
}
2019-02-14 21:50:28 +01:00
lastPurge = DateTime . Now ;
2025-05-05 16:13:46 +02:00
foreach ( var e in connection . GetTempFiles (). Where (( f ) => f . Expires < DateTime . Now ))
2019-02-14 21:50:28 +01:00
{
try
{
if ( System . IO . File . Exists ( e . Path ))
System . IO . File . Delete ( e . Path );
}
catch ( Exception ex )
{
2025-05-05 16:13:46 +02:00
connection . LogError ( null , $"Failed to delete temp file: {e.Path}" , ex );
2019-02-14 21:50:28 +01:00
}
2025-05-05 16:13:46 +02:00
connection . DeleteTempFile ( e . ID );
2019-02-14 21:50:28 +01:00
}
Library . Utility . TempFile . RemoveOldApplicationTempFiles (( path , ex ) =>
{
2025-05-05 16:13:46 +02:00
connection . LogError ( null , $"Failed to delete temp file: {path}" , ex );
2019-02-14 21:50:28 +01:00
});
if (! commandlineOptions . TryGetValue ( "log-retention" , out string pts ))
{
pts = DEFAULT_LOG_RETENTION ;
}
2025-05-05 16:13:46 +02:00
connection . PurgeLogData ( Library . Utility . Timeparser . ParseTimeInterval ( pts , DateTime . Now , true ));
2019-02-14 21:50:28 +01:00
}
catch ( Exception ex )
{
2025-05-05 16:13:46 +02:00
connection . LogError ( null , "Failed during temp file cleanup" , ex );
2019-02-14 21:50:28 +01:00
}
};
2024-06-19 13:54:10 +02:00
PurgeTempFilesTimer =
2024-08-28 12:52:33 +02:00
new System . Threading . Timer ( purgeTempFilesCallback , null ,
DEBUG_MODE ? TimeSpan . FromSeconds ( 10 ) : TimeSpan . FromHours ( 1 ),
DEBUG_MODE ? TimeSpan . FromHours ( 1 ) : TimeSpan . FromDays ( 1 ));
2019-02-14 21:50:28 +01:00
}
2025-05-16 13:31:34 +02:00
private static void RegisterForRemoteControl ( IRemoteControllerRegistration remoteControllerRegistration , IRemoteController remoteController , string remoteControlUrl , bool reRegister , Action < string > logMessageToConsole )
2025-05-16 11:42:50 +02:00
{
if ( remoteController . CanEnable )
{
if (! reRegister )
{
Log . WriteInformationMessage ( LOGTAG , "RemoteControlAlreadyRegistered" , Strings . Program . RemoteControlAlreadyRegistered );
return ;
}
remoteController . DeleteRegistration ();
}
Task . Run ( async () =>
{
try
{
var regTask = remoteControllerRegistration . RegisterMachine ( remoteControlUrl );
while (! regTask . IsCompleted )
{
// Interface does not have events, so poll it every second
await Task . WhenAny ( Task . Delay ( 1000 ), regTask ). ConfigureAwait ( false );
if (! string . IsNullOrWhiteSpace ( remoteControllerRegistration . RegistrationUrl ))
{
Log . WriteWarningMessage ( LOGTAG , "RemoteControlRegistrationUrl" , null , Strings . Program . RemoteControlRegistrationUrl ( remoteControllerRegistration . RegistrationUrl ));
2025-05-16 13:31:34 +02:00
logMessageToConsole ( Strings . Program . RemoteControlRegistrationUrl ( remoteControllerRegistration . RegistrationUrl ));
2025-05-16 11:42:50 +02:00
break ;
}
}
// Await the registration task and ensure any errors are logged
await regTask . ConfigureAwait ( false );
}
catch ( Exception ex )
{
Log . WriteErrorMessage ( LOGTAG , "RemoteControlRegistrationFailed" , ex , Strings . Program . RemoteControlRegistrationFailed ( ex . Message ));
}
});
}
2025-05-05 16:13:46 +02:00
private static void AdjustApplicationSettings ( Connection connection , Dictionary < string , string > commandlineOptions )
2019-02-13 22:10:00 +01:00
{
2024-06-19 13:54:10 +02:00
// This clears the JWT config, and a new will be generated, invalidating all existing tokens
if ( Library . Utility . Utility . ParseBoolOption ( commandlineOptions , WebServerLoader . OPTION_WEBSERVICE_RESET_JWT_CONFIG ))
{
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . JWTConfig = null ;
2024-06-19 13:54:10 +02:00
// Clean up stored tokens as they are now invalid
2025-05-05 16:13:46 +02:00
connection . ExecuteWithCommand (( con ) => con . ExecuteNonQuery ( "DELETE FROM TokenFamily" ));
2024-06-19 13:54:10 +02:00
}
2024-12-12 08:59:59 +01:00
if ( Library . Utility . Utility . ParseBoolOption ( commandlineOptions , WebServerLoader . OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN ))
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . EnableForeverTokens ();
2024-12-12 08:59:59 +01:00
2024-09-18 11:07:59 +02:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS ))
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . DisableSigninTokens = Library . Utility . Utility . ParseBool ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS ], true );
2024-09-18 11:07:59 +02:00
2025-05-28 12:47:42 +02:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS ))
connection . ApplicationSettings . DisabledAPIExtensions = commandlineOptions . GetValueOrDefault ( WebServerLoader . OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS )?
. Split ( new [] { ',' , ';' }, StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries )
. ToHashSet ( StringComparer . OrdinalIgnoreCase ) ?? [];
2024-06-07 15:56:43 +02:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_PASSWORD ))
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . SetWebserverPassword ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_PASSWORD ]);
2019-02-13 22:10:00 +01:00
2024-06-07 15:56:43 +02:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES ))
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . SetAllowedHostnames ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES ]);
2024-08-04 16:31:16 +02:00
else if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT ))
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . SetAllowedHostnames ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT ]);
2024-11-05 21:26:30 +01:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_TIMEZONE ) && ! string . IsNullOrEmpty ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_TIMEZONE ]))
try
{
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . Timezone = TimeZoneHelper . FindTimeZone ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_TIMEZONE ]);
2024-11-05 21:26:30 +01:00
}
catch ( Exception ex )
{
throw new UserInformationException ( Strings . Program . InvalidTimezone ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_TIMEZONE ]), "InvalidTimeZone" , ex );
}
2024-11-25 17:27:50 +01:00
// The database has recorded a new version
2025-05-05 16:13:46 +02:00
if ( connection . ApplicationSettings . UpdatedVersion != null )
2024-11-25 17:27:50 +01:00
{
// Check if the running version is newer than the recorded version
2025-05-05 16:13:46 +02:00
if ( UpdaterManager . TryParseVersion ( connection . ApplicationSettings . UpdatedVersion . Version ) <= UpdaterManager . TryParseVersion ( UpdaterManager . SelfVersion . Version ))
2024-11-25 17:27:50 +01:00
{
// Clean up lingering update notifications
2025-05-05 16:13:46 +02:00
var updateNotifications = connection . GetNotifications (). Where ( x => x . Action == "update:new" ). ToList ();
2024-11-25 17:27:50 +01:00
foreach ( var n in updateNotifications )
2025-05-05 16:13:46 +02:00
connection . DismissNotification ( n . ID );
2024-11-25 17:27:50 +01:00
// Clear up the recorded version
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . UpdatedVersion = null ;
2024-11-25 17:27:50 +01:00
}
}
2025-01-27 08:52:06 +01:00
}
2025-05-05 16:13:46 +02:00
private static void EmitWarningsForConfigurationIssues ( Connection connection , IApplicationSettings applicationSettings , Dictionary < string , string > commandlineOptions )
2025-01-27 08:52:06 +01:00
{
2025-05-05 16:13:46 +02:00
if ( connection . ApplicationSettings . LastConfigIssueCheckVersion != UpdaterManager . SelfVersion . Version )
2025-01-27 08:52:06 +01:00
{
2025-05-05 16:13:46 +02:00
var updateNotifications = connection . GetNotifications (). Where ( x => x . Action . StartsWith ( "config:issue:" )). ToList ();
2025-01-27 08:52:06 +01:00
foreach ( var n in updateNotifications )
2025-05-05 16:13:46 +02:00
connection . DismissNotification ( n . ID );
2025-01-27 08:52:06 +01:00
2025-05-05 16:13:46 +02:00
if (! connection . IsEncryptingFields && ! Library . Utility . Utility . ParseBoolOption ( commandlineOptions , DISABLE_DB_ENCRYPTION_OPTION ))
2025-01-27 08:52:06 +01:00
{
2025-05-05 16:13:46 +02:00
connection . RegisterNotification (
2025-01-27 08:52:06 +01:00
Serialization . NotificationType . Warning ,
"Unencrypted database" ,
"The database is not encrypted. This is a security risk and should be fixed as soon as possible." ,
null ,
null ,
"config:issue:unencrypted-database" ,
null ,
"UnencryptedDatabase" ,
null ,
( self , all ) =>
{
return all . FirstOrDefault ( x => x . Action == "config:issue:unencrypted-database" ) ?? self ;
}
);
}
2024-11-25 17:27:50 +01:00
2025-05-05 16:13:46 +02:00
if ( OperatingSystem . IsWindows () && applicationSettings . DataFolder . StartsWith ( Util . AppendDirSeparator ( Environment . GetFolderPath ( Environment . SpecialFolder . Windows )), StringComparison . OrdinalIgnoreCase ))
2025-01-27 11:49:17 +01:00
{
2025-05-05 16:13:46 +02:00
connection . RegisterNotification (
2025-01-27 11:49:17 +01:00
Serialization . NotificationType . Warning ,
"Incorrect storage folder" ,
2025-09-16 09:51:44 +02:00
"The server configuration is stored inside the Windows folder. Please move the configuration to a different location, or it may be deleted on Windows version upgrades." ,
2025-01-27 11:49:17 +01:00
null ,
null ,
"config:issue:windows-folder-used" ,
null ,
"UnencryptedDatabase" ,
null ,
( self , all ) =>
{
return all . FirstOrDefault ( x => x . Action == "config:issue:windows-folder-used" ) ?? self ;
}
);
}
2025-05-05 16:13:46 +02:00
connection . ApplicationSettings . LastConfigIssueCheckVersion = UpdaterManager . SelfVersion . Version ;
2025-01-27 08:52:06 +01:00
}
2019-02-13 22:10:00 +01:00
}
2019-02-12 21:59:43 +01:00
2025-05-05 16:13:46 +02:00
private static void CreateApplicationInstance ( string dataFolder , bool writeToConsoleOnExceptionw )
2019-02-12 21:59:43 +01:00
{
try
{
//This will also create DATAFOLDER if it does not exist
2025-05-05 16:13:46 +02:00
ApplicationInstance = new SingleInstance ( dataFolder );
2019-02-12 21:59:43 +01:00
}
catch ( Exception ex )
{
2024-09-09 11:25:39 +02:00
if ( writeToConsoleOnExceptionw )
2019-02-12 21:59:43 +01:00
{
Console . WriteLine ( Strings . Program . StartupFailure ( ex ));
Environment . Exit ( 200 );
}
throw new Exception ( Strings . Program . StartupFailure ( ex ));
}
if (! ApplicationInstance . IsFirstInstance )
{
2024-09-09 11:25:39 +02:00
if ( writeToConsoleOnExceptionw )
2019-02-12 21:59:43 +01:00
{
Console . WriteLine ( Strings . Program . AnotherInstanceDetected );
Environment . Exit ( 200 );
}
throw new SingleInstance . MultipleInstanceException ( Strings . Program . AnotherInstanceDetected );
}
}
2024-08-14 21:20:49 +02:00
private static void ApplyEnvironmentVariables ( Dictionary < string , string > commandlineOptions )
{
foreach ( var key in SupportedCommands . SelectMany ( x => ( x . Aliases ?? []). Prepend ( x . Name )). Distinct ())
{
// Commandline options take precedence
if ( commandlineOptions . ContainsKey ( key ))
continue ;
var envkey = $"{ENV_NAME_PREFIX}__{key.Replace('-', '_').ToUpperInvariant()}" ;
var envval = Environment . GetEnvironmentVariable ( envkey );
if (! string . IsNullOrWhiteSpace ( envval ))
commandlineOptions [ key ] = envval ;
}
2024-10-24 15:56:39 +02:00
// Set the encryption key from the environment variable
if (! string . IsNullOrWhiteSpace ( Environment . GetEnvironmentVariable ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME )) && string . IsNullOrWhiteSpace ( commandlineOptions . GetValueOrDefault ( SETTINGS_ENCRYPTION_KEY_OPTION )))
commandlineOptions [ SETTINGS_ENCRYPTION_KEY_OPTION ] = Environment . GetEnvironmentVariable ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME );
2024-08-14 21:20:49 +02:00
}
2025-05-05 16:13:46 +02:00
private static async Task ApplySecretProvider ( IApplicationSettings applicationSettings , Dictionary < string , string > commandlineOptions , CancellationToken cancellationToken )
=> applicationSettings . SecretProvider = await SecretProviderHelper . ApplySecretProviderAsync ([], [], commandlineOptions , TempFolder . SystemTempPath , applicationSettings . SecretProvider , cancellationToken ). ConfigureAwait ( false );
2024-10-24 15:56:39 +02:00
2025-02-09 10:28:56 +01:00
private class ConsoleLogDestination ( LogMessageType level ) : ILogDestination
{
public void WriteMessage ( LogEntry entry )
{
if ( entry . Level >= level )
Console . WriteLine ( entry . AsString ( true ));
}
}
2025-05-05 13:41:28 +02:00
private static IDisposable ConfigureLogging ( ILogWriteHandler logWriteHandler , Dictionary < string , string > commandlineOptions )
2019-02-09 16:39:40 +01:00
{
2025-05-05 13:41:28 +02:00
IDisposable logScope ;
2019-02-09 16:39:40 +01:00
//Log various information in the logfile
2025-02-09 10:28:56 +01:00
if ( DEBUG_MODE && ! commandlineOptions . ContainsKey ( LOG_FILE_OPTION ))
2019-02-09 16:39:40 +01:00
{
2024-08-13 16:43:53 +02:00
var prefix = System . Reflection . Assembly . GetEntryAssembly (). GetName (). Name . StartsWith ( "Duplicati.Server" ) ? "server" : "trayicon" ;
2025-02-09 10:28:56 +01:00
commandlineOptions [ LOG_FILE_OPTION ] = System . IO . Path . Combine ( StartupPath , $"Duplicati-{prefix}.debug.log" );
commandlineOptions [ LOG_LEVEL_OPTION ] = Duplicati . Library . Logging . LogMessageType . Profiling . ToString ();
if ( System . IO . File . Exists ( commandlineOptions [ LOG_FILE_OPTION ]))
System . IO . File . Delete ( commandlineOptions [ LOG_FILE_OPTION ]);
2019-02-09 16:39:40 +01:00
}
2025-05-05 13:41:28 +02:00
logScope = Log . StartScope ( logWriteHandler , null );
2019-02-09 16:39:40 +01:00
2025-02-09 10:28:56 +01:00
if ( commandlineOptions . ContainsKey ( LOG_FILE_OPTION ))
2019-02-09 16:39:40 +01:00
{
2024-08-27 16:24:33 +02:00
var loglevel = Library . Logging . LogMessageType . Warning ;
2025-02-09 10:28:56 +01:00
if ( commandlineOptions . ContainsKey ( LOG_LEVEL_OPTION ))
Enum . TryParse ( commandlineOptions [ LOG_LEVEL_OPTION ], true , out loglevel );
2025-05-05 13:41:28 +02:00
logWriteHandler . SetServerFile ( commandlineOptions [ LOG_FILE_OPTION ], loglevel );
2025-02-09 10:28:56 +01:00
}
if ( Library . Utility . Utility . ParseBoolOption ( commandlineOptions , LOG_CONSOLE_OPTION ))
{
var loglevel = Library . Logging . LogMessageType . Information ;
if ( commandlineOptions . ContainsKey ( LOG_LEVEL_OPTION ))
Enum . TryParse ( commandlineOptions [ LOG_LEVEL_OPTION ], true , out loglevel );
2019-02-09 16:39:40 +01:00
2025-05-05 13:41:28 +02:00
logWriteHandler . AppendLogDestination ( new ConsoleLogDestination ( loglevel ), loglevel );
2019-02-09 16:39:40 +01:00
}
2024-08-27 16:24:33 +02:00
if ( commandlineOptions . TryGetValue ( WINDOWS_EVENTLOG_OPTION , out var source ) && ! string . IsNullOrEmpty ( source ))
{
if (! OperatingSystem . IsWindows ())
{
2025-03-25 11:13:57 +01:00
Log . WriteWarningMessage ( LOGTAG , "WindowsLogNotSupported" , null , Strings . Program . WindowsEventLogNotSupported );
2024-08-27 16:24:33 +02:00
}
else
{
2024-12-20 16:09:42 +01:00
if (! WindowsEventLogSource . SourceExists ( source ))
{
2025-03-25 11:13:57 +01:00
Log . WriteInformationMessage ( LOGTAG , "WindowsLogMissingCreating" , null , Strings . Program . WindowsEventLogSourceNotFound ( source ));
2024-12-20 16:09:42 +01:00
try
{
WindowsEventLogSource . CreateEventSource ( source );
}
catch ( Exception ex )
{
2025-03-25 11:13:57 +01:00
Log . WriteWarningMessage ( LOGTAG , "WindowsLogFailedCreate" , ex , Strings . Program . WindowsEventLogSourceNotCreated ( source ));
2024-12-20 16:09:42 +01:00
}
}
if ( WindowsEventLogSource . SourceExists ( source ))
{
2025-05-05 13:41:28 +02:00
var loglevel = LogMessageType . Information ;
2024-12-20 16:09:42 +01:00
if ( commandlineOptions . ContainsKey ( WINDOWS_EVENTLOG_LEVEL_OPTION ))
Enum . TryParse ( commandlineOptions [ WINDOWS_EVENTLOG_LEVEL_OPTION ], true , out loglevel );
2024-08-27 16:24:33 +02:00
2025-05-05 13:41:28 +02:00
logWriteHandler . AppendLogDestination ( new WindowsEventLogSource ( source ), loglevel );
2024-12-20 16:09:42 +01:00
}
2024-08-27 16:24:33 +02:00
}
}
2024-11-29 08:42:43 +01:00
2025-05-05 13:41:28 +02:00
CrashlogHelper . OnUnobservedTaskException += ( ex ) => logWriteHandler . WriteMessage ( new LogEntry ( ex . Message , null , Library . Logging . LogMessageType . Error , LOGTAG , "UnobservedTaskException" , ex ));
return logScope ;
2019-02-09 16:39:40 +01:00
}
2024-09-09 11:25:39 +02:00
private static int ShowHelp ( bool writeToConsoleOnExceptionw )
2019-02-07 21:41:58 +01:00
{
2024-09-09 11:25:39 +02:00
if ( writeToConsoleOnExceptionw )
2019-02-07 21:41:58 +01:00
{
Console . WriteLine ( Strings . Program . HelpDisplayDialog );
2025-03-25 11:13:57 +01:00
foreach ( var arg in SupportedCommands )
2019-02-07 21:41:58 +01:00
Console . WriteLine ( Strings . Program . HelpDisplayFormat ( arg . Name , arg . LongDescription ));
return 0 ;
}
throw new Exception ( "Server invoked with --help" );
}
2025-07-10 21:03:16 +02:00
public static Connection GetDatabaseConnection ( IApplicationSettings applicationSettings , Dictionary < string , string > commandlineOptions , bool silentConsole , bool changeDbEncryption )
2017-05-21 19:25:39 +02:00
{
2025-01-28 13:01:35 +01:00
// Emit a warning if the database is stored in the Windows folder
2025-05-05 16:13:46 +02:00
if ( Util . IsPathUnderWindowsFolder ( applicationSettings . DataFolder ))
2025-01-28 13:01:35 +01:00
Log . WriteWarningMessage ( LOGTAG , "DatabaseInWindowsFolder" , null , "The database is stored in the Windows folder, this is not recommended as it will be deleted on Windows upgrades." );
2024-08-30 12:59:49 +02:00
2025-05-05 16:13:46 +02:00
CrashlogHelper . DefaultLogDir = applicationSettings . DataFolder ;
2017-05-21 19:25:39 +02:00
2024-03-05 08:55:13 +01:00
var sqliteVersion = new Version ( Duplicati . Library . SQLiteHelper . SQLiteLoader . SQLiteVersion );
2017-05-21 19:25:39 +02:00
if ( sqliteVersion < new Version ( 3 , 6 , 3 ))
{
//The official Mono SQLite provider is also broken with less than 3.6.3
throw new Exception ( Strings . Program . WrongSQLiteVersion ( sqliteVersion , "3.6.3" ));
}
//Create the connection instance
var con = Library . SQLiteHelper . SQLiteLoader . LoadConnection ();
try
{
2025-05-05 16:13:46 +02:00
var databasePath = System . IO . Path . Combine ( applicationSettings . DataFolder , DataFolderManager . SERVER_DATABASE_FILENAME );
2017-05-21 19:25:39 +02:00
2025-05-05 16:13:46 +02:00
if (! System . IO . Directory . Exists ( System . IO . Path . GetDirectoryName ( databasePath )))
System . IO . Directory . CreateDirectory ( System . IO . Path . GetDirectoryName ( databasePath ));
2017-05-21 19:25:39 +02:00
2024-04-23 17:06:32 +02:00
// Attempt to open the database, removing any encryption present
2025-06-11 08:58:32 +02:00
Library . SQLiteHelper . SQLiteLoader . OpenDatabaseAsync ( con , databasePath , Library . SQLiteHelper . SQLiteRC4Decrypter . GetEncryptionPassword ( commandlineOptions )). Await ();
2017-05-21 19:25:39 +02:00
2025-05-05 16:13:46 +02:00
Library . SQLiteHelper . DatabaseUpgrader . UpgradeDatabase ( con , databasePath , typeof ( Library . RestAPI . Database . DatabaseSchemaMarker ));
2017-05-21 19:25:39 +02:00
}
catch ( Exception ex )
{
//Unwrap the reflection exceptions
if ( ex is System . Reflection . TargetInvocationException && ex . InnerException != null )
ex = ex . InnerException ;
2024-08-20 12:16:11 +02:00
throw new Exception ( Strings . Program . DatabaseOpenError ( ex . Message ), ex );
2017-05-21 19:25:39 +02:00
}
2024-08-28 22:25:23 +02:00
var disableDbEncryption = Library . Utility . Utility . ParseBoolOption ( commandlineOptions , DISABLE_DB_ENCRYPTION_OPTION );
2024-08-30 12:45:39 +02:00
var requireDbEncryptionKey = Library . Utility . Utility . ParseBoolOption ( commandlineOptions , REQUIRE_DB_ENCRYPTION_KEY_OPTION );
2024-10-24 15:56:39 +02:00
var encKey = EncryptedFieldHelper . KeyInstance . CreateKeyIfValid ( commandlineOptions . GetValueOrDefault ( SETTINGS_ENCRYPTION_KEY_OPTION ));
var usingBlacklistedKey = encKey ?. IsBlacklisted ?? false ;
var hasValidEncryptionKey = encKey != null ;
2025-05-05 16:13:46 +02:00
applicationSettings . SettingsEncryptionKeyProvidedExternally = hasValidEncryptionKey ;
2024-08-30 12:45:39 +02:00
2024-10-24 15:56:39 +02:00
if ( requireDbEncryptionKey && !( hasValidEncryptionKey || disableDbEncryption ))
throw new UserInformationException ( Strings . Program . DatabaseEncryptionKeyRequired ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ), "RequireDbEncryptionKey" );
2024-08-28 22:25:23 +02:00
2025-07-10 21:03:16 +02:00
var hasEncryptedFields = false ;
try
2024-08-31 11:02:14 +02:00
{
2025-07-10 21:03:16 +02:00
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 );
2024-08-31 11:02:14 +02:00
2025-07-10 21:03:16 +02:00
if ( hasEncryptedFields && ! hasValidEncryptionKey )
2024-08-31 11:02:14 +02:00
{
2025-07-10 21:03:16 +02:00
Log . WriteWarningMessage ( LOGTAG , "EncryptionKeyMissing" , null , Strings . Program . EncryptionKeyMissing ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME ));
if (! silentConsole )
Console . WriteLine ( Strings . Program . EncryptionKeyMissing ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME ));
2024-08-31 11:02:14 +02:00
}
2017-05-21 19:25:39 +02:00
}
2025-07-10 21:03:16 +02:00
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
}
2017-05-21 19:25:39 +02:00
2024-08-31 11:02:14 +02:00
if (! hasValidEncryptionKey && ! disableDbEncryption )
{
disableDbEncryption = true ;
2025-05-05 16:13:46 +02:00
Log . WriteWarningMessage ( LOGTAG , "MissingEncryptionKey" , null , Strings . Program . NoEncryptionKeySpecified ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
2024-09-09 11:25:39 +02:00
if (! silentConsole )
2025-05-05 16:13:46 +02:00
Console . WriteLine ( Strings . Program . NoEncryptionKeySpecified ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
2024-08-31 11:02:14 +02:00
}
2024-08-30 12:45:39 +02:00
if ( usingBlacklistedKey && ! disableDbEncryption )
{
disableDbEncryption = true ;
2025-05-05 16:13:46 +02:00
Log . WriteErrorMessage ( LOGTAG , "BlacklistedEncryptionKey" , null , Strings . Program . BlacklistedEncryptionKey ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
2024-09-09 11:25:39 +02:00
if (! silentConsole )
2025-05-05 16:13:46 +02:00
Console . WriteLine ( Strings . Program . BlacklistedEncryptionKey ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
2024-08-30 12:45:39 +02:00
}
2025-07-10 21:03:16 +02:00
// 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 ;
2025-05-05 16:13:46 +02:00
return new Connection ( con , disableDbEncryption , encKey , applicationSettings . DataFolder , applicationSettings . StartOrStopUsageReporter );
2017-05-21 19:25:39 +02:00
}
2025-05-05 16:13:46 +02:00
private static void StartOrStopUsageReporter ( Connection connection )
2016-03-12 00:29:51 +01:00
{
2018-05-22 21:49:48 +02:00
var disableUsageReporter =
2025-05-05 16:13:46 +02:00
string . Equals ( connection . ApplicationSettings . UsageReporterLevel , "none" , StringComparison . OrdinalIgnoreCase )
2016-03-12 00:29:51 +01:00
||
2025-05-05 16:13:46 +02:00
string . Equals ( connection . ApplicationSettings . UsageReporterLevel , "disabled" , StringComparison . OrdinalIgnoreCase );
2016-03-12 00:29:51 +01:00
2025-05-05 16:13:46 +02:00
if (! Enum . TryParse < Library . UsageReporter . ReportType >( connection . ApplicationSettings . UsageReporterLevel , true , out var reportLevel ))
2016-03-12 00:29:51 +01:00
Library . UsageReporter . Reporter . SetReportLevel ( null , disableUsageReporter );
else
Library . UsageReporter . Reporter . SetReportLevel ( reportLevel , disableUsageReporter );
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// This event handler updates the trayicon menu with the current state of the runner.
/// </summary>
2024-03-15 16:51:01 +01:00
/// <exception cref="ArgumentOutOfRangeException"></exception>
2025-10-15 17:58:15 +02:00
private static void LiveControl_StateChanged ( IQueueRunnerService queueRunnerService , Connection connection , EventPollNotify eventPollNotify , ISchedulerService schedulerService , LiveControls . LiveControlEvent e )
2013-02-12 21:43:14 +00:00
{
2025-05-03 14:58:07 +02:00
var appSettings = connection . ApplicationSettings ;
2024-12-18 08:27:59 +01:00
switch ( e . State )
2013-02-12 21:43:14 +00:00
{
case LiveControls . LiveControlState . Paused :
2014-05-15 12:47:16 +02:00
{
2025-05-03 14:58:07 +02:00
queueRunnerService . Pause ();
queueRunnerService . GetCurrentTask ()?. Pause ( e . TransfersPaused );
2024-12-18 08:27:59 +01:00
appSettings . PausedUntil = e . WaitTimeExpiration ;
2014-05-15 12:47:16 +02:00
break ;
}
2013-02-12 21:43:14 +00:00
case LiveControls . LiveControlState . Running :
2014-05-15 12:47:16 +02:00
{
2025-05-03 14:58:07 +02:00
queueRunnerService . Resume ();
queueRunnerService . GetCurrentTask ()?. Resume ();
2025-10-15 17:58:15 +02:00
schedulerService ?. Reschedule ();
2024-12-18 08:27:59 +01:00
appSettings . PausedUntil = null ;
2014-05-15 12:47:16 +02:00
break ;
}
2024-03-15 16:51:01 +01:00
default :
2024-12-18 08:27:59 +01:00
Log . WriteWarningMessage ( LOGTAG , "InvalidPauseResumeState" , null , Strings . Program . InvalidPauseResumeState ( LiveControl . State ));
break ;
2013-02-12 21:43:14 +00:00
}
2025-05-03 14:58:07 +02:00
eventPollNotify . SignalNewEvent ();
2024-12-18 08:27:59 +01:00
2013-02-12 21:43:14 +00:00
}
2024-03-15 16:51:01 +01:00
2015-08-24 08:04:22 +01:00
/// <summary>
/// Simple method for tracking if the server has crashed
/// </summary>
2025-05-05 16:13:46 +02:00
/// <param name="applicationSettings">The application settings</param>
private static void PingPongMethod ( IApplicationSettings applicationSettings )
2014-07-25 14:09:26 +02:00
{
var rd = new System . IO . StreamReader ( Console . OpenStandardInput ());
var wr = new System . IO . StreamWriter ( Console . OpenStandardOutput ());
2016-04-13 11:44:21 +02:00
string line ;
while (( line = rd . ReadLine ()) != null )
2014-07-25 14:09:26 +02:00
{
2016-04-13 11:44:21 +02:00
if ( string . Equals ( "shutdown" , line , StringComparison . OrdinalIgnoreCase ))
{
// TODO: All calls to ApplicationExitEvent and TrayIcon->Quit
// should check if we are running something
2025-08-07 11:50:01 +02:00
applicationSettings . SignalApplicationExit ();
2016-04-13 11:44:21 +02:00
}
else
{
wr . WriteLine ( "pong" );
wr . Flush ();
}
2014-07-25 14:09:26 +02:00
}
}
2015-02-15 23:26:52 +01:00
/// <summary>
/// The default log retention
/// </summary>
2019-10-19 10:56:21 -07:00
private static readonly string DEFAULT_LOG_RETENTION = "30D" ;
2015-02-15 23:26:52 +01:00
2024-10-24 15:56:39 +02:00
/// <summary>
/// The options related to the secret provider
/// </summary>
private static readonly IReadOnlyList < ICommandLineArgument > SECRET_PROVIDER_OPTIONS = new Options ( new Dictionary < string , string >()). SupportedCommands . Where ( x => x . Name . StartsWith ( "secret-provider" )). ToList ();
2025-03-25 11:13:57 +01:00
/// <summary>
/// Gets additional commandline arguments support on Windows
/// </summary>
private static readonly ICommandLineArgument [] WindowsOptions = OperatingSystem . IsWindows ()
? [
new CommandLineArgument ( WINDOWS_EVENTLOG_OPTION , CommandLineArgument . ArgumentType . Boolean , Strings . Program . LogwindowseventlogShort , Strings . Program . LogwindowseventlogLong ),
new CommandLineArgument ( WINDOWS_EVENTLOG_LEVEL_OPTION , CommandLineArgument . ArgumentType . Enumeration , Strings . Program . LogwindowseventloglevelShort , Strings . Program . LogwindowseventloglevelLong , Library . Logging . LogMessageType . Information . ToString (), null , Enum . GetNames ( typeof ( Duplicati . Library . Logging . LogMessageType )))
]
: [];
2013-02-12 21:43:14 +00:00
/// <summary>
/// Gets a list of all supported commandline options
/// </summary>
2025-03-25 11:13:57 +01:00
public static ICommandLineArgument [] SupportedCommands =>
[
.. WindowsOptions,
new CommandLineArgument(TEMPDIR_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.TempdirShort, Strings.Program.TempdirLong, System.IO.Path.GetTempPath()),
new CommandLineArgument(HELP_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.HelpCommandDescription, Strings.Program.HelpCommandDescription),
new CommandLineArgument(PARAMETERS_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong, "", PARAMETERS_FILE_OPTION_EXTRAS),
new CommandLineArgument(DataFolderManager.PORTABLE_MODE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortablemodeCommandDescription, Strings.Program.PortablemodeCommandDescription, DataFolderManager.PORTABLE_MODE.ToString().ToLowerInvariant()),
new CommandLineArgument(LOG_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.LogfileCommandDescription, Strings.Program.LogfileCommandDescription),
new CommandLineArgument(LOG_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LoglevelCommandDescription, Strings.Program.LoglevelCommandDescription, Library.Logging.LogMessageType.Warning.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))),
new CommandLineArgument(LOG_CONSOLE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.LogConsoleDescription, Strings.Program.LogConsoleDescription, false.ToString()),
new CommandLineArgument(WebServerLoader.OPTION_WEBROOT, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverWebrootDescription, Strings.Program.WebserverWebrootDescription, WebServerLoader.DEFAULT_OPTION_WEBROOT),
new CommandLineArgument(WebServerLoader.OPTION_PORT, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPortDescription, Strings.Program.WebserverPortDescription, WebServerLoader.DEFAULT_OPTION_PORT.ToString()),
new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLEHTTPS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverDisableHTTPSDescription, Strings.Program.WebserverDisableHTTPSDescription),
new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_REMOVESSLCERTIFICATE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverRemoveCertificateDescription, Strings.Program.WebserverRemoveCertificateDescription),
new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription),
new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILEPASSWORD, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription),
new CommandLineArgument(WebServerLoader.OPTION_INTERFACE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverInterfaceDescription, Strings.Program.WebserverInterfaceDescription, WebServerLoader.DEFAULT_OPTION_INTERFACE),
new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_PASSWORD, CommandLineArgument.ArgumentType.Password, Strings.Program.WebserverPasswordDescription, Strings.Program.WebserverPasswordDescription),
new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverAllowedhostnamesDescription, Strings.Program.WebserverAllowedhostnamesDescription, null, [WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT] ),
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_RESET_JWT_CONFIG , CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverResetJwtConfigDescription , Strings . Program . WebserverResetJwtConfigDescription ),
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN , CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverEnableForeverTokenDescription , Strings . Program . WebserverEnableForeverTokenDescription ),
2025-05-28 12:47:42 +02:00
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS , CommandLineArgument . ArgumentType . String , Strings . Program . WebserverDisableApiExtensionsDescription , Strings . Program . WebserverDisableApiExtensionsDescription ),
2025-03-25 11:13:57 +01:00
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_API_ONLY , CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverApiOnlyDescription , Strings . Program . WebserverApiOnlyDescription ),
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS , CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverDisableSigninTokensDescription , Strings . Program . WebserverDisableSigninTokensDescription ),
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_SPAPATHS , CommandLineArgument . ArgumentType . Path , Strings . Program . WebserverSpaPathsDescription , Strings . Program . WebserverSpaPathsDescription , WebServerLoader . DEFAULT_OPTION_SPAPATHS ),
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_TIMEZONE , CommandLineArgument . ArgumentType . String , Strings . Program . WebserverTimezoneDescription , Strings . Program . WebserverTimezoneDescription , TimeZoneHelper . GetLocalTimeZone (), null , TimeZoneHelper . GetTimeZones (). Select ( x => x . Id ). ToArray ()),
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_CORS_ORIGINS , CommandLineArgument . ArgumentType . Path , Strings . Program . WebserverCorsOriginsDescription , Strings . Program . WebserverCorsOriginsDescription , WebServerLoader . DEFAULT_OPTION_SPAPATHS ),
2025-03-28 13:47:27 +01:00
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_PRE_AUTH_TOKENS , CommandLineArgument . ArgumentType . String , Strings . Program . WebserverPreAuthTokensDescription , Strings . Program . WebserverPreAuthTokensDescription ),
2025-06-29 16:57:47 +02:00
new CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_TOKENDURATION , CommandLineArgument . ArgumentType . Timespan , Strings . Program . WebserverTokenDurationDescription , Strings . Program . WebserverTokenDurationDescription , WebServerLoader . DEFAULT_OPTION_TOKENDURATION ),
2025-03-25 11:13:57 +01:00
new CommandLineArgument ( PING_PONG_KEEPALIVE_OPTION , CommandLineArgument . ArgumentType . Boolean , Strings . Program . PingpongkeepaliveShort , Strings . Program . PingpongkeepaliveLong ),
new CommandLineArgument ( DISABLE_UPDATE_CHECK_OPTION , CommandLineArgument . ArgumentType . Boolean , Strings . Program . DisableupdatecheckShort , Strings . Program . DisableupdatecheckLong ),
new CommandLineArgument ( LOG_RETENTION_OPTION , CommandLineArgument . ArgumentType . Timespan , Strings . Program . LogretentionShort , Strings . Program . LogretentionLong , DEFAULT_LOG_RETENTION ),
2025-07-27 11:45:18 +02:00
new CommandLineArgument ( DataFolderManager . SERVER_DATAFOLDER_OPTION , CommandLineArgument . ArgumentType . Path , Strings . Program . ServerdatafolderShort , Strings . Program . ServerdatafolderLong ( DataFolderManager . DATAFOLDER_ENV_NAME ), DataFolderManager . GetDataFolder ( DataFolderManager . AccessMode . ProbeOnly )),
2025-03-25 11:13:57 +01:00
new CommandLineArgument ( DISABLE_DB_ENCRYPTION_OPTION , CommandLineArgument . ArgumentType . Boolean , Strings . Program . DisabledbencryptionShort , Strings . Program . DisabledbencryptionLong ),
new CommandLineArgument ( REQUIRE_DB_ENCRYPTION_KEY_OPTION , CommandLineArgument . ArgumentType . Boolean , Strings . Program . RequiredbencryptionShort , Strings . Program . RequiredbencryptionLong ),
new CommandLineArgument ( SETTINGS_ENCRYPTION_KEY_OPTION , CommandLineArgument . ArgumentType . Password , Strings . Program . SettingsencryptionkeyShort , Strings . Program . SettingsencryptionkeyLong ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME )),
2025-05-16 11:42:50 +02:00
new CommandLineArgument ( REGISTER_REMOTE_CONTROL_OPTION , CommandLineArgument . ArgumentType . String , Strings . Program . RegisterRemoteControlShort , Strings . Program . RegisterRemoteControlLong ),
new CommandLineArgument ( REGISTER_REMOTE_CONTROL_REREGISTER_OPTION , CommandLineArgument . ArgumentType . String , Strings . Program . RegisterRemoteControlReregisterShort , Strings . Program . RegisterRemoteControlReregisterLong ),
2025-10-17 10:01:31 +02:00
new CommandLineArgument ( ALLOWED_BACKEND_MODULES , CommandLineArgument . ArgumentType . String , Strings . Program . AllowedBackendModulesShort , Strings . Program . AllowedBackendModulesLong ),
new CommandLineArgument ( ALLOWED_ENCRYPTION_MODULES , CommandLineArgument . ArgumentType . String , Strings . Program . AllowedEncryptionModulesShort , Strings . Program . AllowedEncryptionModulesLong ),
new CommandLineArgument ( ALLOWED_COMPRESSION_MODULES , CommandLineArgument . ArgumentType . String , Strings . Program . AllowedCompressionModulesShort , Strings . Program . AllowedCompressionModulesLong ),
2025-03-25 11:13:57 +01:00
.. SECRET_PROVIDER_OPTIONS
];
/// <summary>
/// List of known duplicate option names
/// </summary>
public static readonly HashSet < string > KnownDuplicateOptions = new HashSet < string >( StringComparer . OrdinalIgnoreCase );
private static bool ReadOptionsFromFile ( string filename , ref IFilter filter , List < string > cargs , Dictionary < string , string > options )
2018-01-28 20:03:06 +01:00
{
try
{
2025-03-25 11:13:57 +01:00
var fargs = new List < string >( Library . Utility . Utility . ReadFileWithDefaultEncoding ( Environment . ExpandEnvironmentVariables ( filename )). Replace ( "\r\n" , "\n" ). Replace ( "\r" , "\n" ). Split ( new String [] { "\n" }, StringSplitOptions . RemoveEmptyEntries ). Select ( x => x . Trim ()));
2018-01-28 20:03:06 +01:00
var newsource = new List < string >();
string newtarget = null ;
string prependfilter = null ;
string appendfilter = null ;
string replacefilter = null ;
2025-03-25 11:13:57 +01:00
var tmpparsed = FilterCollector . ExtractOptions ( fargs , ( key , value ) =>
2024-06-07 15:56:43 +02:00
{
2018-01-28 20:03:06 +01:00
if ( key . Equals ( "source" , StringComparison . OrdinalIgnoreCase ))
{
newsource . Add ( value );
return false ;
}
else if ( key . Equals ( "target" , StringComparison . OrdinalIgnoreCase ))
{
newtarget = value ;
return false ;
}
else if ( key . Equals ( "append-filter" , StringComparison . OrdinalIgnoreCase ))
{
appendfilter = value ;
return false ;
}
else if ( key . Equals ( "prepend-filter" , StringComparison . OrdinalIgnoreCase ))
{
prependfilter = value ;
return false ;
}
else if ( key . Equals ( "replace-filter" , StringComparison . OrdinalIgnoreCase ))
{
replacefilter = value ;
return false ;
}
return true ;
});
var opt = tmpparsed . Item1 ;
var newfilter = tmpparsed . Item2 ;
// If the user specifies parameters-file, all filters must be in the file.
// Allowing to specify some filters on the command line could result in wrong filter ordering
if (! filter . Empty && ! newfilter . Empty )
2025-03-25 11:13:57 +01:00
throw new UserInformationException ( Strings . Program . FiltersCannotBeUsedWithFileError2 , "FiltersCannotBeUsedOnCommandLineAndInParameterFile" );
2018-01-28 20:03:06 +01:00
if (! newfilter . Empty )
filter = newfilter ;
if (! string . IsNullOrWhiteSpace ( prependfilter ))
2025-03-25 11:13:57 +01:00
filter = FilterExpression . Combine ( FilterExpression . Deserialize ( prependfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries )), filter );
2018-01-28 20:03:06 +01:00
if (! string . IsNullOrWhiteSpace ( appendfilter ))
2025-03-25 11:13:57 +01:00
filter = FilterExpression . Combine ( filter , FilterExpression . Deserialize ( appendfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries )));
2018-01-28 20:03:06 +01:00
if (! string . IsNullOrWhiteSpace ( replacefilter ))
2025-03-25 11:13:57 +01:00
filter = FilterExpression . Deserialize ( replacefilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries ));
2018-01-28 20:03:06 +01:00
2025-03-25 11:13:57 +01:00
foreach ( var keyvalue in opt )
2018-01-28 20:03:06 +01:00
options [ keyvalue . Key ] = keyvalue . Value ;
if (! string . IsNullOrEmpty ( newtarget ))
{
if ( cargs . Count <= 1 )
cargs . Add ( newtarget );
else
cargs [ 1 ] = newtarget ;
}
if ( cargs . Count >= 1 && cargs [ 0 ]. Equals ( "backup" , StringComparison . OrdinalIgnoreCase ))
cargs . AddRange ( newsource );
2018-03-12 14:07:11 +01:00
else if ( newsource . Count > 0 )
2025-03-25 11:13:57 +01:00
Log . WriteVerboseMessage ( LOGTAG , "NotUsingBackupSources" , Strings . Program . SkippingSourceArgumentsOnNonBackupOperation );
2018-01-28 20:03:06 +01:00
return true ;
}
catch ( Exception e )
{
throw new Exception ( Strings . Program . FailedToParseParametersFileError ( filename , e . Message ));
}
}
2013-02-12 21:43:14 +00:00
}
}