Add command-line program to import backup configurations.

This concerns issue #2693.
This commit is contained in:
Kenneth Hsu
2019-01-10 21:43:30 -08:00
parent fc59e5bede
commit fecde4f992
7 changed files with 218 additions and 12 deletions
+6
View File
@@ -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>
+105
View File
@@ -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("")]
+5 -5
View File
@@ -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))
{