Add command-line program to import backup configurations.
This concerns issue #2693.
This commit is contained in:
@@ -101,6 +101,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.R
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Common", "Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigurationImporter", "Duplicati\ConfigurationImporter\ConfigurationImporter.csproj", "{864B98B9-1B56-40D9-9733-9002E5B9B9D5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -303,6 +305,10 @@ Global
|
||||
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{864B98B9-1B56-40D9-9733-9002E5B9B9D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{864B98B9-1B56-40D9-9733-9002E5B9B9D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{864B98B9-1B56-40D9-9733-9002E5B9B9D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{864B98B9-1B56-40D9-9733-9002E5B9B9D5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{864B98B9-1B56-40D9-9733-9002E5B9B9D5}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>ConfigurationImporter</RootNamespace>
|
||||
<AssemblyName>ConfigurationImporter</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Server\Duplicati.Server.csproj">
|
||||
<Project>{19E661D2-C5DA-4F35-B3EE-7586E5734B5F}</Project>
|
||||
<Name>Duplicati.Server</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Library\Utility\Duplicati.Library.Utility.csproj">
|
||||
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
|
||||
<Name>Duplicati.Library.Utility</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Server\Duplicati.Server.Serialization\Duplicati.Server.Serialization.csproj">
|
||||
<Project>{33FD1D24-C28F-4C71-933F-98F1586EA76C}</Project>
|
||||
<Name>Duplicati.Server.Serialization</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Library\SQLiteHelper\Duplicati.Library.SQLiteHelper.csproj">
|
||||
<Project>{2C838169-B187-4B09-8768-1C24C2521C8D}</Project>
|
||||
<Name>Duplicati.Library.SQLiteHelper</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (C) 2019, The Duplicati Team
|
||||
// http://www.duplicati.com, info@duplicati.com
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
using Duplicati.Library.SQLiteHelper;
|
||||
using Duplicati.Server;
|
||||
using Duplicati.Server.Serializable;
|
||||
using Duplicati.Server.WebServer.RESTMethods;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Duplicati.CommandLine.ConfigurationImporter
|
||||
{
|
||||
public class ConfigurationImporter
|
||||
{
|
||||
private static readonly string usageString = $"Usage: {nameof(ConfigurationImporter)}.exe <configuration-file> --import-metadata=(true | false) [<advanced-option>]...";
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
throw new ArgumentException($"Incorrect number of input arguments. {ConfigurationImporter.usageString}");
|
||||
}
|
||||
|
||||
string configurationFile = args[0];
|
||||
Dictionary<string, string> importOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List<string> { args[1] });
|
||||
if (!importOptions.TryGetValue("import-metadata", out string importMetadataString))
|
||||
{
|
||||
throw new ArgumentException($"Invalid import-metadata argument. {ConfigurationImporter.usageString}");
|
||||
}
|
||||
bool importMetadata = Duplicati.Library.Utility.Utility.ParseBool(importMetadataString, false);
|
||||
|
||||
// This removes the ID and DBPath from the backup configuration.
|
||||
ImportExportStructure importedStructure = Backups.LoadConfiguration(configurationFile, importMetadata, () => ConfigurationImporter.ReadPassword($"Password for {configurationFile}: "));
|
||||
|
||||
Dictionary<string, string> advancedOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List<string>(args.Skip(2)));
|
||||
|
||||
// This will create the Duplicati-server.sqlite database file if it doesn't exist.
|
||||
Duplicati.Server.Database.Connection connection = Program.GetDatabaseConnection(advancedOptions);
|
||||
|
||||
if (connection.Backups.Any(x => x.Name.Equals(importedStructure.Backup.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
throw new InvalidOperationException($"A backup with the name {importedStructure.Backup.Name} already exists.");
|
||||
}
|
||||
|
||||
string error = connection.ValidateBackup(importedStructure.Backup, importedStructure.Schedule);
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
throw new InvalidOperationException(error);
|
||||
}
|
||||
|
||||
// This creates a new ID and DBPath.
|
||||
connection.AddOrUpdateBackupAndSchedule(importedStructure.Backup, importedStructure.Schedule);
|
||||
|
||||
// This creates the local database file at the location specified by the DBPath.
|
||||
SQLiteLoader.LoadConnection(importedStructure.Backup.DBPath);
|
||||
|
||||
Console.WriteLine($"Imported \"{importedStructure.Backup.Name}\" with ID {importedStructure.Backup.ID} and local database at {importedStructure.Backup.DBPath}.");
|
||||
}
|
||||
|
||||
private static string ReadPassword(string prompt)
|
||||
{
|
||||
Console.Write(prompt);
|
||||
string password = "";
|
||||
while (true)
|
||||
{
|
||||
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
|
||||
if (keyInfo.Key == ConsoleKey.Enter)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (keyInfo.Key == ConsoleKey.Backspace)
|
||||
{
|
||||
if (password.Length > 0)
|
||||
{
|
||||
password = password.Substring(0, password.Length - 1);
|
||||
Console.Write("\b \b");
|
||||
}
|
||||
}
|
||||
else if (keyInfo.KeyChar != '\u0000') // Only accept if the key maps to a Unicode character (e.g., ignore F1 or Home).
|
||||
{
|
||||
|
||||
password += keyInfo.KeyChar;
|
||||
Console.Write("*");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
return password;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2019, The Duplicati Team
|
||||
// http://www.duplicati.com, info@duplicati.com
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as
|
||||
// published by the Free Software Foundation; either version 2.1 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("ConfigurationImporter")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
||||
@@ -372,12 +372,12 @@ namespace Duplicati.Server.Database
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddOrUpdateBackupAndSchedule(IBackup item, ISchedule schedule)
|
||||
public void AddOrUpdateBackupAndSchedule(IBackup item, ISchedule schedule)
|
||||
{
|
||||
AddOrUpdateBackup(item, true, schedule);
|
||||
}
|
||||
|
||||
internal string ValidateBackup(IBackup item, ISchedule schedule)
|
||||
public string ValidateBackup(IBackup item, ISchedule schedule)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.Name))
|
||||
return "Missing a name";
|
||||
@@ -506,7 +506,7 @@ namespace Duplicati.Server.Database
|
||||
var folder = Program.DataFolder;
|
||||
if (!System.IO.Directory.Exists(folder))
|
||||
System.IO.Directory.CreateDirectory(folder);
|
||||
|
||||
|
||||
for(var i = 0; i < 100; i++)
|
||||
{
|
||||
var guess = System.IO.Path.Combine(folder, System.IO.Path.ChangeExtension(Duplicati.Library.Main.DatabaseLocator.GenerateRandomName(), ".sqlite"));
|
||||
@@ -520,7 +520,7 @@ namespace Duplicati.Server.Database
|
||||
if (item.DBPath == null)
|
||||
throw new Exception("Unable to generate a unique database file name");
|
||||
}
|
||||
|
||||
|
||||
using(var tr = m_connection.BeginTransaction())
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
@@ -669,7 +669,7 @@ namespace Duplicati.Server.Database
|
||||
tr.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
System.Threading.Interlocked.Increment(ref Program.LastDataUpdateID);
|
||||
Program.StatusEventNotifyer.SignalNewEvent();
|
||||
}
|
||||
|
||||
@@ -15,19 +15,16 @@
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Duplicati.Server.Serializable
|
||||
{
|
||||
internal class ImportExportStructure
|
||||
public class ImportExportStructure
|
||||
{
|
||||
public string CreatedByVersion { get; set; }
|
||||
public Duplicati.Server.Database.Schedule Schedule { get; set; }
|
||||
public Duplicati.Server.Database.Backup Backup { get; set; }
|
||||
public Dictionary<string, string> DisplayNames { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Duplicati.Server.WebServer.RESTMethods
|
||||
if (file == null)
|
||||
throw new Exception("No file uploaded");
|
||||
|
||||
Serializable.ImportExportStructure ipx = Backups.LoadConfiguration(file.Filename, import_metadata, input["passphrase"].Value);
|
||||
Serializable.ImportExportStructure ipx = Backups.LoadConfiguration(file.Filename, import_metadata, () => input["passphrase"].Value);
|
||||
if (direct)
|
||||
{
|
||||
lock (Program.DataConnection.m_lock)
|
||||
@@ -121,7 +121,7 @@ namespace Duplicati.Server.WebServer.RESTMethods
|
||||
}
|
||||
}
|
||||
|
||||
internal static Serializable.ImportExportStructure LoadConfiguration(string filename, bool importMetadata, string passphrase = null)
|
||||
public static Serializable.ImportExportStructure LoadConfiguration(string filename, bool importMetadata, Func<string> getPassword)
|
||||
{
|
||||
Serializable.ImportExportStructure ipx;
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace Duplicati.Server.WebServer.RESTMethods
|
||||
fs.Position = 0;
|
||||
if (buf[0] == 'A' && buf[1] == 'E' && buf[2] == 'S')
|
||||
{
|
||||
using (var m = new Duplicati.Library.Encryption.AESEncryption(passphrase, new Dictionary<string, string>()))
|
||||
using (var m = new Duplicati.Library.Encryption.AESEncryption(getPassword(), new Dictionary<string, string>()))
|
||||
{
|
||||
using (var m2 = m.Decrypt(fs))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user