2025-01-07 09:40:39 +01:00
// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2025-03-28 16:08:45 -03:00
2024-08-26 20:00:13 +02:00
using System.CommandLine ;
using System.CommandLine.NamingConventionBinder ;
namespace Duplicati.CommandLine.ServerUtil.Commands ;
public static class Import
{
public static Command Create () =>
new Command ( "import" , "Import a backup configuration" )
{
new Argument < FileInfo >( "file" , "The file to import, may be encrypted" ) {
Arity = ArgumentArity . ExactlyOne
},
2025-12-02 10:26:45 +01:00
new Argument < string >( "passphrase" , "The passphrase to use for decryption of the configuration file, if encrypted" ) {
2024-08-26 20:00:13 +02:00
Arity = ArgumentArity . ZeroOrOne
},
2025-12-02 10:26:45 +01:00
new Option < bool >( name : "--import-metadata" , description : "Import metadata from the backup" , getDefaultValue : () => false ),
new Option < string >( name : "--backup-passphrase" , description : "The passphrase to inject into the backup configuration after import. Use this option if the configuration was exported without secrets." , getDefaultValue : () => string . Empty ),
new Option < string >( name : "--backup-url" , description : "The url to inject into the backup configuration after import. Use this option to replace the storage URL." , getDefaultValue : () => string . Empty )
2024-08-26 20:00:13 +02:00
}
2025-12-02 10:26:45 +01:00
. WithHandler ( CommandHandler . Create < Settings , OutputInterceptor , FileInfo , string , bool , string , string >( async ( settings , output , file , passphrase , importMetadata , backupPassphrase , backupUrl ) =>
2024-08-26 20:00:13 +02:00
{
if (! file . Exists )
throw new UserReportedException ( $"File {file.FullName} does not exist" );
2025-03-28 16:08:45 -03:00
output . AppendConsoleMessage ( $"Importing backup configuration from {file.FullName}" );
2024-08-26 20:00:13 +02:00
if ( IsEncrypted ( file ))
{
2025-03-28 16:08:45 -03:00
if ( output . JsonOutputMode )
throw new UserReportedException ( "No password provided with json mode." );
2025-12-02 10:26:45 +01:00
2024-08-26 20:00:13 +02:00
if ( string . IsNullOrWhiteSpace ( passphrase ))
2025-11-30 23:01:57 +01:00
passphrase = Library . Utility . Utility . ReadSecretFromConsole ( "The file is encrypted. Please provide the encryption password: " );
2024-08-26 20:00:13 +02:00
if ( string . IsNullOrWhiteSpace ( passphrase ))
throw new UserReportedException ( "No password provided" );
2024-10-24 16:52:44 +02:00
if ( settings . SecretProvider != null )
{
2025-03-28 16:08:45 -03:00
var opts = new Dictionary < string , string? > { { "password" , passphrase } };
2024-11-04 15:57:56 +01:00
await settings . ReplaceSecrets ( opts ). ConfigureAwait ( false );
2024-10-24 16:52:44 +02:00
passphrase = opts [ "password" ]!;
}
2024-08-26 20:00:13 +02:00
}
2025-12-02 10:26:45 +01:00
var extraSettings = new Dictionary < string , string >();
if (! string . IsNullOrWhiteSpace ( backupPassphrase ))
extraSettings . Add ( "settings.passphrase" , backupPassphrase );
if (! string . IsNullOrWhiteSpace ( backupUrl ))
extraSettings . Add ( "targeturl" , backupUrl );
2025-03-28 16:08:45 -03:00
var connection = await settings . GetConnection ( output );
2025-12-02 10:26:45 +01:00
var result = await connection . ImportBackup ( file . FullName , passphrase , importMetadata , extraSettings );
2024-08-26 20:00:13 +02:00
2025-03-28 16:08:45 -03:00
output . AppendConsoleMessage ( $"Imported \" { result . Name } \ " with ID {result.ID}" );
2025-12-02 10:26:45 +01:00
output . AppendCustomObject ( "Imported" , new { Id = result . ID , Name = result . Name });
2025-03-28 16:08:45 -03:00
output . SetResult ( true );
2024-08-26 20:00:13 +02:00
}));
private static bool IsEncrypted ( FileInfo file )
{
using var fs = file . OpenRead ();
var header = new byte [ 3 ]. AsSpan ();
if ( fs . Read ( header ) != 3 )
return false ;
return header . SequenceEqual ( "AES" u8 );
}
}