2024-03-15 14:18:56 +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
2008-09-13 16:24:43 +00:00
using System ;
using System.Collections.Generic ;
2013-05-13 22:32:05 +02:00
using System.Linq ;
2014-08-17 15:45:44 +02:00
using Duplicati.Library.Localization.Short ;
2017-04-04 16:56:34 +02:00
using System.IO ;
2019-09-29 20:16:28 -07:00
using Duplicati.Library.Interface ;
2024-04-15 13:17:49 +02:00
using Duplicati.Library.AutoUpdater ;
2019-07-18 12:15:01 -04:00
2008-09-13 16:24:43 +00:00
namespace Duplicati.CommandLine
{
2015-04-10 22:29:52 +02:00
public class Program
2008-09-13 16:24:43 +00:00
{
2018-05-23 21:18:01 -07:00
private static readonly string LOGTAG = Library . Logging . Log . LogTagFromType < Program >();
2018-03-12 14:07:11 +01:00
2017-04-04 16:56:34 +02:00
public static bool FROM_COMMANDLINE = false ;
2014-06-26 22:37:58 +02:00
/// <summary>
/// The main entry point for the application.
/// </summary>
public static int Main ( string [] args )
2008-09-13 16:24:43 +00:00
{
2015-12-22 00:35:00 +01:00
Library . UsageReporter . Reporter . Initialize ();
2017-04-04 16:56:34 +02:00
FROM_COMMANDLINE = true ;
2010-05-24 09:00:03 +00:00
try
{
2017-04-04 16:56:34 +02:00
return RunCommandLine ( Console . Out , Console . Error , c => { }, args );
}
finally
{
Library . UsageReporter . Reporter . ShutDown ();
}
}
private static Dictionary < string , Func < TextWriter , Action < Library . Main . Controller >, List < string >, Dictionary < string , string >, Library . Utility . IFilter , int >> CommandMap
{
get
{
2019-07-25 11:14:45 -04:00
var knownCommands =
new Dictionary < string , Func < TextWriter , Action < Library . Main . Controller >, List < string >,
Dictionary < string , string >, Library . Utility . IFilter , int >>( StringComparer . OrdinalIgnoreCase )
{
["help"] = Commands . Help ,
["example"] = Commands . Examples ,
["examples"] = Commands . Examples ,
["find"] = Commands . List ,
["list"] = Commands . List ,
["delete"] = Commands . Delete ,
["backup"] = Commands . Backup ,
["restore"] = Commands . Restore ,
["repair"] = Commands . Repair ,
["purge"] = Commands . PurgeFiles ,
["list-broken-files"] = Commands . ListBrokenFiles ,
["purge-broken-files"] = Commands . PurgeBrokenFiles ,
["compact"] = Commands . Compact ,
["create-report"] = Commands . CreateBugReport ,
["compare"] = Commands . ListChanges ,
["test"] = Commands . Test ,
["verify"] = Commands . Test ,
["test-filters"] = Commands . TestFilters ,
["test-filter"] = Commands . TestFilters ,
["affected"] = Commands . Affected ,
["vacuum"] = Commands . Vacuum ,
["system-info"] = Commands . SystemInfo ,
["systeminfo"] = Commands . SystemInfo ,
["send-mail"] = Commands . SendMail
};
2017-04-04 16:56:34 +02:00
return knownCommands ;
}
}
public static IEnumerable < string > SupportedCommands { get { return CommandMap . Keys ; } }
2019-07-18 12:15:01 -04:00
private static int ShowChangeLog ( TextWriter outwriter )
{
2024-04-15 13:17:49 +02:00
var path = System . IO . Path . Combine ( UpdaterManager . INSTALLATIONDIR , "changelog.txt" );
2018-04-29 18:51:22 +02:00
outwriter . WriteLine ( System . IO . File . ReadAllText ( path ));
return 0 ;
}
2017-04-04 16:56:34 +02:00
2019-07-18 12:15:01 -04:00
private static void CheckForUpdates ( TextWriter outwriter )
{
2018-04-29 18:51:22 +02:00
var update = Library . AutoUpdater . UpdaterManager . LastUpdateCheckVersion ;
if ( update == null )
update = Library . AutoUpdater . UpdaterManager . CheckForUpdate ();
if ( update != null && update . Version != Library . AutoUpdater . UpdaterManager . SelfVersion . Version )
2017-04-04 16:56:34 +02:00
{
2024-03-15 14:18:56 +01:00
var package = update . FindPackage ();
if ( package == null )
2018-04-29 18:51:22 +02:00
{
2024-03-15 14:18:56 +01:00
outwriter . WriteLine ( $"Failed to locate a matching package for this machine, please visit this link and select the correct package: {update.GetGenericUpdatePageUrl()}" );
}
else
{
var filename = Path . GetFullPath ( package . GetFilename ());
outwriter . WriteLine ( "Downloading update \"{0}\" to {1} ..." , update . Displayname , filename );
long lastpg = 0 ;
Library . AutoUpdater . UpdaterManager . DownloadUpdate ( update , package , filename , f =>
2018-04-29 18:51:22 +02:00
{
2024-03-15 14:18:56 +01:00
var npg = ( long )( f * 100 );
if ( Math . Abs ( npg - lastpg ) >= 5 || ( npg == 100 && lastpg != 100 ))
{
lastpg = npg ;
outwriter . WriteLine ( "Downloading {0}% ..." , npg );
}
});
outwriter . WriteLine ( "Update \"{0}\" ({1}) downloaded" , update . Displayname , update . Version );
}
2018-04-29 18:51:22 +02:00
}
}
2017-04-04 16:56:34 +02:00
2019-07-18 12:15:01 -04:00
private static int ParseCommandLine ( TextWriter outwriter , Action < Library . Main . Controller > setup , ref bool verboseErrors , string [] args )
{
2018-04-29 18:51:22 +02:00
List < string > cargs = new List < string >( args );
2017-04-04 16:56:34 +02:00
2018-04-29 18:51:22 +02:00
var tmpparsed = Library . Utility . FilterCollector . ExtractOptions ( cargs );
var options = tmpparsed . Item1 ;
var filter = tmpparsed . Item2 ;
verboseErrors = Library . Utility . Utility . ParseBoolOption ( options , "debug-output" );
if ( cargs . Count == 1 && string . Equals ( cargs [ 0 ], "changelog" , StringComparison . OrdinalIgnoreCase ))
{
return ShowChangeLog ( outwriter );
}
2017-04-04 16:56:34 +02:00
2018-04-29 18:51:22 +02:00
foreach ( string internaloption in Library . Main . Options . InternalOptions )
if ( options . ContainsKey ( internaloption ))
2014-07-09 22:40:11 +02:00
{
2018-04-29 18:51:22 +02:00
outwriter . WriteLine ( Strings . Program . InternalOptionUsedError ( internaloption ));
return 200 ;
2014-07-09 22:40:11 +02:00
}
2018-04-29 18:51:22 +02:00
// Probe for "help" to avoid extra processing
2018-05-26 22:55:29 +02:00
if ( cargs . Count == 0 || ( string . Equals ( cargs [ 0 ], "help" , StringComparison . OrdinalIgnoreCase )))
2018-04-29 18:51:22 +02:00
{
return Commands . Help ( outwriter , setup , cargs , options , filter );
}
2013-01-22 16:12:24 +00:00
2018-05-23 19:52:11 +02:00
// try and parse all parameter file aliases
2019-09-29 19:13:55 -07:00
foreach ( string parameterOption in new [] { "parameters-file" , "parameter-file" , "parameterfile" })
2018-04-29 18:51:22 +02:00
{
2018-05-23 19:52:11 +02:00
if ( options . ContainsKey ( parameterOption ) && ! string . IsNullOrEmpty ( options [ parameterOption ]))
2018-04-29 18:51:22 +02:00
{
2018-05-23 19:52:11 +02:00
string filename = options [ parameterOption ];
options . Remove ( parameterOption );
if (! ReadOptionsFromFile ( outwriter , filename , ref filter , cargs , options ))
return 100 ;
break ;
2018-05-26 22:55:29 +02:00
}
2018-04-29 18:51:22 +02:00
}
2017-01-09 23:21:00 +01:00
2018-04-29 18:51:22 +02:00
if (! options . ContainsKey ( "passphrase" ))
if (! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "PASSPHRASE" )))
options [ "passphrase" ] = System . Environment . GetEnvironmentVariable ( "PASSPHRASE" );
2016-12-01 23:16:15 +01:00
2018-04-29 18:51:22 +02:00
if (! options . ContainsKey ( "auth-password" ))
if (! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "AUTH_PASSWORD" )))
options [ "auth-password" ] = System . Environment . GetEnvironmentVariable ( "AUTH_PASSWORD" );
2016-12-01 23:16:15 +01:00
2018-04-29 18:51:22 +02:00
if (! options . ContainsKey ( "auth-username" ))
if (! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "AUTH_USERNAME" )))
options [ "auth-username" ] = System . Environment . GetEnvironmentVariable ( "AUTH_USERNAME" );
2018-05-20 16:07:12 +02:00
2019-10-05 06:20:57 -07:00
if ( options . ContainsKey ( "tempdir" ) && ! string . IsNullOrEmpty ( options [ "tempdir" ]))
Library . Utility . SystemContextSettings . DefaultTempPath = options [ "tempdir" ];
2018-04-29 18:51:22 +02:00
var showDeletionErrors = verboseErrors ;
Duplicati . Library . Utility . TempFile . RemoveOldApplicationTempFiles (( path , ex ) =>
{
if ( showDeletionErrors )
2020-01-25 17:07:02 -08:00
outwriter . WriteLine ( "Failed to delete temp file: {0}" , path );
2018-04-29 18:51:22 +02:00
});
2014-08-06 10:30:46 +02:00
2018-04-29 18:51:22 +02:00
string command = cargs [ 0 ];
cargs . RemoveAt ( 0 );
2014-08-06 10:30:46 +02:00
2018-06-30 18:48:07 +02:00
if ( verboseErrors )
{
outwriter . WriteLine ( "Input command: {0}" , command );
outwriter . WriteLine ( "Input arguments: " );
foreach ( var a in cargs )
outwriter . WriteLine ( "\t{0}" , a );
outwriter . WriteLine ();
outwriter . WriteLine ( "Input options: " );
foreach ( var n in options )
outwriter . WriteLine ( "{0}: {1}" , n . Key , n . Value );
outwriter . WriteLine ();
}
2018-04-29 18:51:22 +02:00
if ( CommandMap . ContainsKey ( command ))
{
2014-08-17 15:45:44 +02:00
var autoupdate = Library . Utility . Utility . ParseBoolOption ( options , "auto-update" );
options . Remove ( "auto-update" );
2018-04-29 18:51:22 +02:00
var res = CommandMap [ command ]( outwriter , setup , cargs , options , filter );
2014-08-17 15:45:44 +02:00
2018-04-29 18:51:22 +02:00
if ( autoupdate && FROM_COMMANDLINE )
2010-05-24 09:00:03 +00:00
{
2018-04-29 18:51:22 +02:00
CheckForUpdates ( outwriter );
2010-05-24 09:00:03 +00:00
}
2018-04-29 18:51:22 +02:00
return res ;
}
else
{
2018-10-06 13:30:13 -07:00
Commands . PrintInvalidCommand ( outwriter , command );
2018-04-29 18:51:22 +02:00
return 200 ;
}
}
public static int RunCommandLine ( TextWriter outwriter , TextWriter errwriter , Action < Library . Main . Controller > setup , string [] args )
{
bool verboseErrors = false ;
try
{
2018-04-29 19:59:50 +02:00
return ParseCommandLine ( outwriter , setup , ref verboseErrors , args );
2009-03-21 12:46:54 +00:00
}
2010-05-24 09:00:03 +00:00
catch ( Exception ex )
2009-01-14 16:19:54 +00:00
{
2015-12-22 00:35:00 +01:00
Library . UsageReporter . Reporter . Report ( ex );
2010-05-24 09:00:03 +00:00
while ( ex is System . Reflection . TargetInvocationException && ex . InnerException != null )
ex = ex . InnerException ;
2009-01-14 16:19:54 +00:00
2019-09-29 20:16:28 -07:00
if ( ex is UserInformationException exception && ! verboseErrors )
2010-05-24 09:00:03 +00:00
{
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ();
2019-09-29 20:16:28 -07:00
errwriter . WriteLine ( "ErrorID: {0}" , exception . HelpID );
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ( ex . Message );
2017-01-09 11:35:38 +01:00
}
else if (!( ex is Library . Interface . CancelException ))
{
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ();
2017-11-26 15:24:53 -08:00
errwriter . WriteLine ( ex );
2010-05-24 09:00:03 +00:00
}
2010-01-30 20:37:27 +00:00
else
2009-01-14 16:19:54 +00:00
{
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ( Strings . Program . UnhandledException ( ex . ToString ()));
2009-01-14 16:19:54 +00:00
2010-09-17 16:20:16 +00:00
while ( ex . InnerException != null )
{
ex = ex . InnerException ;
2017-04-04 16:56:34 +02:00
errwriter . WriteLine ();
errwriter . WriteLine ( Strings . Program . UnhandledInnerException ( ex . ToString ()));
2010-09-17 16:20:16 +00:00
}
2009-01-14 16:19:54 +00:00
}
2012-08-07 19:36:49 +00:00
return 100 ;
2009-01-14 16:19:54 +00:00
}
2008-09-13 16:24:43 +00:00
}
2018-05-26 22:55:29 +02:00
2017-04-04 16:56:34 +02:00
public static IList < Library . Interface . ICommandLineArgument > SupportedOptions
2013-01-22 16:12:24 +00:00
{
get
{
return new List < Library . Interface . ICommandLineArgument >( new Library . Interface . ICommandLineArgument [] {
2015-12-27 00:16:45 +01:00
new Library . Interface . CommandLineArgument ( "parameters-file" , Library . Interface . CommandLineArgument . ArgumentType . Path , Strings . Program . ParametersFileOptionShort , Strings . Program . ParametersFileOptionLong2 , "" , new string [] { "parameter-file" , "parameterfile" }),
2013-05-13 22:32:05 +02:00
new Library . Interface . CommandLineArgument ( "include" , Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . IncludeShort , Strings . Program . IncludeLong ),
new Library . Interface . CommandLineArgument ( "exclude" , Library . Interface . CommandLineArgument . ArgumentType . String , Strings . Program . ExcludeShort , Strings . Program . ExcludeLong ),
2013-05-30 21:54:43 +02:00
new Library . Interface . CommandLineArgument ( "control-files" , Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . ControlFilesOptionShort , Strings . Program . ControlFilesOptionLong , "false" ),
2013-08-18 23:16:58 +02:00
new Library . Interface . CommandLineArgument ( "quiet-console" , Library . Interface . CommandLineArgument . ArgumentType . Boolean , Strings . Program . QuietConsoleOptionShort , Strings . Program . QuietConsoleOptionLong , "false" ),
2014-08-17 15:45:44 +02:00
new Library . Interface . CommandLineArgument ( "auto-update" , Library . Interface . CommandLineArgument . ArgumentType . Boolean , LC . L ( "Toggle automatic updates" ), LC . L ( "Set this option if you prefer to have the commandline version automatically update" ), "false" ),
2013-01-22 16:12:24 +00:00
});
}
}
2011-07-23 14:57:32 +00:00
2017-04-04 16:56:34 +02:00
private static bool ReadOptionsFromFile ( TextWriter outwriter , string filename , ref Library . Utility . IFilter filter , List < string > cargs , Dictionary < string , string > options )
2013-01-22 16:12:24 +00:00
{
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 ()));
2013-05-13 22:32:05 +02:00
var newsource = new List < string >();
string newtarget = null ;
2015-12-27 00:16:45 +01:00
string prependfilter = null ;
string appendfilter = null ;
string replacefilter = null ;
2013-01-22 16:12:24 +00:00
2019-07-18 12:15:01 -04:00
var tmpparsed = Library . Utility . FilterCollector . ExtractOptions ( fargs , ( key , value ) =>
{
2015-12-27 00:16:45 +01:00
if ( key . Equals ( "source" , StringComparison . OrdinalIgnoreCase ))
2016-09-15 11:39:27 +02:00
{
newsource . Add ( value );
return false ;
}
2015-12-27 00:16:45 +01:00
else if ( key . Equals ( "target" , StringComparison . OrdinalIgnoreCase ))
2016-09-15 11:39:27 +02:00
{
newtarget = value ;
return false ;
}
2015-12-27 00:16:45 +01:00
else if ( key . Equals ( "append-filter" , StringComparison . OrdinalIgnoreCase ))
{
appendfilter = value ;
return false ;
}
else if ( key . Equals ( "prepend-filter" , StringComparison . OrdinalIgnoreCase ))
{
prependfilter = value ;
return false ;
}
2015-12-27 22:28:03 +01:00
else if ( key . Equals ( "replace-filter" , StringComparison . OrdinalIgnoreCase ))
2015-12-27 00:16:45 +01:00
{
replacefilter = value ;
return false ;
}
2016-09-15 11:39:27 +02:00
return true ;
});
2018-05-26 22:55:29 +02:00
2013-05-13 22:32:05 +02:00
var opt = tmpparsed . Item1 ;
var newfilter = tmpparsed . Item2 ;
2013-01-22 16:12:24 +00:00
// 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
2013-05-13 22:32:05 +02:00
if (! filter . Empty && ! newfilter . Empty )
2018-03-12 14:07:11 +01:00
throw new Duplicati . Library . Interface . UserInformationException ( Strings . Program . FiltersCannotBeUsedWithFileError2 , "FiltersCannotBeUsedOnCommandLineAndInParameterFile" );
2013-01-22 16:12:24 +00:00
2016-09-15 11:39:27 +02:00
if (! newfilter . Empty )
filter = newfilter ;
2013-01-22 16:12:24 +00:00
2015-12-27 00:16:45 +01:00
if (! string . IsNullOrWhiteSpace ( prependfilter ))
2019-07-18 12:15:01 -04:00
filter = Library . Utility . FilterExpression . Combine ( Library . Utility . FilterExpression . Deserialize ( prependfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries )), filter );
2015-12-27 00:16:45 +01:00
if (! string . IsNullOrWhiteSpace ( appendfilter ))
2019-07-18 12:15:01 -04:00
filter = Library . Utility . FilterExpression . Combine ( filter , Library . Utility . FilterExpression . Deserialize ( appendfilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries )));
2015-12-27 00:16:45 +01:00
if (! string . IsNullOrWhiteSpace ( replacefilter ))
2019-07-18 12:15:01 -04:00
filter = Library . Utility . FilterExpression . Deserialize ( replacefilter . Split ( new string [] { System . IO . Path . PathSeparator . ToString () }, StringSplitOptions . RemoveEmptyEntries ));
2015-12-27 00:16:45 +01:00
2013-01-22 16:12:24 +00:00
foreach ( KeyValuePair < String , String > keyvalue in opt )
2013-05-13 22:32:05 +02:00
options [ keyvalue . Key ] = keyvalue . Value ;
2018-05-26 22:55:29 +02:00
2013-05-13 22:32:05 +02:00
if (! string . IsNullOrEmpty ( newtarget ))
2019-07-18 12:15:01 -04:00
{
if ( cargs . Count <= 1 )
cargs . Add ( newtarget );
else
cargs [ 1 ] = newtarget ;
}
2015-09-17 20:03:26 +02:00
2017-09-18 23:23:45 -06:00
if ( cargs . Count >= 1 && cargs [ 0 ]. Equals ( "backup" , StringComparison . OrdinalIgnoreCase ))
2019-07-18 12:15:01 -04:00
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 );
2013-01-22 16:12:24 +00:00
return true ;
}
catch ( Exception e )
{
2017-04-04 16:56:34 +02:00
outwriter . WriteLine ( Strings . Program . FailedToParseParametersFileError ( filename , e . Message ));
2013-01-22 16:12:24 +00:00
return false ;
}
2011-07-23 14:57:32 +00:00
}
2008-09-13 16:24:43 +00:00
}
}