2025-01-14 14:03:48 +01:00
// 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
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 ;
2022-12-30 10:59:29 -08:00
using Duplicati.Library.RestAPI ;
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 ;
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
{
2024-08-14 21:20:49 +02:00
private static readonly string [] ParameterFileOptionStrings = [ "parameters-file" , "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-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" ;
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
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
/// <summary>
/// Gets the folder where Duplicati data is stored
/// </summary>
2022-12-30 10:59:29 -08:00
public static string DataFolder { get => FIXMEGlobal . DataFolder ; private set => FIXMEGlobal . DataFolder = value ; }
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
2013-02-12 21:43:14 +00:00
/// <summary>
/// This is the only access to the database
/// </summary>
2022-12-30 10:59:29 -08:00
public static Database . Connection DataConnection { get => FIXMEGlobal . DataConnection ; set => FIXMEGlobal . DataConnection = value ; }
2013-02-12 21:43:14 +00:00
/// <summary>
/// This is the lock to be used before manipulating the shared resources
/// </summary>
2022-12-30 10:59:29 -08:00
public static object MainLock { get => FIXMEGlobal . MainLock ; }
2013-02-12 21:43:14 +00:00
/// <summary>
/// This is the scheduling thread
/// </summary>
2024-03-15 16:51:01 +01:00
public static IScheduler Scheduler { get => FIXMEGlobal . Scheduler ; }
2013-02-12 21:43:14 +00:00
2015-10-04 17:14:20 +02:00
/// <summary>
/// List of completed task results
/// </summary>
2022-12-30 10:59:29 -08:00
public static List < KeyValuePair < long , Exception >> TaskResultCache { get => FIXMEGlobal . TaskResultCache ; }
2015-10-04 17:14:20 +02:00
/// <summary>
/// The maximum number of completed task results to keep in memory
/// </summary>
2019-10-19 10:56:21 -07:00
private static readonly int MAX_TASK_RESULT_CACHE_SIZE = 100 ;
2015-10-04 17:14:20 +02:00
2014-07-25 14:09:26 +02:00
/// <summary>
/// The thread running the ping-pong handler
/// </summary>
2019-02-28 17:58:26 +01:00
private static System . Threading . Thread PingPongThread ;
2014-07-25 14:09:26 +02:00
2013-02-12 21:43:14 +00:00
/// <summary>
/// The path to the file that contains the current database
/// </summary>
2019-02-28 17:58:26 +01:00
private static string DatabasePath ;
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
/// <summary>
/// The application exit event
/// </summary>
2022-12-30 10:59:29 -08:00
public static System . Threading . ManualResetEvent ApplicationExitEvent { get => FIXMEGlobal . ApplicationExitEvent ; set => FIXMEGlobal . ApplicationExitEvent = value ; }
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
2014-06-30 11:31:06 +02:00
/// <summary>
/// The update poll thread.
/// </summary>
2024-03-15 16:51:01 +01:00
public static UpdatePollThread UpdatePoller => FIXMEGlobal . UpdatePoller ;
2018-05-22 21:49:48 +02: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>
2019-10-19 10:56:21 -07:00
public static readonly System . Threading . ManualResetEvent ServerStartedEvent = new System . Threading . ManualResetEvent ( false );
2013-02-12 21:43:14 +00:00
/// <summary>
2019-11-30 11:35:43 -08:00
/// The status event signaler, used to control long polling of status updates
2013-02-12 21:43:14 +00:00
/// </summary>
2024-03-15 16:51:01 +01:00
public static EventPollNotify StatusEventNotifyer => FIXMEGlobal . Provider . GetRequiredService < EventPollNotify >();
2018-05-22 21:49:48 +02:00
2013-02-12 21:43:14 +00:00
/// <summary>
2014-03-10 00:16:53 +01:00
/// A delegate method for creating a copy of the current progress state
2013-02-12 21:43:14 +00:00
/// </summary>
2022-12-30 10:59:29 -08:00
public static Func < Duplicati . Server . Serialization . Interface . IProgressEventData > GenerateProgressState { get => FIXMEGlobal . GenerateProgressState ; set => FIXMEGlobal . GenerateProgressState = value ; }
2013-02-12 21:43:14 +00:00
2014-08-01 11:18:10 +02:00
/// <summary>
/// The log redirect handler
/// </summary>
2022-12-30 10:59:29 -08:00
public static LogWriteHandler LogHandler { get => FIXMEGlobal . LogHandler ; }
2014-08-01 11:18:10 +02:00
2014-08-06 10:30:46 +02:00
private static System . Threading . Timer PurgeTempFilesTimer = null ;
2013-12-07 15:24:57 +01:00
public static int ServerPort
{
get
{
2024-06-07 15:56:43 +02:00
return DuplicatiWebserver . Port ;
2013-12-07 15:24:57 +01:00
}
}
2013-02-12 21:43:14 +00:00
2014-06-23 11:22:54 +02:00
public static bool IsFirstRun
{
get { return DataConnection . ApplicationSettings . IsFirstRun ; }
set { DataConnection . ApplicationSettings . IsFirstRun = value ; }
}
public static bool ServerPortChanged
{
get { return DataConnection . ApplicationSettings . ServerPortChanged ; }
set { DataConnection . ApplicationSettings . ServerPortChanged = value ; }
}
2022-12-30 10:59:29 -08:00
static Program ()
{
FIXMEGlobal . StartOrStopUsageReporter = Program . StartOrStopUsageReporter ;
}
2013-02-12 21:43:14 +00:00
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
2024-03-15 14:18:56 +01:00
public static int Main ( string [] _args )
2014-06-26 22:37:58 +02:00
{
2024-08-28 00:18:55 +02:00
Library . AutoUpdater . PreloadSettingsLoader . ConfigurePreloadSettings ( ref _args , Library . AutoUpdater . PackageHelper . NamedExecutable . Server , out var preloadDbSettings );
2013-02-27 14:25:14 +00:00
//If this executable is invoked directly, write to console, otherwise throw exceptions
2024-09-09 11:25:39 +02:00
var writeToConsoleOnException = FIXMEGlobal . Origin == "Server" ;
// 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 );
2019-02-25 12:34:32 +01:00
var optionsWithFilter = Library . Utility . FilterCollector . ExtractOptions ( new List < string >( args ));
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
{
2018-05-22 21:49:48 +02:00
Library . Utility . SystemContextSettings . DefaultTempPath = commandlineOptions [ "tempdir" ];
2019-02-09 16:06:40 +01:00
}
2019-02-09 16:39:40 +01:00
2018-09-26 21:12:13 -07:00
Library . Utility . SystemContextSettings . StartSession ();
2018-05-22 21:49:48 +02:00
2024-08-14 21:20:49 +02:00
ApplyEnvironmentVariables ( commandlineOptions );
2024-10-24 15:56:39 +02:00
ApplySecretProvider ( commandlineOptions , CancellationToken . None ). Await ();
2024-08-14 21:20:49 +02:00
2019-02-09 16:39:40 +01:00
var parameterFileOption = commandlineOptions . Keys . Select ( s => s . ToLower ())
2024-08-14 21:20:49 +02:00
. Intersect ( ParameterFileOptionStrings . Select ( x => x . ToLower ())). FirstOrDefault ();
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
}
2019-02-09 16:39:40 +01:00
ConfigureLogging ( commandlineOptions );
2018-05-22 21:49:48 +02:00
2024-08-20 21:11:59 +02:00
var crashed = false ;
2024-08-27 11:33:52 +02:00
var terminated = false ;
2013-02-12 21:43:14 +00:00
try
{
2024-09-09 11:25:39 +02:00
DataConnection = GetDatabaseConnection ( commandlineOptions , silentConsole );
2013-02-12 21:43:14 +00:00
2017-05-21 19:25:39 +02:00
if (! DataConnection . ApplicationSettings . FixedInvalidBackupId )
DataConnection . FixInvalidBackupId ();
2013-02-12 21:43:14 +00:00
2024-06-19 13:54:10 +02:00
DataConnection . ApplicationSettings . UpgradePasswordToKBDF ();
2024-09-09 11:25:39 +02:00
CreateApplicationInstance ( writeToConsoleOnException );
2015-02-15 22:54:49 +01:00
2016-03-12 00:29:51 +01:00
StartOrStopUsageReporter ();
2019-02-13 22:10:00 +01:00
AdjustApplicationSettings ( commandlineOptions );
2018-08-07 00:36:12 +02:00
2013-02-12 21:43:14 +00:00
ApplicationExitEvent = new System . Threading . ManualResetEvent ( false );
2018-05-22 21:49:48 +02:00
2024-03-15 16:51:01 +01:00
Library . AutoUpdater . UpdaterManager . OnError += obj =>
2014-07-25 14:09:26 +02:00
{
2019-02-10 19:58:56 +01:00
DataConnection . LogError ( null , "Error in updater" , obj );
2014-07-01 23:59:30 +02:00
};
2024-06-07 15:56:43 +02:00
2025-02-08 17:30:27 +01:00
DuplicatiWebserver = StartWebServer ( commandlineOptions , DataConnection ). Await ();
2024-06-07 15:56:43 +02:00
2025-02-06 11:50:38 +01:00
UpdatePoller . Init ( Library . Utility . Utility . ParseBoolOption ( commandlineOptions , DISABLE_UPDATE_CHECK_OPTION ));
2019-08-31 13:22:45 -04:00
2019-02-14 21:50:28 +01:00
SetPurgeTempFilesTimer ( commandlineOptions );
2018-05-22 21:49:48 +02:00
2024-12-18 08:27:59 +01:00
LiveControl . StateChanged = LiveControl_StateChanged ;
2013-02-12 21:43:14 +00:00
2019-02-28 17:51:52 +01:00
SetWorkerThread ();
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
{
2024-06-07 15:56:43 +02:00
PingPongThread = new System . Threading . Thread ( PingPongMethod ) { IsBackground = true };
2019-02-28 17:51:52 +01:00
PingPongThread . Start ();
2014-07-25 14:09:26 +02:00
}
2024-08-20 17:10:34 +02:00
DataConnection . ReWriteAllFieldsIfEncryptionChanged ();
2024-08-28 00:18:55 +02:00
DataConnection . SetPreloadSettingsIfChanged ( preloadDbSettings );
2025-01-27 12:59:51 +01:00
EmitWarningsForConfigurationIssues ( commandlineOptions );
2024-08-20 17:10:34 +02:00
2025-02-14 11:28:22 +01:00
Library . Logging . Log . WriteInformationMessage ( LOGTAG , "ServerStarted" , Strings . Program . ServerStarted ( DuplicatiWebserver . Interface , DuplicatiWebserver . Port ));
logMessageToConsole ( Strings . Program . ServerStarted ( DuplicatiWebserver . Interface , DuplicatiWebserver . Port ));
2024-08-27 16:24:33 +02:00
if ( FIXMEGlobal . Origin == "Server" && DataConnection . ApplicationSettings . AutogeneratedPassphrase )
{
var signinToken = DuplicatiWebserver . Provider . GetRequiredService < IJWTTokenProvider >(). CreateSigninToken ( "server-cli" );
var hostname = ( DataConnection . ApplicationSettings . AllowedHostnames ?? string . Empty ). Split ( new char [] { ';' }, StringSplitOptions . RemoveEmptyEntries ). FirstOrDefault ( x => x != "*" ) ?? "localhost" ;
2024-10-29 11:15:06 +01:00
var scheme = DataConnection . 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}" ;
2024-08-27 16:24:33 +02:00
Library . Logging . 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
{
Library . Logging . 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 ;
ApplicationExitEvent . Set ();
});
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 ();
ApplicationExitEvent . WaitOne ();
}
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 [] {
() => StatusEventNotifyer . SignalNewEvent (),
() => { if ( ShutdownModernWebserver != null ) ShutdownModernWebserver (); },
() => UpdatePoller ?. Terminate (),
() => Scheduler ?. Terminate ( true ),
() => FIXMEGlobal . WorkThread ?. Terminate ( true ),
() => ApplicationInstance ?. Dispose (),
() => PurgeTempFilesTimer ?. Dispose (),
() => Library . UsageReporter . Reporter . ShutDown (),
() => PingPongThread ?. Interrupt (),
2024-12-20 16:09:42 +01:00
() =>
{
Library . Logging . Log . WriteInformationMessage ( LOGTAG , "ServerStopped" , Strings . Program . ServerStopped );
LogHandler ?. Dispose ();
}
2024-08-20 21:11:59 +02:00
};
2014-07-25 14:09:26 +02: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
}
2024-06-09 22:00:34 +02:00
private static async Task < DuplicatiWebserver > StartWebServer ( IReadOnlyDictionary < string , string > options , Connection connection )
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-06-07 15:56:43 +02:00
parsedOptions . Servername ,
2024-09-18 11:07:59 +02:00
parsedOptions . AllowedHostnames ,
2024-10-03 14:49:07 +02:00
parsedOptions . DisableStaticFiles ,
2025-01-18 11:03:52 +01:00
parsedOptions . SPAPaths ,
parsedOptions . CorsOrigins
2024-10-29 13:55:12 +01:00
);
2024-06-07 15:56:43 +02:00
2025-01-14 14:03:48 +01:00
var server = DuplicatiWebserver . CreateWebServer ( mappedSettings , connection );
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 );
FIXMEGlobal . Provider = server . Provider ;
ServerPortChanged |= server . Port != DataConnection . ApplicationSettings . LastWebserverPort ;
DataConnection . ApplicationSettings . LastWebserverPort = server . Port ;
return server ;
2019-02-28 17:51:52 +01:00
}
private static void SetWorkerThread ()
{
2024-03-15 16:51:01 +01:00
FIXMEGlobal . WorkerThreadsManager . Spawn ( x => { Runner . Run ( x , true ); });
FIXMEGlobal . WorkThread . StartingWork += ( worker , task ) => { SignalNewEvent ( null , null ); };
FIXMEGlobal . WorkThread . CompletedWork += ( worker , task ) => { SignalNewEvent ( null , null ); };
FIXMEGlobal . WorkThread . WorkQueueChanged += ( worker ) => { SignalNewEvent ( null , null ); };
2024-06-07 15:56:43 +02:00
FIXMEGlobal . Scheduler . SubScribeToNewSchedule (() => SignalNewEvent ( null , null ));
2024-03-15 16:51:01 +01:00
FIXMEGlobal . WorkThread . OnError += ( worker , task , exception ) =>
2019-02-28 17:51:52 +01:00
{
Program . DataConnection . LogError ( task ?. BackupID , "Error in worker" , exception );
};
2024-03-15 16:51:01 +01:00
var lastScheduleId = FIXMEGlobal . NotificationUpdateService . LastDataUpdateId ;
2019-02-28 17:51:52 +01:00
Program . StatusEventNotifyer . NewEvent += ( sender , e ) =>
{
2024-03-15 16:51:01 +01:00
if ( lastScheduleId == FIXMEGlobal . NotificationUpdateService . LastDataUpdateId ) return ;
lastScheduleId = FIXMEGlobal . NotificationUpdateService . LastDataUpdateId ;
2019-02-28 17:51:52 +01:00
Program . Scheduler . Reschedule ();
};
void RegisterTaskResult ( long id , Exception ex )
{
lock ( MainLock )
{
// If the new results says it crashed, we store that instead of success
if ( Program . TaskResultCache . Count > 0 && Program . TaskResultCache . Last (). Key == id )
{
if ( ex != null && Program . TaskResultCache . Last (). Value == null )
Program . TaskResultCache . RemoveAt ( Program . TaskResultCache . Count - 1 );
else
return ;
}
Program . TaskResultCache . Add ( new KeyValuePair < long , Exception >( id , ex ));
while ( Program . TaskResultCache . Count > MAX_TASK_RESULT_CACHE_SIZE )
Program . TaskResultCache . RemoveAt ( 0 );
}
}
2024-04-22 09:35:53 +02:00
FIXMEGlobal . WorkThread . CompletedWork += ( worker , task ) => { RegisterTaskResult ( task . TaskID , null ); };
FIXMEGlobal . WorkThread . OnError += ( worker , task , exception ) => { RegisterTaskResult ( task . TaskID , exception ); };
2019-02-28 17:51:52 +01:00
}
2019-02-14 21:50:28 +01:00
private static void SetPurgeTempFilesTimer ( Dictionary < string , string > commandlineOptions )
{
var lastPurge = new DateTime ( 0 );
System . Threading . TimerCallback purgeTempFilesCallback = ( x ) =>
{
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 ;
foreach ( var e in DataConnection . GetTempFiles (). Where (( f ) => f . Expires < DateTime . Now ))
{
try
{
if ( System . IO . File . Exists ( e . Path ))
System . IO . File . Delete ( e . Path );
}
catch ( Exception ex )
{
DataConnection . LogError ( null , $"Failed to delete temp file: {e.Path}" , ex );
}
DataConnection . DeleteTempFile ( e . ID );
}
Library . Utility . TempFile . RemoveOldApplicationTempFiles (( path , ex ) =>
{
DataConnection . LogError ( null , $"Failed to delete temp file: {path}" , ex );
});
if (! commandlineOptions . TryGetValue ( "log-retention" , out string pts ))
{
pts = DEFAULT_LOG_RETENTION ;
}
DataConnection . PurgeLogData ( Library . Utility . Timeparser . ParseTimeInterval ( pts , DateTime . Now , true ));
}
catch ( Exception ex )
{
DataConnection . LogError ( null , "Failed during temp file cleanup" , ex );
}
};
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
}
2019-02-13 22:10:00 +01:00
private static void AdjustApplicationSettings ( Dictionary < string , string > commandlineOptions )
{
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 ))
{
DataConnection . ApplicationSettings . JWTConfig = null ;
// Clean up stored tokens as they are now invalid
DataConnection . ExecuteWithCommand (( con ) => con . ExecuteNonQuery ( "DELETE FROM TokenFamily" ));
}
2024-12-12 08:59:59 +01:00
if ( Library . Utility . Utility . ParseBoolOption ( commandlineOptions , WebServerLoader . OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN ))
2024-12-12 11:05:09 +01:00
DataConnection . ApplicationSettings . EnableForeverTokens ();
2024-12-12 08:59:59 +01:00
2024-08-13 13:50:21 +02:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_DISABLE_VISUAL_CAPTCHA ))
DataConnection . ApplicationSettings . DisableVisualCaptcha = Library . Utility . Utility . ParseBool ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_DISABLE_VISUAL_CAPTCHA ], true );
2024-09-18 11:07:59 +02:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS ))
DataConnection . ApplicationSettings . DisableSigninTokens = Library . Utility . Utility . ParseBool ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS ], true );
2024-06-07 15:56:43 +02:00
if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_PASSWORD ))
DataConnection . 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 ))
DataConnection . ApplicationSettings . SetAllowedHostnames ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES ]);
2024-08-04 16:31:16 +02:00
else if ( commandlineOptions . ContainsKey ( WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT ))
DataConnection . 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
{
DataConnection . ApplicationSettings . Timezone = TimeZoneHelper . FindTimeZone ( commandlineOptions [ WebServerLoader . OPTION_WEBSERVICE_TIMEZONE ]);
}
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
if ( DataConnection . ApplicationSettings . UpdatedVersion != null )
{
// Check if the running version is newer than the recorded version
if ( UpdaterManager . TryParseVersion ( DataConnection . ApplicationSettings . UpdatedVersion . Version ) <= UpdaterManager . TryParseVersion ( UpdaterManager . SelfVersion . Version ))
{
// Clean up lingering update notifications
var updateNotifications = DataConnection . GetNotifications (). Where ( x => x . Action == "update:new" ). ToList ();
foreach ( var n in updateNotifications )
DataConnection . DismissNotification ( n . ID );
// Clear up the recorded version
DataConnection . ApplicationSettings . UpdatedVersion = null ;
}
}
2025-01-27 08:52:06 +01:00
}
private static void EmitWarningsForConfigurationIssues ( Dictionary < string , string > commandlineOptions )
{
if ( DataConnection . ApplicationSettings . LastConfigIssueCheckVersion != UpdaterManager . SelfVersion . Version )
{
var updateNotifications = DataConnection . GetNotifications (). Where ( x => x . Action . StartsWith ( "config:issue:" )). ToList ();
foreach ( var n in updateNotifications )
DataConnection . DismissNotification ( n . ID );
if (! DataConnection . IsEncryptingFields && ! Library . Utility . Utility . ParseBoolOption ( commandlineOptions , DISABLE_DB_ENCRYPTION_OPTION ))
{
DataConnection . RegisterNotification (
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-01-27 23:17:23 +01:00
if ( OperatingSystem . IsWindows () && DataFolder . StartsWith ( Util . AppendDirSeparator ( Environment . GetFolderPath ( Environment . SpecialFolder . Windows )), StringComparison . OrdinalIgnoreCase ))
2025-01-27 11:49:17 +01:00
{
DataConnection . RegisterNotification (
Serialization . NotificationType . Warning ,
"Incorrect storage folder" ,
2025-01-27 23:17:23 +01:00
"The server configuraion 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-01-27 08:52:06 +01:00
DataConnection . ApplicationSettings . LastConfigIssueCheckVersion = UpdaterManager . SelfVersion . Version ;
}
2019-02-13 22:10:00 +01:00
}
2019-02-12 21:59:43 +01:00
2024-09-09 11:25:39 +02:00
private static void CreateApplicationInstance ( bool writeToConsoleOnExceptionw )
2019-02-12 21:59:43 +01:00
{
try
{
//This will also create DATAFOLDER if it does not exist
ApplicationInstance = new SingleInstance ( DataFolder );
}
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
}
2024-10-24 15:56:39 +02:00
private static async Task ApplySecretProvider ( Dictionary < string , string > commandlineOptions , CancellationToken cancellationToken )
2024-11-04 15:57:56 +01:00
=> FIXMEGlobal . SecretProvider = await SecretProviderHelper . ApplySecretProviderAsync ([], [], commandlineOptions , TempFolder . SystemTempPath , FIXMEGlobal . 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 ));
}
}
2019-02-09 16:39:40 +01:00
private static void ConfigureLogging ( Dictionary < string , string > commandlineOptions )
{
//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-02-09 10:28:56 +01:00
Log . StartScope ( LogHandler , 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 );
LogHandler . SetServerFile ( commandlineOptions [ LOG_FILE_OPTION ], loglevel );
}
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-02-09 10:28:56 +01:00
LogHandler . 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 ())
{
Library . Logging . Log . WriteWarningMessage ( LOGTAG , "WindowsLogNotSupported" , null , Strings . Program . WindowsEventLogNotSupported );
}
else
{
2024-12-20 16:09:42 +01:00
if (! WindowsEventLogSource . SourceExists ( source ))
{
Library . Logging . Log . WriteInformationMessage ( LOGTAG , "WindowsLogMissingCreating" , null , Strings . Program . WindowsEventLogSourceNotFound ( source ));
try
{
WindowsEventLogSource . CreateEventSource ( source );
}
catch ( Exception ex )
{
Library . Logging . Log . WriteWarningMessage ( LOGTAG , "WindowsLogFailedCreate" , ex , Strings . Program . WindowsEventLogSourceNotCreated ( source ));
}
}
if ( WindowsEventLogSource . SourceExists ( source ))
{
var loglevel = Library . Logging . LogMessageType . Information ;
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
2024-12-20 16:09:42 +01:00
LogHandler . AppendLogDestination ( new WindowsEventLogSource ( source ), loglevel );
}
2024-08-27 16:24:33 +02:00
}
}
2024-11-29 08:42:43 +01:00
2024-11-29 09:02:12 +01:00
CrashlogHelper . OnUnobservedTaskException += ( ex ) => LogHandler . WriteMessage ( new Library . Logging . LogEntry ( ex . Message , null , Library . Logging . LogMessageType . Error , LOGTAG , "UnobservedTaskException" , ex ));
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 );
foreach ( Library . Interface . ICommandLineArgument arg in SupportedCommands )
Console . WriteLine ( Strings . Program . HelpDisplayFormat ( arg . Name , arg . LongDescription ));
return 0 ;
}
throw new Exception ( "Server invoked with --help" );
}
2025-01-28 13:01:35 +01:00
public static Connection GetDatabaseConnection ( Dictionary < string , string > commandlineOptions , bool silentConsole )
2017-05-21 19:25:39 +02:00
{
2025-01-28 13:01:35 +01:00
DataFolder = DataFolderManager . DATAFOLDER ;
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
if ( Util . IsPathUnderWindowsFolder ( DataFolder ))
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
2024-11-21 20:23:42 +01:00
CrashlogHelper . DefaultLogDir = 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-01-28 13:01:35 +01:00
DatabasePath = System . IO . Path . Combine ( DataFolder , DataFolderManager . SERVER_DATABASE_FILENAME );
2017-05-21 19:25:39 +02:00
if (! System . IO . Directory . Exists ( System . IO . Path . GetDirectoryName ( DatabasePath )))
System . IO . Directory . CreateDirectory ( System . IO . Path . GetDirectoryName ( DatabasePath ));
2024-04-23 17:06:32 +02:00
// Attempt to open the database, removing any encryption present
Duplicati . Library . SQLiteHelper . SQLiteLoader . OpenDatabase ( con , DatabasePath , Library . SQLiteHelper . SQLiteRC4Decrypter . GetEncryptionPassword ( commandlineOptions ));
2017-05-21 19:25:39 +02:00
2022-12-30 10:59:29 -08:00
Duplicati . Library . SQLiteHelper . DatabaseUpgrader . UpgradeDatabase ( con , DatabasePath , typeof ( Duplicati . Library . RestAPI . Database . DatabaseConnectionSchemaMarker ));
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 ;
FIXMEGlobal . 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
2024-08-31 11:02:14 +02:00
if (! hasValidEncryptionKey )
{
try
{
var hasEncryptedFields = false ;
using ( var cmd = con . CreateCommand ())
{
cmd . CommandText = @ $"SELECT ""Value"" FROM ""Option"" WHERE ""Name"" = '{Database.ServerSettings.CONST.ENCRYPTED_FIELDS}' AND ""BackupID"" = {Connection.SERVER_SETTINGS_ID}" ;
hasEncryptedFields = Library . Utility . Utility . ParseBool ( cmd . ExecuteScalar ()?. ToString (), false );
}
if ( hasEncryptedFields )
{
Library . Logging . Log . WriteWarningMessage ( LOGTAG , "EncryptionKeyMissing" , null , Strings . Program . EncryptionKeyMissing ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME ));
2024-09-09 11:25:39 +02:00
if (! silentConsole )
2024-08-31 11:02:14 +02:00
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
}
2017-05-21 19:25:39 +02:00
}
2024-08-31 11:02:14 +02:00
if (! hasValidEncryptionKey && ! disableDbEncryption )
{
disableDbEncryption = true ;
Duplicati . Library . Logging . Log . WriteWarningMessage ( LOGTAG , "MissingEncryptionKey" , null , Strings . Program . NoEncryptionKeySpecified ( Library . Encryption . EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
2024-09-09 11:25:39 +02:00
if (! silentConsole )
2024-08-31 11:02:14 +02:00
Console . WriteLine ( Strings . Program . NoEncryptionKeySpecified ( Library . Encryption . EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
}
2024-08-30 12:45:39 +02:00
if ( usingBlacklistedKey && ! disableDbEncryption )
{
disableDbEncryption = true ;
2024-08-31 11:02:14 +02:00
Duplicati . Library . Logging . Log . WriteErrorMessage ( LOGTAG , "BlacklistedEncryptionKey" , null , Strings . Program . BlacklistedEncryptionKey ( Library . Encryption . EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
2024-09-09 11:25:39 +02:00
if (! silentConsole )
2024-08-31 11:02:14 +02:00
Console . WriteLine ( Strings . Program . BlacklistedEncryptionKey ( Library . Encryption . EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ));
2024-08-30 12:45:39 +02:00
}
2024-10-24 15:56:39 +02:00
return new Database . Connection ( con , disableDbEncryption , encKey );
2017-05-21 19:25:39 +02:00
}
2016-03-12 00:29:51 +01:00
public static void StartOrStopUsageReporter ()
{
2018-05-22 21:49:48 +02:00
var disableUsageReporter =
2017-09-18 23:23:45 -06:00
string . Equals ( DataConnection . ApplicationSettings . UsageReporterLevel , "none" , StringComparison . OrdinalIgnoreCase )
2016-03-12 00:29:51 +01:00
||
2017-09-18 23:23:45 -06:00
string . Equals ( DataConnection . ApplicationSettings . UsageReporterLevel , "disabled" , StringComparison . OrdinalIgnoreCase );
2016-03-12 00:29:51 +01:00
Library . UsageReporter . ReportType reportLevel ;
2018-05-22 21:49:48 +02:00
if (! Enum . TryParse < Library . UsageReporter . ReportType >( DataConnection . ApplicationSettings . UsageReporterLevel , true , out 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
private static void SignalNewEvent ( object sender , EventArgs e )
{
StatusEventNotifyer . SignalNewEvent ();
2018-05-22 21:49:48 +02:00
}
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>
2024-12-18 08:27:59 +01:00
private static void LiveControl_StateChanged ( LiveControls . LiveControlEvent e )
2013-02-12 21:43:14 +00:00
{
2024-03-15 16:51:01 +01:00
var worker = FIXMEGlobal . WorkThread ;
2024-12-18 08:27:59 +01:00
var appSettings = FIXMEGlobal . DataConnection . ApplicationSettings ;
switch ( e . State )
2013-02-12 21:43:14 +00:00
{
case LiveControls . LiveControlState . Paused :
2014-05-15 12:47:16 +02:00
{
2024-06-07 15:56:43 +02:00
worker . Pause ();
2024-12-18 08:27:59 +01:00
worker . CurrentTask ?. Pause ( e . TransfersPaused );
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
{
2024-03-15 16:51:01 +01:00
worker . Resume ();
2024-12-18 08:27:59 +01:00
worker . CurrentTask ?. Resume ();
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
}
StatusEventNotifyer . 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>
2014-07-25 14:09:26 +02:00
private static void PingPongMethod ()
{
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
ApplicationExitEvent . Set ();
}
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 ();
2013-02-12 21:43:14 +00:00
/// <summary>
/// Gets a list of all supported commandline options
/// </summary>
2014-04-02 11:34:33 +02:00
public static Library . Interface . ICommandLineArgument [] SupportedCommands
2024-08-31 11:36:32 +02:00
=> ( OperatingSystem . IsWindows ()
? new [] {
2024-08-27 16:24:33 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( WINDOWS_EVENTLOG_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . LogwindowseventlogShort , Strings . Program . LogwindowseventlogLong ),
new Duplicati . Library . Interface . CommandLineArgument ( WINDOWS_EVENTLOG_LEVEL_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Enumeration , Strings . Program . LogwindowseventloglevelShort , Strings . Program . LogwindowseventloglevelLong , Library . Logging . LogMessageType . Information . ToString (), null , Enum . GetNames ( typeof ( Duplicati . Library . Logging . LogMessageType )))
}
2024-08-31 11:36:32 +02:00
: []
)
2024-08-27 16:24:33 +02:00
. Concat ([
2024-08-14 21:20:49 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( "tempdir" , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . TempdirShort , Strings . Program . TempdirLong , System . IO . Path . GetTempPath ()),
new Duplicati . Library . Interface . CommandLineArgument ( "help" , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . HelpCommandDescription , Strings . Program . HelpCommandDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( "parameters-file" , Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . ParametersFileOptionShort , Strings . Program . ParametersFileOptionLong2 , "" , ParameterFileOptionStrings ),
2025-01-28 13:01:35 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( DataFolderManager . PORTABLE_MODE_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . PortablemodeCommandDescription , Strings . Program . PortablemodeCommandDescription , DataFolderManager . PORTABLE_MODE . ToString (). ToLowerInvariant ()),
2025-02-09 10:28:56 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( LOG_FILE_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . LogfileCommandDescription , Strings . Program . LogfileCommandDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( LOG_LEVEL_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Enumeration , Strings . Program . LoglevelCommandDescription , Strings . Program . LoglevelCommandDescription , Library . Logging . LogMessageType . Warning . ToString (), null , Enum . GetNames ( typeof ( Duplicati . Library . Logging . LogMessageType ))),
new Duplicati . Library . Interface . CommandLineArgument ( LOG_CONSOLE_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . LogConsoleDescription , Strings . Program . LogConsoleDescription , false . ToString ()),
2024-08-14 21:20:49 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBROOT , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . WebserverWebrootDescription , Strings . Program . WebserverWebrootDescription , WebServerLoader . DEFAULT_OPTION_WEBROOT ),
2024-11-07 15:52:08 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_PORT , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverPortDescription , Strings . Program . WebserverPortDescription , WebServerLoader . DEFAULT_OPTION_PORT . ToString ()),
2024-12-05 10:34:10 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_DISABLEHTTPS , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverDisableHTTPSDescription , Strings . Program . WebserverDisableHTTPSDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_REMOVESSLCERTIFICATE , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverRemoveCertificateDescription , Strings . Program . WebserverRemoveCertificateDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_SSLCERTIFICATEFILE , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverCertificateFileDescription , Strings . Program . WebserverCertificateFileDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_SSLCERTIFICATEFILEPASSWORD , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverCertificatePasswordDescription , Strings . Program . WebserverCertificatePasswordDescription ),
2024-08-14 21:20:49 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_INTERFACE , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverInterfaceDescription , Strings . Program . WebserverInterfaceDescription , WebServerLoader . DEFAULT_OPTION_INTERFACE ),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_PASSWORD , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Password , Strings . Program . WebserverPasswordDescription , Strings . Program . WebserverPasswordDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverAllowedhostnamesDescription , Strings . Program . WebserverAllowedhostnamesDescription , null , [ WebServerLoader . OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT ]),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_RESET_JWT_CONFIG , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverResetJwtConfigDescription , Strings . Program . WebserverResetJwtConfigDescription ),
2024-12-12 08:59:59 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverEnableForeverTokenDescription , Strings . Program . WebserverEnableForeverTokenDescription ),
2024-08-15 13:28:54 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_DISABLE_VISUAL_CAPTCHA , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverDisableVisualCaptchaDescription , Strings . Program . WebserverDisableVisualCaptchaDescription ),
2024-09-18 11:07:59 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_API_ONLY , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverApiOnlyDescription , Strings . Program . WebserverApiOnlyDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . WebserverDisableSigninTokensDescription , Strings . Program . WebserverDisableSigninTokensDescription ),
2024-10-02 22:14:37 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_SPAPATHS , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . WebserverSpaPathsDescription , Strings . Program . WebserverSpaPathsDescription , WebServerLoader . DEFAULT_OPTION_SPAPATHS ),
2024-11-05 21:26:30 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_TIMEZONE , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverTimezoneDescription , Strings . Program . WebserverTimezoneDescription , TimeZoneHelper . GetLocalTimeZone (), null , TimeZoneHelper . GetTimeZones (). Select ( x => x . Id ). ToArray ()),
2025-01-18 11:03:52 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_WEBSERVICE_CORS_ORIGINS , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . WebserverCorsOriginsDescription , Strings . Program . WebserverCorsOriginsDescription , WebServerLoader . DEFAULT_OPTION_SPAPATHS ),
2024-08-27 16:24:33 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( PING_PONG_KEEPALIVE_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . PingpongkeepaliveShort , Strings . Program . PingpongkeepaliveLong ),
2025-02-06 11:50:38 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( DISABLE_UPDATE_CHECK_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . DisableupdatecheckShort , Strings . Program . DisableupdatecheckLong ),
2024-08-14 21:20:49 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( "log-retention" , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Timespan , Strings . Program . LogretentionShort , Strings . Program . LogretentionLong , DEFAULT_LOG_RETENTION ),
2025-01-28 13:01:35 +01:00
new Duplicati . Library . Interface . CommandLineArgument ( DataFolderManager . SERVER_DATAFOLDER_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . ServerdatafolderShort , Strings . Program . ServerdatafolderLong ( DataFolderManager . DATAFOLDER_ENV_NAME ), DataFolderManager . DATAFOLDER ),
2024-08-27 16:24:33 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( DISABLE_DB_ENCRYPTION_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . DisabledbencryptionShort , Strings . Program . DisabledbencryptionLong ),
2024-08-30 12:45:39 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( REQUIRE_DB_ENCRYPTION_KEY_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . RequiredbencryptionShort , Strings . Program . RequiredbencryptionLong ),
2024-10-24 15:56:39 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( SETTINGS_ENCRYPTION_KEY_OPTION , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Password , Strings . Program . SettingsencryptionkeyShort , Strings . Program . SettingsencryptionkeyLong ( EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME )),
2024-08-27 16:24:33 +02:00
])
2024-10-24 15:56:39 +02:00
. Concat ( SECRET_PROVIDER_OPTIONS )
2024-08-27 16:24:33 +02:00
. ToArray ();
2018-01-28 20:03:06 +01:00
private static bool ReadOptionsFromFile ( string filename , ref Library . Utility . IFilter filter , List < string > cargs , Dictionary < string , string > options )
{
try
{
2018-09-22 19:51:30 -07:00
List < string > 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 ;
2024-06-07 15:56:43 +02:00
var tmpparsed = Library . Utility . FilterCollector . ExtractOptions ( fargs , ( key , value ) =>
{
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 )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Program . FiltersCannotBeUsedWithFileError2 , "FiltersCannotBeUsedOnCommandLineAndInParameterFile" );
2018-01-28 20:03:06 +01:00
if (! newfilter . Empty )
filter = newfilter ;
if (! string . IsNullOrWhiteSpace ( prependfilter ))
filter = Library . Utility . FilterExpression . Combine ( Library . Utility . FilterExpression . Deserialize ( prependfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries )), filter );
if (! string . IsNullOrWhiteSpace ( appendfilter ))
filter = Library . Utility . FilterExpression . Combine ( filter , Library . Utility . FilterExpression . Deserialize ( appendfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries )));
if (! string . IsNullOrWhiteSpace ( replacefilter ))
filter = Library . Utility . FilterExpression . Deserialize ( replacefilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries ));
foreach ( KeyValuePair < String , String > keyvalue in opt )
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 )
Library . Logging . 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
}
}