private static void StartWebServer(Dictionary <string, string> commandlineOptions) { WebServer = new WebServer.Server(commandlineOptions); ServerPortChanged |= WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort; DataConnection.ApplicationSettings.LastWebserverPort = WebServer.Port; }
public static void RealMain(string[] args) { //If we are on Windows, append the bundled "win-tools" programs to the search path //We add it last, to allow the user to override with other versions if (!Library.Utility.Utility.IsClientLinux) { Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH") + System.IO.Path.PathSeparator.ToString() + System.IO.Path.Combine( System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "win-tools") ); } //If this executable is invoked directly, write to console, otherwise throw exceptions bool writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly(); //If we are on windows we encrypt the database by default //We do not encrypt on Linux as most distros use a SQLite library without encryption support, //Linux users can use an encrypted home folder, or install a SQLite library with encryption support if (!Library.Utility.Utility.IsClientLinux && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DB_KEY_ENV_NAME))) { //Note that the password here is a default password and public knowledge // //The purpose of this is to prevent casual read of the database, as well // as protect from harddisk string scans, not to protect from determined // attacks. // //If you desire better security, start Duplicati once with the commandline option // --unencrypted-database to decrypt the database. //Then set the environment variable DUPLICATI_DB_KEY to the desired key, // and run Duplicati again without the --unencrypted-database option // to re-encrypt it with the new key // //If you change the key, please note that you need to supply the same // key when restoring the setup, as the setup being backed up will // be encrypted as well. Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, Library.AutoUpdater.AutoUpdateSettings.AppName + "_Key_42"); } //Find commandline options here for handling special startup cases Dictionary <string, string> commandlineOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List <string>(args)); foreach (string s in args) { if ( s.Equals("help", StringComparison.InvariantCultureIgnoreCase) || s.Equals("/help", StringComparison.InvariantCultureIgnoreCase) || s.Equals("usage", StringComparison.InvariantCultureIgnoreCase) || s.Equals("/usage", StringComparison.InvariantCultureIgnoreCase)) { commandlineOptions["help"] = ""; } } //If the commandline issues --help, just stop here if (commandlineOptions.ContainsKey("help")) { if (writeConsole) { Console.WriteLine(Strings.Program.HelpDisplayDialog); foreach (Library.Interface.ICommandLineArgument arg in SupportedCommands) { Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription)); } return; } else { throw new Exception("Server invoked with --help"); } } #if DEBUG //Log various information in the logfile if (!commandlineOptions.ContainsKey("log-file")) { commandlineOptions["log-file"] = System.IO.Path.Combine(StartupPath, "Duplicati.debug.log"); commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString(); } #endif // Allow override of the environment variables from the commandline if (commandlineOptions.ContainsKey("server-datafolder")) { Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, commandlineOptions["server-datafolder"]); } if (commandlineOptions.ContainsKey("server-encryption-key")) { Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, commandlineOptions["server-encryption-key"]); } //Set the %DUPLICATI_HOME% env variable, if it is not already set if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME))) { #if DEBUG //debug mode uses a lock file located in the app folder Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, StartupPath); #else bool portableMode = commandlineOptions.ContainsKey("portable-mode") ? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true) : false; if (portableMode) { //Portable mode uses a data folder in the application home dir Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(StartupPath, "data")); } else { //Normal release mode uses the systems "Application Data" folder Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName)); } #endif } try { try { //This will also create Program.DATAFOLDER if it does not exist Instance = new SingleInstance(Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName, Program.DATAFOLDER); } catch (Exception ex) { if (writeConsole) { Console.WriteLine(Strings.Program.StartupFailure(ex)); return; } else { throw new Exception(Strings.Program.StartupFailure(ex)); } } if (!Instance.IsFirstInstance) { if (writeConsole) { Console.WriteLine(Strings.Program.AnotherInstanceDetected); return; } else { throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected); } } // Setup the log redirect Duplicati.Library.Logging.Log.CurrentLog = Program.LogHandler; if (commandlineOptions.ContainsKey("log-file")) { if (System.IO.File.Exists(commandlineOptions["log-file"])) { System.IO.File.Delete(commandlineOptions["log-file"]); } var loglevel = Duplicati.Library.Logging.LogMessageType.Error; if (commandlineOptions.ContainsKey("log-level")) { Enum.TryParse <Duplicati.Library.Logging.LogMessageType>(commandlineOptions["log-level"], true, out loglevel); } Program.LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel); } Version sqliteVersion = new Version((string)Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType.GetProperty("SQLiteVersion").GetValue(null, null)); if (sqliteVersion < new Version(3, 6, 3)) { if (writeConsole) { //The official Mono SQLite provider is also broken with less than 3.6.3 Console.WriteLine(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3")); return; } else { throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3")); } } //Create the connection instance System.Data.IDbConnection con = (System.Data.IDbConnection)Activator.CreateInstance(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType); try { DatabasePath = System.IO.Path.Combine(Program.DATAFOLDER, "Duplicati-server.sqlite"); if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DatabasePath))) { System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(DatabasePath)); } #if DEBUG //Default is to not use encryption for debugging Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : false; #else Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : true; #endif con.ConnectionString = "Data Source=" + DatabasePath; //Attempt to open the database, handling any encryption present OpenDatabase(con); Duplicati.Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Duplicati.Server.Database.Connection)); } catch (Exception ex) { //Unwrap the reflection exceptions if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } if (writeConsole) { Console.WriteLine(Strings.Program.DatabaseOpenError(ex.Message)); return; } else { throw new Exception(Strings.Program.DatabaseOpenError(ex.Message), ex); } } DataConnection = new Duplicati.Server.Database.Connection(con); if (!DataConnection.ApplicationSettings.FixedInvalidBackupId) { DataConnection.FixInvalidBackupId(); } StartOrStopUsageReporter(); if (commandlineOptions.ContainsKey("webservice-password")) { Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]); } ApplicationExitEvent = new System.Threading.ManualResetEvent(false); Duplicati.Library.AutoUpdater.UpdaterManager.OnError += (Exception obj) => { Program.DataConnection.LogError(null, "Error in updater", obj); }; UpdatePoller = new UpdatePollThread(); DateTime lastPurge = new DateTime(0); System.Threading.TimerCallback purgeTempFilesCallback = (x) => { try { if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < 23) { return; } lastPurge = DateTime.Now; foreach (var e in Program.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) { Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", e.Path), ex); } Program.DataConnection.DeleteTempFile(e.ID); } Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => { Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", path), ex); }); string pts; if (!commandlineOptions.TryGetValue("log-retention", out pts)) { pts = DEFAULT_LOG_RETENTION; } Program.DataConnection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true)); } catch (Exception ex) { Program.DataConnection.LogError(null, "Failed during temp file cleanup", ex); } }; try { PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1)); } catch (ArgumentOutOfRangeException) { //Bugfix for older Mono, slightly more resources used to avoid large values in the period field PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); } LiveControl = new LiveControls(DataConnection.ApplicationSettings); LiveControl.StateChanged += new EventHandler(LiveControl_StateChanged); LiveControl.ThreadPriorityChanged += new EventHandler(LiveControl_ThreadPriorityChanged); LiveControl.ThrottleSpeedChanged += new EventHandler(LiveControl_ThrottleSpeedChanged); Program.WorkThread = new Duplicati.Library.Utility.WorkerThread <Runner.IRunnerData>((x) => { Runner.Run(x, true); }, LiveControl.State == LiveControls.LiveControlState.Paused); Program.Scheduler = new Scheduler(WorkThread); Program.WorkThread.StartingWork += (worker, task) => { SignalNewEvent(null, null); }; Program.WorkThread.CompletedWork += (worker, task) => { SignalNewEvent(null, null); }; Program.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); }; Program.Scheduler.NewSchedule += new EventHandler(SignalNewEvent); Program.WorkThread.OnError += (worker, task, exception) => { Program.DataConnection.LogError(task == null ? null : task.BackupID, "Error in worker", exception); }; Action <long, Exception> registerTaskResult = (id, ex) => { lock (Program.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); } } }; Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); }; Program.WorkThread.OnError += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); }; Program.WebServer = new WebServer.Server(commandlineOptions); if (Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort) { ServerPortChanged = true; } DataConnection.ApplicationSettings.LastWebserverPort = Program.WebServer.Port; if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, "ping-pong-keepalive")) { Program.PingPongThread = new System.Threading.Thread(PingPongMethod); Program.PingPongThread.IsBackground = true; Program.PingPongThread.Start(); } ServerStartedEvent.Set(); ApplicationExitEvent.WaitOne(); } catch (SingleInstance.MultipleInstanceException mex) { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString())); if (writeConsole) { Console.WriteLine(Strings.Program.SeriousError(mex.ToString())); } else { throw mex; } } catch (Exception ex) { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString())); if (writeConsole) { Console.WriteLine(Strings.Program.SeriousError(ex.ToString())); } else { throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex); } } finally { StatusEventNotifyer.SignalNewEvent(); if (UpdatePoller != null) { UpdatePoller.Terminate(); } if (Scheduler != null) { Scheduler.Terminate(true); } if (WorkThread != null) { WorkThread.Terminate(true); } if (Instance != null) { Instance.Dispose(); } if (PurgeTempFilesTimer != null) { PurgeTempFilesTimer.Dispose(); } if (PingPongThread != null) { try { PingPongThread.Abort(); } catch { } } if (LogHandler != null) { LogHandler.Dispose(); } } }
public static int RealMain(string[] _args) { //If we are on Windows, append the bundled "win-tools" programs to the search path //We add it last, to allow the user to override with other versions if (Platform.IsClientWindows) { Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH") + System.IO.Path.PathSeparator.ToString() + System.IO.Path.Combine( System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "win-tools") ); } //If this executable is invoked directly, write to console, otherwise throw exceptions var writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly(); //Find commandline options here for handling special startup cases var args = new List <string>(_args); var argtuple = Library.Utility.FilterCollector.ExtractOptions(new List <string>(args)); var commandlineOptions = argtuple.Item1; var filter = argtuple.Item2; foreach (var s in _args) { if ( s.Equals("help", StringComparison.OrdinalIgnoreCase) || s.Equals("/help", StringComparison.OrdinalIgnoreCase) || s.Equals("usage", StringComparison.OrdinalIgnoreCase) || s.Equals("/usage", StringComparison.OrdinalIgnoreCase)) { commandlineOptions["help"] = ""; } } if (commandlineOptions.ContainsKey("tempdir") && !string.IsNullOrEmpty(commandlineOptions["tempdir"])) { Library.Utility.SystemContextSettings.DefaultTempPath = commandlineOptions["tempdir"]; } Library.Utility.SystemContextSettings.StartSession(); // Check if a parameters-file was provided. Skip if help was already specified if (!commandlineOptions.ContainsKey("help")) { // try and parse all parameter file aliases foreach (string parameterOption in new[] { "parameters-file", "parameters-file", "parameterfile" }) { if (commandlineOptions.ContainsKey(parameterOption) && !string.IsNullOrEmpty(commandlineOptions[parameterOption])) { string filename = commandlineOptions[parameterOption]; commandlineOptions.Remove(parameterOption); if (!ReadOptionsFromFile(filename, ref filter, args, commandlineOptions)) { return(100); } break; } } } //If the commandline issues --help, just stop here if (commandlineOptions.ContainsKey("help")) { if (writeConsole) { 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"); } #if DEBUG //Log various information in the logfile if (!commandlineOptions.ContainsKey("log-file")) { commandlineOptions["log-file"] = System.IO.Path.Combine(StartupPath, "Duplicati.debug.log"); commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString(); } #endif try { // Setup the log redirect var logscope = Library.Logging.Log.StartScope(Program.LogHandler, null); if (commandlineOptions.ContainsKey("log-file")) { #if DEBUG // Only delete the --log-file when in DEBUG mode. Otherwise, don't delete and just append logs. if (System.IO.File.Exists(commandlineOptions["log-file"])) { System.IO.File.Delete(commandlineOptions["log-file"]); } #endif var loglevel = Duplicati.Library.Logging.LogMessageType.Error; if (commandlineOptions.ContainsKey("log-level")) { Enum.TryParse <Duplicati.Library.Logging.LogMessageType>(commandlineOptions["log-level"], true, out loglevel); } Program.LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel); } DataConnection = GetDatabaseConnection(commandlineOptions); if (!DataConnection.ApplicationSettings.FixedInvalidBackupId) { DataConnection.FixInvalidBackupId(); } try { //This will also create DATAFOLDER if it does not exist Instance = new SingleInstance(DataFolder); } catch (Exception ex) { if (writeConsole) { Console.WriteLine(Strings.Program.StartupFailure(ex)); return(200); } throw new Exception(Strings.Program.StartupFailure(ex)); } if (!Instance.IsFirstInstance) { if (writeConsole) { Console.WriteLine(Strings.Program.AnotherInstanceDetected); return(200); } throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected); } StartOrStopUsageReporter(); if (commandlineOptions.ContainsKey("webservice-password")) { Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]); } Program.DataConnection.ApplicationSettings.GenerateWebserverPasswordTrayIcon(); if (commandlineOptions.ContainsKey("webservice-allowed-hostnames")) { Program.DataConnection.ApplicationSettings.SetAllowedHostnames(commandlineOptions["webservice-allowed-hostnames"]); } ApplicationExitEvent = new System.Threading.ManualResetEvent(false); Duplicati.Library.AutoUpdater.UpdaterManager.OnError += (Exception obj) => { Program.DataConnection.LogError(null, "Error in updater", obj); }; UpdatePoller = new UpdatePollThread(); DateTime lastPurge = new DateTime(0); System.Threading.TimerCallback purgeTempFilesCallback = (x) => { try { if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < 23) { return; } lastPurge = DateTime.Now; foreach (var e in Program.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) { Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", e.Path), ex); } Program.DataConnection.DeleteTempFile(e.ID); } Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => { Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", path), ex); }); string pts; if (!commandlineOptions.TryGetValue("log-retention", out pts)) { pts = DEFAULT_LOG_RETENTION; } Program.DataConnection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true)); } catch (Exception ex) { Program.DataConnection.LogError(null, "Failed during temp file cleanup", ex); } }; try { PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1)); } catch (ArgumentOutOfRangeException) { //Bugfix for older Mono, slightly more resources used to avoid large values in the period field PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); } LiveControl = new LiveControls(DataConnection.ApplicationSettings); LiveControl.StateChanged += new EventHandler(LiveControl_StateChanged); LiveControl.ThreadPriorityChanged += new EventHandler(LiveControl_ThreadPriorityChanged); LiveControl.ThrottleSpeedChanged += new EventHandler(LiveControl_ThrottleSpeedChanged); Program.WorkThread = new Duplicati.Library.Utility.WorkerThread <Runner.IRunnerData>((x) => { Runner.Run(x, true); }, LiveControl.State == LiveControls.LiveControlState.Paused); Program.Scheduler = new Scheduler(WorkThread); Program.WorkThread.StartingWork += (worker, task) => { SignalNewEvent(null, null); }; Program.WorkThread.CompletedWork += (worker, task) => { SignalNewEvent(null, null); }; Program.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); }; Program.Scheduler.NewSchedule += new EventHandler(SignalNewEvent); Program.WorkThread.OnError += (worker, task, exception) => { Program.DataConnection.LogError(task?.BackupID, "Error in worker", exception); }; var lastscheduleid = LastDataUpdateID; Program.StatusEventNotifyer.NewEvent += (sender, e) => { if (lastscheduleid != LastDataUpdateID) { lastscheduleid = LastDataUpdateID; Program.Scheduler.Reschedule(); } }; Action <long, Exception> registerTaskResult = (id, ex) => { lock (Program.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); } } }; Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); }; Program.WorkThread.OnError += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); }; Program.WebServer = new WebServer.Server(commandlineOptions); ServerPortChanged |= Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort; DataConnection.ApplicationSettings.LastWebserverPort = Program.WebServer.Port; if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, "ping-pong-keepalive")) { Program.PingPongThread = new System.Threading.Thread(PingPongMethod); Program.PingPongThread.IsBackground = true; Program.PingPongThread.Start(); } ServerStartedEvent.Set(); ApplicationExitEvent.WaitOne(); } catch (SingleInstance.MultipleInstanceException mex) { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString())); if (writeConsole) { Console.WriteLine(Strings.Program.SeriousError(mex.ToString())); return(100); } else { throw mex; } } catch (Exception ex) { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString())); if (writeConsole) { Console.WriteLine(Strings.Program.SeriousError(ex.ToString())); return(100); } else { throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex); } } finally { StatusEventNotifyer.SignalNewEvent(); if (UpdatePoller != null) { UpdatePoller.Terminate(); } if (Scheduler != null) { Scheduler.Terminate(true); } if (WorkThread != null) { WorkThread.Terminate(true); } if (Instance != null) { Instance.Dispose(); } if (PurgeTempFilesTimer != null) { PurgeTempFilesTimer.Dispose(); } Library.UsageReporter.Reporter.ShutDown(); if (PingPongThread != null) { try { PingPongThread.Abort(); } catch { } } if (LogHandler != null) { LogHandler.Dispose(); } } if (UpdatePoller != null && UpdatePoller.IsUpdateRequested) { return(Library.AutoUpdater.UpdaterManager.MAGIC_EXIT_CODE); } return(0); }
public static void RealMain(string[] args) { //If we are on Windows, append the bundled "win-tools" programs to the search path //We add it last, to allow the user to override with other versions if (!Library.Utility.Utility.IsClientLinux) { Environment.SetEnvironmentVariable("PATH", Environment.GetEnvironmentVariable("PATH") + System.IO.Path.PathSeparator.ToString() + System.IO.Path.Combine( System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "win-tools") ); } //If this executable is invoked directly, write to console, otherwise throw exceptions bool writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly(); //If we are on windows we encrypt the database by default //We do not encrypt on Linux as most distros use a SQLite library without encryption support, //Linux users can use an encrypted home folder, or install a SQLite library with encryption support if (!Library.Utility.Utility.IsClientLinux && string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DB_KEY_ENV_NAME))) { //Note that the password here is a default password and public knowledge // //The purpose of this is to prevent casual read of the database, as well // as protect from harddisk string scans, not to protect from determined // attacks. // //If you desire better security, start Duplicati once with the commandline option // --unencrypted-database to decrypt the database. //Then set the environment variable DUPLICATI_DB_KEY to the desired key, // and run Duplicati again without the --unencrypted-database option // to re-encrypt it with the new key // //If you change the key, please note that you need to supply the same // key when restoring the setup, as the setup being backed up will // be encrypted as well. Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, Library.AutoUpdater.AutoUpdateSettings.AppName + "_Key_42"); } //Find commandline options here for handling special startup cases Dictionary<string, string> commandlineOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List<string>(args)); foreach(string s in args) if ( s.Equals("help", StringComparison.InvariantCultureIgnoreCase) || s.Equals("/help", StringComparison.InvariantCultureIgnoreCase) || s.Equals("usage", StringComparison.InvariantCultureIgnoreCase) || s.Equals("/usage", StringComparison.InvariantCultureIgnoreCase)) commandlineOptions["help"] = ""; //If the commandline issues --help, just stop here if (commandlineOptions.ContainsKey("help")) { if (writeConsole) { Console.WriteLine(Strings.Program.HelpDisplayDialog); foreach(Library.Interface.ICommandLineArgument arg in SupportedCommands) Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription)); return; } else { throw new Exception("Server invoked with --help"); } } #if DEBUG //Log various information in the logfile if (!commandlineOptions.ContainsKey("log-file")) { commandlineOptions["log-file"] = System.IO.Path.Combine(StartupPath, "Duplicati.debug.log"); commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString(); } #endif // Allow override of the environment variables from the commandline if (commandlineOptions.ContainsKey("server-datafolder")) Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, commandlineOptions["server-datafolder"]); if (commandlineOptions.ContainsKey("server-encryption-key")) Environment.SetEnvironmentVariable(DB_KEY_ENV_NAME, commandlineOptions["server-encryption-key"]); //Set the %DUPLICATI_HOME% env variable, if it is not already set if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME))) { #if DEBUG //debug mode uses a lock file located in the app folder Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, StartupPath); #else bool portableMode = commandlineOptions.ContainsKey("portable-mode") ? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true) : false; if (portableMode) { //Portable mode uses a data folder in the application home dir Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(StartupPath, "data")); } else { //Normal release mode uses the systems "Application Data" folder Environment.SetEnvironmentVariable(DATAFOLDER_ENV_NAME, System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName)); } #endif } try { try { //This will also create Program.DATAFOLDER if it does not exist Instance = new SingleInstance(Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName, Program.DATAFOLDER); } catch (Exception ex) { if (writeConsole) { Console.WriteLine(Strings.Program.StartupFailure(ex)); return; } else { throw new Exception(Strings.Program.StartupFailure(ex)); } } if (!Instance.IsFirstInstance) { if (writeConsole) { Console.WriteLine(Strings.Program.AnotherInstanceDetected); return; } else { throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected); } } // Setup the log redirect Duplicati.Library.Logging.Log.CurrentLog = Program.LogHandler; if (commandlineOptions.ContainsKey("log-file")) { if (System.IO.File.Exists(commandlineOptions["log-file"])) System.IO.File.Delete(commandlineOptions["log-file"]); var loglevel = Duplicati.Library.Logging.LogMessageType.Error; if (commandlineOptions.ContainsKey("log-level")) Enum.TryParse<Duplicati.Library.Logging.LogMessageType>(commandlineOptions["log-level"], true, out loglevel); Program.LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel); } Version sqliteVersion = new Version((string)Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType.GetProperty("SQLiteVersion").GetValue(null, null)); if (sqliteVersion < new Version(3, 6, 3)) { if (writeConsole) { //The official Mono SQLite provider is also broken with less than 3.6.3 Console.WriteLine(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3")); return; } else { throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3")); } } //Create the connection instance System.Data.IDbConnection con = (System.Data.IDbConnection)Activator.CreateInstance(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteConnectionType); try { DatabasePath = System.IO.Path.Combine(Program.DATAFOLDER, "Duplicati-server.sqlite"); if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DatabasePath))) System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(DatabasePath)); #if DEBUG //Default is to not use encryption for debugging Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : false; #else Program.UseDatabaseEncryption = commandlineOptions.ContainsKey("unencrypted-database") ? !Library.Utility.Utility.ParseBool(commandlineOptions["unencrypted-database"], true) : true; #endif con.ConnectionString = "Data Source=" + DatabasePath; //Attempt to open the database, handling any encryption present OpenDatabase(con); Duplicati.Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Duplicati.Server.Database.Connection)); } catch (Exception ex) { //Unwrap the reflection exceptions if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) ex = ex.InnerException; if (writeConsole) { Console.WriteLine(Strings.Program.DatabaseOpenError(ex.Message)); return; } else { throw new Exception(Strings.Program.DatabaseOpenError(ex.Message), ex); } } DataConnection = new Duplicati.Server.Database.Connection(con); if (!DataConnection.ApplicationSettings.FixedInvalidBackupId) DataConnection.FixInvalidBackupId(); if (commandlineOptions.ContainsKey("webservice-password")) Program.DataConnection.ApplicationSettings.SetWebserverPassword(commandlineOptions["webservice-password"]); ApplicationExitEvent = new System.Threading.ManualResetEvent(false); Duplicati.Library.AutoUpdater.UpdaterManager.OnError += (Exception obj) => { Program.DataConnection.LogError(null, "Error in updater", obj); }; UpdatePoller = new UpdatePollThread(); DateTime lastPurge = new DateTime(0); System.Threading.TimerCallback purgeTempFilesCallback = (x) => { try { if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < 23) return; lastPurge = DateTime.Now; foreach(var e in Program.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) { Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", e.Path), ex); } Program.DataConnection.DeleteTempFile(e.ID); } Duplicati.Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => { Program.DataConnection.LogError(null, string.Format("Failed to delete temp file: {0}", path), ex); }); string pts; if (!commandlineOptions.TryGetValue("log-retention", out pts)) pts = DEFAULT_LOG_RETENTION; Program.DataConnection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true)); } catch (Exception ex) { Program.DataConnection.LogError(null, "Failed during temp file cleanup", ex); } }; try { PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromDays(1)); } catch (ArgumentOutOfRangeException) { //Bugfix for older Mono, slightly more resources used to avoid large values in the period field PurgeTempFilesTimer = new System.Threading.Timer(purgeTempFilesCallback, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); } LiveControl = new LiveControls(DataConnection.ApplicationSettings); LiveControl.StateChanged += new EventHandler(LiveControl_StateChanged); LiveControl.ThreadPriorityChanged += new EventHandler(LiveControl_ThreadPriorityChanged); LiveControl.ThrottleSpeedChanged += new EventHandler(LiveControl_ThrottleSpeedChanged); Program.WorkThread = new Duplicati.Library.Utility.WorkerThread<Runner.IRunnerData>((x) => { Runner.Run(x, true); }, LiveControl.State == LiveControls.LiveControlState.Paused); Program.Scheduler = new Scheduler(WorkThread); Program.WorkThread.StartingWork += (worker, task) => { SignalNewEvent(null, null); }; Program.WorkThread.CompletedWork += (worker, task) => { SignalNewEvent(null, null); }; Program.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); }; Program.Scheduler.NewSchedule += new EventHandler(SignalNewEvent); Program.WorkThread.OnError += (worker, task, exception) => { Program.DataConnection.LogError(task == null ? null : task.BackupID, "Error in worker", exception); }; Action<long, Exception> registerTaskResult = (id, ex) => { lock(Program.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); } }; Program.WorkThread.CompletedWork += (worker, task) => { registerTaskResult(task.TaskID, null); }; Program.WorkThread.OnError += (worker, task, exception) => { registerTaskResult(task.TaskID, exception); }; Program.WebServer = new WebServer.Server(commandlineOptions); if (Program.WebServer.Port != DataConnection.ApplicationSettings.LastWebserverPort) ServerPortChanged = true; DataConnection.ApplicationSettings.LastWebserverPort = Program.WebServer.Port; if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, "ping-pong-keepalive")) { Program.PingPongThread = new System.Threading.Thread(PingPongMethod); Program.PingPongThread.IsBackground = true; Program.PingPongThread.Start(); } ServerStartedEvent.Set(); ApplicationExitEvent.WaitOne(); } catch (SingleInstance.MultipleInstanceException mex) { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString())); if (writeConsole) Console.WriteLine(Strings.Program.SeriousError(mex.ToString())); else throw mex; } catch (Exception ex) { System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString())); if (writeConsole) Console.WriteLine(Strings.Program.SeriousError(ex.ToString())); else throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex); } finally { StatusEventNotifyer.SignalNewEvent(); if (UpdatePoller != null) UpdatePoller.Terminate(); if (Scheduler != null) Scheduler.Terminate(true); if (WorkThread != null) WorkThread.Terminate(true); if (Instance != null) Instance.Dispose(); if (PurgeTempFilesTimer != null) PurgeTempFilesTimer.Dispose(); if (PingPongThread != null) try { PingPongThread.Abort(); } catch { } if (LogHandler != null) LogHandler.Dispose(); } }