2024-03-05 08:55:13 +01:00
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2024-02-28 15:45:30 +01:00
using System ;
2013-02-12 21:43:14 +00:00
using System.Collections.Generic ;
2018-12-11 21:07:30 -08:00
using System.Globalization ;
2013-02-12 21:43:14 +00:00
using System.Linq ;
2024-06-07 15:56:43 +02:00
using System.Threading.Tasks ;
2019-02-14 21:50:28 +01:00
using Duplicati.Library.Common.IO ;
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-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-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
{
2019-02-07 21:57:36 +01:00
2024-08-14 21:20:49 +02:00
private static readonly string [] AlternativeHelpStrings = [ "help" , "/help" , "usage" , "/usage" , "--help" ];
2019-02-07 21:57:36 +01:00
2024-08-14 21:20:49 +02:00
private static readonly string [] ParameterFileOptionStrings = [ "parameters-file" , "parameterfile" ];
2019-02-09 16:06:40 +01:00
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-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
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-07-31 11:13:35 +02:00
/// <summary>
/// Name of the database file
/// </summary>
2024-08-30 13:08:37 +02:00
public const string SERVER_DATABASE_FILENAME = "Duplicati-server.sqlite" ;
2024-07-31 11:13:35 +02:00
2024-08-14 21:20:49 +02:00
/// <summary>
/// The environment variable prefix
/// </summary>
private static readonly string ENV_NAME_PREFIX = Duplicati . Library . AutoUpdater . AutoUpdateSettings . AppName . ToUpper ( CultureInfo . InvariantCulture );
2013-02-12 21:43:14 +00:00
/// <summary>
/// The name of the environment variable that holds the path to the data folder used by Duplicati
/// </summary>
2024-08-14 21:20:49 +02:00
private static readonly string DATAFOLDER_ENV_NAME = ENV_NAME_PREFIX + "_HOME" ;
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
2018-03-09 21:24:57 +01:00
/// <summary>
2018-05-22 21:49:48 +02:00
/// Used to check the origin of the web server (e.g. Tray icon or a stand alone Server)
2018-03-09 21:24:57 +01:00
/// </summary>
2022-12-30 10:59:29 -08:00
public static string Origin { get => FIXMEGlobal . Origin ; set => FIXMEGlobal . Origin = value ; }
2018-03-09 21:24:57 +01: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 ; }
}
2018-03-09 21:24:57 +01:00
public static string StartedBy
{
get { return Origin ; }
set { Origin = value ; }
}
2014-06-23 11:22:54 +02:00
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
2024-08-14 21:20:49 +02:00
if ( _args . Select ( s => s . ToLower ()). Intersect ( AlternativeHelpStrings . Select ( x => x . ToLower ())). Any ())
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 );
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 );
2024-08-30 12:45:39 +02: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
2024-06-09 22:00:34 +02:00
DuplicatiWebserver = StartWebServer ( commandlineOptions , DataConnection ). ConfigureAwait ( false ). GetAwaiter (). GetResult ();
2024-06-07 15:56:43 +02:00
2024-03-15 16:51:01 +01:00
UpdatePoller . Init ();
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
2019-02-25 12:34:32 +01:00
SetLiveControls ();
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 );
2024-08-20 17:10:34 +02:00
2024-08-27 16:24:33 +02:00
Library . Logging . Log . WriteInformationMessage ( LOGTAG , "ServerStarted" , Strings . Program . ServerStarted ( DuplicatiWebserver . Port ));
2024-09-09 11:25:39 +02:00
logMessageToConsole ( Strings . Program . ServerStarted ( 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" ;
var protocol = DataConnection . ApplicationSettings . ServerSSLCertificate != null ? "https" : "http" ;
var url = $"{protocol}://{hostname}:{DuplicatiWebserver.Port}/signin.html?token={signinToken}" ;
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 ();
});
2024-08-27 18:22:53 +02:00
ServerStartedEvent . Set ();
2013-02-12 21:43:14 +00:00
ApplicationExitEvent . WaitOne ();
}
2014-03-21 10:34:34 +01:00
catch ( SingleInstance . MultipleInstanceException mex )
{
2024-08-20 21:11:59 +02:00
crashed = true ;
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 ;
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
{
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 (),
() => LogHandler ?. Dispose ()
};
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 ,
parsedOptions . Certificate ,
parsedOptions . Servername ,
parsedOptions . AllowedHostnames );
var server = new DuplicatiWebserver ();
2024-06-09 22:00:34 +02:00
server . InitWebServer ( mappedSettings , connection );
2024-06-20 14:27:09 +02:00
// Start the server, but catch any configuration issues
var task = server . Start ( mappedSettings );
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-25 12:34:32 +01:00
private static void SetLiveControls ()
{
LiveControl . StateChanged += LiveControl_StateChanged ;
LiveControl . ThreadPriorityChanged += LiveControl_ThreadPriorityChanged ;
LiveControl . ThrottleSpeedChanged += LiveControl_ThrottleSpeedChanged ;
}
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
{
2019-02-14 21:50:28 +01:00
return ;
2019-08-31 13:22:45 -04:00
}
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-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-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 ]);
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 ;
}
}
2019-02-09 16:39:40 +01:00
private static void ConfigureLogging ( Dictionary < string , string > commandlineOptions )
{
//Log various information in the logfile
2024-08-28 12:52:33 +02:00
if ( DEBUG_MODE && ! commandlineOptions . ContainsKey ( "log-file" ))
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" ;
commandlineOptions [ "log-file" ] = System . IO . Path . Combine ( StartupPath , $"Duplicati-{prefix}.debug.log" );
2019-02-09 16:39:40 +01:00
commandlineOptions [ "log-level" ] = Duplicati . Library . Logging . LogMessageType . Profiling . ToString ();
if ( System . IO . File . Exists ( commandlineOptions [ "log-file" ]))
{
System . IO . File . Delete ( commandlineOptions [ "log-file" ]);
}
}
// Setup the log redirect
Library . Logging . Log . StartScope ( LogHandler , null );
if ( commandlineOptions . ContainsKey ( "log-file" ))
{
2024-08-27 16:24:33 +02:00
var loglevel = Library . Logging . LogMessageType . Warning ;
2019-02-09 16:39:40 +01:00
if ( commandlineOptions . ContainsKey ( "log-level" ))
Enum . TryParse ( commandlineOptions [ "log-level" ], true , out loglevel );
LogHandler . SetServerFile ( commandlineOptions [ "log-file" ], loglevel );
}
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 if (! WindowsEventLogSource . SourceExists ( source ))
{
Library . Logging . Log . WriteWarningMessage ( LOGTAG , "WindowsLogMissing" , null , Strings . Program . WindowsEventLogSourceNotFound ( source ));
}
else
{
var loglevel = Library . Logging . LogMessageType . Information ;
if ( commandlineOptions . ContainsKey ( WINDOWS_EVENTLOG_LEVEL_OPTION ))
Enum . TryParse ( commandlineOptions [ WINDOWS_EVENTLOG_LEVEL_OPTION ], true , out loglevel );
LogHandler . AppendLogDestination ( new WindowsEventLogSource ( source ), loglevel );
}
}
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" );
}
2024-08-30 13:08:37 +02:00
public static string GetDataFolderPath ( Dictionary < string , string > commandlineOptions )
2017-05-21 19:25:39 +02:00
{
var serverDataFolder = Environment . GetEnvironmentVariable ( DATAFOLDER_ENV_NAME );
if ( commandlineOptions . ContainsKey ( "server-datafolder" ))
serverDataFolder = commandlineOptions [ "server-datafolder" ];
if ( string . IsNullOrEmpty ( serverDataFolder ))
{
2024-08-13 12:36:18 +02:00
bool portableMode = commandlineOptions . ContainsKey ( "portable-mode" )
? Library . Utility . Utility . ParseBool ( commandlineOptions [ "portable-mode" ], true )
: ( DEBUG_MODE ? true : false ); // Default to portable mode in debug mode
2017-05-21 19:25:39 +02:00
2024-08-13 12:36:18 +02:00
if ( DEBUG_MODE && portableMode )
{
//debug mode uses a lock file located in the app folder
2024-08-30 12:59:49 +02:00
return StartupPath ;
2024-08-13 12:36:18 +02:00
}
else if ( portableMode )
2017-05-21 19:25:39 +02:00
{
//Portable mode uses a data folder in the application home dir
System . IO . Directory . SetCurrentDirectory ( StartupPath );
2024-08-30 12:59:49 +02:00
return System . IO . Path . Combine ( StartupPath , "data" );
2017-05-21 19:25:39 +02:00
}
else
{
2017-06-27 21:35:23 +02:00
//Normal release mode uses the systems "(Local) Application Data" folder
2024-07-31 11:13:35 +02:00
// %LOCALAPPDATA% on Windows, ~/.config on Linux, ~/Library/Application\ Support on MacOS
2024-08-30 12:59:49 +02:00
return DatabaseLocator . GetDefaultStorageFolder ( SERVER_DATABASE_FILENAME , Library . AutoUpdater . AutoUpdateSettings . AppName );
2017-05-21 19:25:39 +02:00
}
2017-09-18 23:23:45 -06:00
}
2017-05-21 19:25:39 +02:00
else
2024-08-30 12:59:49 +02:00
return Util . AppendDirSeparator ( Environment . ExpandEnvironmentVariables ( serverDataFolder ). Trim ( '"' ));
}
2024-09-09 11:25:39 +02:00
public static Database . Connection GetDatabaseConnection ( Dictionary < string , string > commandlineOptions , bool silentConsole )
2024-08-30 12:59:49 +02:00
{
DataFolder = GetDataFolderPath ( commandlineOptions );
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
{
2024-07-31 11:13:35 +02:00
DatabasePath = System . IO . Path . Combine ( DataFolder , 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-08-28 22:25:23 +02:00
var hasEncryptionKey = ! string . IsNullOrWhiteSpace ( Environment . GetEnvironmentVariable ( Library . Encryption . EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME ));
2024-08-31 11:02:14 +02:00
var usingBlacklistedKey = Library . Encryption . EncryptedFieldHelper . IsDefaultKeyBlacklisted ;
var hasValidEncryptionKey = Library . Encryption . EncryptedFieldHelper . HasValidDefaultKey ;
2024-08-30 12:45:39 +02:00
if ( requireDbEncryptionKey && !( hasEncryptionKey || disableDbEncryption ))
2024-08-28 22:25:23 +02:00
throw new UserInformationException ( Strings . Program . DatabaseEncryptionKeyRequired ( Library . Encryption . EncryptedFieldHelper . ENVIROMENT_VARIABLE_NAME , DISABLE_DB_ENCRYPTION_OPTION ), "RequireDbEncryptionKey" );
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
}
}
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-08-28 22:25:23 +02:00
return new Database . Connection ( con , disableDbEncryption );
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>
/// Handles a change in the LiveControl and updates the Runner
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void LiveControl_ThreadPriorityChanged ( object sender , EventArgs e )
2018-05-22 21:49:48 +02:00
{
2013-02-12 21:43:14 +00:00
StatusEventNotifyer . SignalNewEvent ();
}
/// <summary>
/// Handles a change in the LiveControl and updates the Runner
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void LiveControl_ThrottleSpeedChanged ( object sender , EventArgs e )
{
StatusEventNotifyer . SignalNewEvent ();
}
/// <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>
private static void LiveControl_StateChanged ( object sender , EventArgs e )
2013-02-12 21:43:14 +00:00
{
2024-03-15 16:51:01 +01:00
var worker = FIXMEGlobal . WorkThread ;
2013-02-12 21:43:14 +00:00
switch ( LiveControl . State )
{
case LiveControls . LiveControlState . Paused :
2014-05-15 12:47:16 +02:00
{
2024-06-07 15:56:43 +02:00
worker . Pause ();
2024-03-15 16:51:01 +01:00
var t = worker . CurrentTask ;
2019-02-25 12:34:32 +01:00
t ?. Pause ();
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 ();
var t = worker . CurrentTask ;
2019-02-25 12:34:32 +01:00
t ?. Resume ();
2014-05-15 12:47:16 +02:00
break ;
}
2024-03-15 16:51:01 +01:00
default :
throw new InvalidOperationException ( $"State of {nameof(LiveControl)} was not recognized!" );
2013-02-12 21:43:14 +00:00
}
StatusEventNotifyer . SignalNewEvent ();
}
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
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 ),
new Duplicati . Library . Interface . CommandLineArgument ( "portable-mode" , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . PortablemodeCommandDescription , Strings . Program . PortablemodeCommandDescription ),
new Duplicati . Library . Interface . CommandLineArgument ( "log-file" , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . LogfileCommandDescription , Strings . Program . LogfileCommandDescription ),
2024-08-27 16:24:33 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( "log-level" , 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 ))),
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 ),
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 ()),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_SSLCERTIFICATEFILE , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverCertificateFileDescription , Strings . Program . WebserverCertificateFileDescription , WebServerLoader . OPTION_SSLCERTIFICATEFILE ),
new Duplicati . Library . Interface . CommandLineArgument ( WebServerLoader . OPTION_SSLCERTIFICATEFILEPASSWORD , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . WebserverCertificatePasswordDescription , Strings . Program . WebserverCertificatePasswordDescription , WebServerLoader . OPTION_SSLCERTIFICATEFILEPASSWORD ),
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-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-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 ),
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 ),
new Duplicati . Library . Interface . CommandLineArgument ( "server-datafolder" , Duplicati . Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . ServerdatafolderShort , Strings . Program . ServerdatafolderLong ( DATAFOLDER_ENV_NAME ), System . IO . Path . Combine ( System . Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ), Library . AutoUpdater . AutoUpdateSettings . AppName )),
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-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
}
}