2024-02-28 15:45:30 +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.
2022-08-03 12:35:43 -07:00
using System ;
2011-06-06 18:48:52 +00:00
using System.Collections.Generic ;
2017-05-02 21:14:28 +02:00
using System.Net ;
2019-02-11 21:35:59 +01:00
using Duplicati.Library.Common ;
2017-06-29 00:08:02 +02:00
using Duplicati.Library.Interface ;
2011-06-06 18:48:52 +00:00
namespace Duplicati.GUI.TrayIcon
{
2011-11-28 11:05:42 +00:00
public static class Program
2011-06-06 18:48:52 +00:00
{
2018-02-25 13:54:20 +01:00
public enum PasswordSource
{
Database ,
HostedServer
}
2011-11-21 22:28:20 +00:00
public static HttpServerConnection Connection ;
2014-07-07 01:33:19 +02:00
2012-07-14 10:42:48 +00:00
private const string HOSTURL_OPTION = "hosturl" ;
private const string NOHOSTEDSERVER_OPTION = "no-hosted-server" ;
2017-05-02 21:14:28 +02:00
private const string READCONFIGFROMDB_OPTION = "read-config-from-db" ;
2017-10-11 08:57:12 +02:00
private const string DETACHED_PROCESS = "detached-process" ;
2014-03-27 15:24:38 +01:00
private const string BROWSER_COMMAND_OPTION = "browser-command" ;
2012-07-14 10:42:48 +00:00
2017-03-06 14:26:40 +01:00
private const string DEFAULT_HOSTURL = "http://localhost:8200" ;
2011-11-28 11:05:42 +00:00
2014-03-27 15:24:38 +01:00
private static string _browser_command = null ;
2019-02-12 21:50:33 +01:00
private static bool disableTrayIconLogin = false ;
private static bool openui = false ;
private static Uri serverURL = new Uri ( DEFAULT_HOSTURL );
2014-03-27 15:24:38 +01:00
public static string BrowserCommand { get { return _browser_command ; } }
2017-05-02 21:39:04 +02:00
public static Server . Database . Connection databaseConnection = null ;
2017-05-02 21:14:28 +02:00
2011-06-06 18:48:52 +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 )
2011-06-06 18:48:52 +00:00
{
2019-08-03 22:14:41 -04:00
List < string > args = new List < string >( _args );
Dictionary < string , string > options = Duplicati . Library . Utility . CommandLineParser . ExtractOptions ( args );
2014-04-02 11:34:33 +02:00
2024-03-15 14:18:56 +01:00
if ( Platform . IsClientWindows && ! Duplicati . Library . Utility . Utility . ParseBoolOption ( options , DETACHED_PROCESS ))
2019-08-03 22:14:41 -04:00
Duplicati . Library . Utility . Win32 . AttachConsole ( Duplicati . Library . Utility . Win32 . ATTACH_PARENT_PROCESS );
2019-02-12 21:47:17 +01:00
2019-08-03 22:14:41 -04:00
foreach ( string s in args )
if (
s . Equals ( "help" , StringComparison . OrdinalIgnoreCase ) ||
s . Equals ( "/help" , StringComparison . OrdinalIgnoreCase ) ||
s . Equals ( "usage" , StringComparison . OrdinalIgnoreCase ) ||
s . Equals ( "/usage" , StringComparison . OrdinalIgnoreCase ))
options [ "help" ] = "" ;
2014-04-02 11:34:33 +02:00
2019-08-03 22:14:41 -04:00
if ( options . ContainsKey ( "help" ))
{
Console . WriteLine ( "Supported commandline arguments:" );
Console . WriteLine ();
2014-04-02 11:34:33 +02:00
2019-08-03 22:14:41 -04:00
foreach ( Library . Interface . ICommandLineArgument arg in SupportedCommands )
2017-05-23 10:17:55 +02:00
{
2019-08-03 22:14:41 -04:00
Console . WriteLine ( "--{0}: {1}" , arg . Name , arg . LongDescription );
}
2014-04-02 11:34:33 +02:00
2019-08-03 22:14:41 -04:00
Console . WriteLine ( "Additionally, these server options are also supported:" );
Console . WriteLine ();
2014-04-02 11:34:33 +02:00
2019-08-03 22:14:41 -04:00
foreach ( Library . Interface . ICommandLineArgument arg in Duplicati . Server . Program . SupportedCommands )
Console . WriteLine ( "--{0}: {1}" , arg . Name , arg . LongDescription );
2017-06-22 15:10:31 +02:00
2024-03-15 14:18:56 +01:00
return 0 ;
2019-08-03 22:14:41 -04:00
}
2019-02-12 21:47:17 +01:00
2019-08-03 22:14:41 -04:00
options . TryGetValue ( BROWSER_COMMAND_OPTION , out _browser_command );
2019-08-03 22:08:17 -04:00
2019-08-03 22:14:41 -04:00
HostedInstanceKeeper hosted = null ;
string password = null ;
var saltedpassword = false ;
if (! Library . Utility . Utility . ParseBoolOption ( options , NOHOSTEDSERVER_OPTION ))
{
try
{
hosted = new HostedInstanceKeeper ( _args );
2014-03-21 10:34:34 +01:00
}
2019-08-03 22:14:41 -04:00
catch ( Server . SingleInstance . MultipleInstanceException )
{
2024-03-15 14:18:56 +01:00
return 1 ;
2019-08-03 22:14:41 -04:00
}
// We have a hosted server, if this is the first run,
// we should open the main page
openui = Duplicati . Server . Program . IsFirstRun || Duplicati . Server . Program . ServerPortChanged ;
password = Duplicati . Server . Program . DataConnection . ApplicationSettings . WebserverPassword ;
saltedpassword = true ;
// Tell the hosted server it was started by the TrayIcon
Duplicati . Server . Program . Origin = "Tray icon" ;
2018-02-25 13:54:20 +01:00
2019-08-03 22:14:41 -04:00
var cert = Duplicati . Server . Program . DataConnection . ApplicationSettings . ServerSSLCertificate ;
var scheme = "http" ;
2018-02-25 13:54:20 +01:00
2019-08-03 22:14:41 -04:00
if ( cert != null && cert . HasPrivateKey )
scheme = "https" ;
2018-02-25 13:54:20 +01:00
2019-08-03 22:14:41 -04:00
serverURL = ( new UriBuilder ( serverURL )
2018-02-25 13:54:20 +01:00
{
2019-08-03 22:14:41 -04:00
Port = Duplicati . Server . Program . ServerPort ,
Scheme = scheme
}). Uri ;
}
else if ( Library . Utility . Utility . ParseBoolOption ( options , READCONFIGFROMDB_OPTION ))
{
databaseConnection = Server . Program . GetDatabaseConnection ( options );
2017-05-02 21:14:28 +02:00
2019-08-03 22:14:41 -04:00
if ( databaseConnection != null )
{
disableTrayIconLogin = databaseConnection . ApplicationSettings . DisableTrayIconLogin ;
password = databaseConnection . ApplicationSettings . WebserverPasswordTrayIcon ;
saltedpassword = false ;
2017-06-29 22:47:21 +02:00
2019-08-03 22:14:41 -04:00
var cert = databaseConnection . ApplicationSettings . ServerSSLCertificate ;
2019-02-11 21:35:59 +01:00
var scheme = "http" ;
2017-06-29 22:47:21 +02:00
2019-02-11 21:35:59 +01:00
if ( cert != null && cert . HasPrivateKey )
scheme = "https" ;
2017-06-29 22:47:21 +02:00
2019-02-11 21:35:59 +01:00
serverURL = ( new UriBuilder ( serverURL )
2017-06-29 22:47:21 +02:00
{
2019-08-03 22:14:41 -04:00
Port = databaseConnection . ApplicationSettings . LastWebserverPort == - 1 ? serverURL . Port : databaseConnection . ApplicationSettings . LastWebserverPort ,
2017-06-29 22:47:21 +02:00
Scheme = scheme
}). Uri ;
2019-02-11 21:35:59 +01:00
}
2019-08-03 22:14:41 -04:00
}
2019-08-03 22:08:17 -04:00
2019-08-03 22:14:41 -04:00
string pwd ;
2019-08-03 22:08:17 -04:00
2019-08-03 22:14:41 -04:00
if ( options . TryGetValue ( "webserver-password" , out pwd ))
{
password = pwd ;
saltedpassword = false ;
}
2017-05-03 00:56:41 +02:00
2019-08-03 22:14:41 -04:00
string url ;
2019-02-12 21:47:17 +01:00
2019-08-03 22:14:41 -04:00
if ( options . TryGetValue ( HOSTURL_OPTION , out url ))
serverURL = new Uri ( url );
2019-08-03 22:08:17 -04:00
2024-03-04 14:34:38 +01:00
StartTray ( _args , options , hosted , password , saltedpassword );
2024-03-15 14:18:56 +01:00
return 0 ;
2019-02-12 21:47:17 +01:00
}
2024-03-04 14:34:38 +01:00
private static void StartTray ( string [] _args , Dictionary < string , string > options , HostedInstanceKeeper hosted , string password , bool saltedpassword )
2019-02-12 21:47:17 +01:00
{
2019-08-03 22:14:41 -04:00
using ( hosted )
2011-11-21 22:28:20 +00:00
{
2019-08-03 22:14:41 -04:00
var reSpawn = 0 ;
2017-06-28 23:45:56 +02:00
2019-08-03 22:14:41 -04:00
do
{
try
2019-08-03 22:08:17 -04:00
{
2022-03-19 14:25:51 +01:00
ServicePointManager . SecurityProtocol = SecurityProtocolType . Tls12 ;
try
{
//try TLS 1.3 (type not available on .NET < 4.8)
ServicePointManager . SecurityProtocol = SecurityProtocolType . Tls12 | ( SecurityProtocolType ) 12288 ;
}
catch ( NotSupportedException )
{
}
2017-05-02 21:14:28 +02:00
2019-08-03 22:14:41 -04:00
using ( Connection = new HttpServerConnection ( serverURL , password , saltedpassword , databaseConnection != null ? PasswordSource . Database : PasswordSource . HostedServer , disableTrayIconLogin , options ))
{
2024-03-04 14:34:38 +01:00
using ( var tk = RunTrayIcon ())
2019-08-03 22:08:17 -04:00
{
2019-08-03 22:14:41 -04:00
if ( hosted != null && Server . Program . ApplicationInstance != null )
Server . Program . ApplicationInstance . SecondInstanceDetected +=
new Server . SingleInstance . SecondInstanceDelegate (
x => { tk . ShowUrlInWindow ( serverURL . ToString ()); });
// TODO: If we change to hosted browser this should be a callback
if ( openui )
2017-05-02 21:14:28 +02:00
{
2019-08-03 22:14:41 -04:00
try
{
tk . ShowUrlInWindow ( Connection . StatusWindowURL );
2017-05-02 21:14:28 +02:00
2019-08-03 22:14:41 -04:00
Duplicati . Server . Program . IsFirstRun = false ;
Duplicati . Server . Program . ServerPortChanged = false ;
}
catch
2017-05-02 21:14:28 +02:00
{
}
2019-08-03 22:14:41 -04:00
}
2017-05-02 21:14:28 +02:00
2019-08-03 22:14:41 -04:00
// If the server shuts down, shut down the tray-icon as well
Action shutdownEvent = () =>
{
// Make sure we do not start again after
// a controlled exit
reSpawn = 100 ;
tk . InvokeExit ();
};
2017-05-02 21:14:28 +02:00
2019-08-03 22:14:41 -04:00
if ( hosted != null )
hosted . InstanceShutdown += shutdownEvent ;
2017-05-02 21:14:28 +02:00
2019-08-03 22:14:41 -04:00
tk . Init ( _args );
2017-05-02 21:14:28 +02:00
2019-08-03 22:14:41 -04:00
// If the tray-icon quits, stop the server
reSpawn = 100 ;
2017-06-27 15:05:46 +02:00
2019-08-03 22:14:41 -04:00
// Make sure that the server shutdown does not access the tray-icon,
// as it would be disposed by now
if ( hosted != null )
hosted . InstanceShutdown -= shutdownEvent ;
2017-05-02 21:14:28 +02:00
}
}
2019-08-03 22:14:41 -04:00
}
catch ( WebException ex )
{
System . Diagnostics . Trace . WriteLine ( "Request error: " + ex );
Console . WriteLine ( "Request error: " + ex );
2017-05-03 00:03:16 +02:00
2019-08-03 22:14:41 -04:00
reSpawn ++;
}
catch ( Exception ex )
{
System . Diagnostics . Trace . WriteLine ( "Unexpected error: " + ex );
Console . WriteLine ( "Unexpected error: " + ex );
return ;
}
} while ( reSpawn < 3 );
2017-05-02 21:14:28 +02:00
}
}
2024-03-04 14:34:38 +01:00
private static TrayIconBase RunTrayIcon ()
=> new AvaloniaRunner ();
2011-11-28 11:05:42 +00:00
2011-11-23 20:52:28 +00:00
public static Duplicati . Library . Interface . ICommandLineArgument [] SupportedCommands
{
get
{
2017-10-11 08:57:12 +02:00
var args = new List < Duplicati . Library . Interface . ICommandLineArgument >()
2011-11-23 20:52:28 +00:00
{
2012-07-14 10:42:48 +00:00
new Duplicati . Library . Interface . CommandLineArgument ( HOSTURL_OPTION , CommandLineArgument . ArgumentType . String , "Selects the url to connect to" , "Supply the url that the TrayIcon will connect to and show status for" , DEFAULT_HOSTURL ),
new Duplicati . Library . Interface . CommandLineArgument ( NOHOSTEDSERVER_OPTION , CommandLineArgument . ArgumentType . String , "Disables local server" , "Set this option to not spawn a local service, use if the TrayIcon should connect to a running service" ),
2017-05-02 21:14:28 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( READCONFIGFROMDB_OPTION , CommandLineArgument . ArgumentType . String , "Read server connection info from DB" , $"Set this option to read server connection info for running service from its database (only together with {NOHOSTEDSERVER_OPTION})" ),
2017-10-11 08:57:12 +02:00
new Duplicati . Library . Interface . CommandLineArgument ( BROWSER_COMMAND_OPTION , CommandLineArgument . ArgumentType . String , "Sets the browser command" , "Set this option to override the default browser detection" ),
2011-11-23 20:52:28 +00:00
};
2017-10-11 08:57:12 +02:00
2018-11-02 22:13:25 +01:00
if ( Platform . IsClientWindows )
2017-10-11 08:57:12 +02:00
{
args . Add ( new Duplicati . Library . Interface . CommandLineArgument ( DETACHED_PROCESS , CommandLineArgument . ArgumentType . String , "Runs the tray-icon detached" , "This option runs the tray-icon in detached mode, meaning that the process will exit immediately and not send output to the console of the caller" ));
}
return args . ToArray ();
2011-11-23 20:52:28 +00:00
}
}
2011-06-06 18:48:52 +00:00
}
}