Merge branch 'refs/heads/feature/kestrel-avalonia-upgrade' into feature/http-server-modernization

# Conflicts:
#	Duplicati.Library.RestAPI/UpdatePollThread.cs
#	Duplicati/Server/Program.cs
#	Duplicati/Server/webroot/ngax/scripts/services/ServerStatus.js
#	Duplicati/WebserverCore/DuplicatiWebserver.cs
This commit is contained in:
natan
2024-04-22 09:35:53 +02:00
235 changed files with 5945 additions and 5567 deletions
+17 -22
View File
@@ -1,10 +1,6 @@
name: Tests
on:
push:
branches: "**net5**"
pull_request:
branches: "**net5**"
on: [push, pull_request]
jobs:
unit_tests:
@@ -13,30 +9,29 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Set up .NET
uses: actions/setup-dotnet@v1.8.0
with:
dotnet-version: 6.0.x
- name: Set up .NET
uses: actions/setup-dotnet@v1.8.0
with:
dotnet-version: 8.0.x
- name: Checkout source
uses: actions/checkout@v2.3.4
- name: Checkout source
uses: actions/checkout@v2.3.4
- name: Restore NuGet dependencies
run: dotnet restore Duplicati.sln
- name: Restore NuGet dependencies
run: dotnet restore Duplicati.sln
- name: Build Duplicati
run: dotnet build --no-restore Duplicati.sln
- name: Build Duplicati
run: dotnet build --no-restore Duplicati.sln
- name: Run unit tests
run: dotnet test --no-build --verbosity minimal Duplicati.sln
- name: Run unit tests
run: dotnet test --no-build --verbosity minimal Duplicati.sln
selenium:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Selenium
run: pipeline/selenium/test.sh
- uses: actions/checkout@v2
- name: Selenium
run: pipeline/selenium/test.sh
+3 -1
View File
@@ -27,4 +27,6 @@ binfiles.wxs
Installer/debian/*.buildinfo
/*.deb
/*.rpm
/*.rpm
ReleaseBuilder/build-temp/
+23 -1
View File
@@ -9,7 +9,29 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Executables/net8/Duplicati.GUI.TrayIcon/bin/Debug/net8.0/Duplicati.GUI.TrayIcon.exe",
"program": "${workspaceFolder}/Executables/net8/Duplicati.GUI.TrayIcon/bin/Debug/net8.0/Duplicati.GUI.TrayIcon",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
},
{
"name": "Launch Server executable",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Executables/net8/Duplicati.Server/bin/Debug/net8.0/Duplicati.Server",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
},
{
"name": "Launch CommandLine executable",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Executables/net8/Duplicati.Server/bin/Debug/net8.0/Duplicati.CommandLine",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
@@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Duplicati\Library\Compression\Duplicati.Library.Compression.csproj" />
<ProjectReference Include="..\..\Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj" />
<ProjectReference Include="..\..\Duplicati\Library\Interface\Duplicati.Library.Interface.csproj" />
<ProjectReference Include="..\..\Duplicati\Library\Logging\Duplicati.Library.Logging.csproj" />
<ProjectReference Include="..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj" />
<ProjectReference Include="..\..\Duplicati\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj" />
<ProjectReference Include="..\..\Duplicati\License\Duplicati.License.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
@@ -1,74 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoUpdateBuilder", "AutoUpdateBuilder.csproj", "{17E3EB29-87FD-4BE9-A531-8DAD8B295494}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Compression", "..\..\Duplicati\Library\Compression\Duplicati.Library.Compression.csproj", "{19ECCE09-B5EB-406C-8C57-BAC66997D469}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Interface", "..\..\Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Logging", "..\..\Duplicati\Library\Logging\Duplicati.Library.Logging.csproj", "{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Encryption", "..\..\Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.AutoUpdater", "..\..\Duplicati\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj", "{7E119745-1F62-43F0-936C-F312A1912C0B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.License", "..\..\Duplicati\License\Duplicati.License.csproj", "{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Localization", "..\..\Duplicati\Library\Localization\Duplicati.Library.Localization.csproj", "{B68F2214-951F-4F78-8488-66E1ED3F50BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Common", "..\..\Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Release|Any CPU.Build.0 = Release|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.Build.0 = Release|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.Build.0 = Release|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Release|Any CPU.Build.0 = Release|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.Build.0 = Release|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Release|Any CPU.Build.0 = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.Build.0 = Release|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.Build.0 = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = AutoUpdateBuilder.csproj
EndGlobalSection
EndGlobal
-184
View File
@@ -1,184 +0,0 @@
// 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.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace AutoUpdateBuilder
{
public class Program
{
private static RSACryptoServiceProvider privkey;
private static string keyfile;
private static string keyfilepassword;
private static void CompareToManifestPublicKey()
{
if (Duplicati.Library.AutoUpdater.AutoUpdateSettings.SignKey == null || privkey.ToXmlString(false) != Duplicati.Library.AutoUpdater.AutoUpdateSettings.SignKey.ToXmlString(false))
{
Console.WriteLine("The public key in the project is not the same as the public key from the file");
Console.WriteLine("Try setting the key to: ");
Console.WriteLine(privkey.ToXmlString(false));
System.Environment.Exit(5);
}
}
private static void LoadKeyFromFile()
{
using (var enc = new Duplicati.Library.Encryption.AESEncryption(keyfilepassword, new Dictionary<string, string>()))
using (var ms = new System.IO.MemoryStream())
using (var fs = System.IO.File.OpenRead(keyfile))
{
enc.Decrypt(fs, ms);
ms.Position = 0;
using (var sr = new System.IO.StreamReader(ms))
privkey.FromXmlString(sr.ReadToEnd());
}
}
public static int Main(string[] _args)
{
var args = new List<string>(_args);
var opts = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args);
opts.TryGetValue("input", out string inputfolder);
opts.TryGetValue("output", out string outputfolder);
opts.TryGetValue("allow-new-key", out string allowNewKey);
opts.TryGetValue("keyfile", out keyfile);
opts.TryGetValue("manifest", out string manifestfile);
opts.TryGetValue("keyfile-password", out keyfilepassword);
var usedoptions = new [] { "allow-new-key", "input", "output", "keyfile", "manifest", "keyfile-password" };
if (string.IsNullOrWhiteSpace(inputfolder))
{
Console.WriteLine("Missing input folder");
return 4;
}
if (string.IsNullOrWhiteSpace(outputfolder))
{
Console.WriteLine("Missing output folder");
return 4;
}
if (string.IsNullOrWhiteSpace(keyfile))
{
Console.WriteLine("Missing keyfile");
return 4;
}
if (!System.IO.Directory.Exists(inputfolder))
{
Console.WriteLine("Input folder not found");
return 4;
}
if (string.IsNullOrWhiteSpace(keyfilepassword))
{
Console.WriteLine("Enter keyfile passphrase: ");
keyfilepassword = Console.ReadLine().Trim();
}
if (!System.IO.File.Exists(keyfile))
{
Console.WriteLine("Keyfile not found, creating new");
var newkey = RSA.Create().ToXmlString(true);
using (var enc = new Duplicati.Library.Encryption.AESEncryption(keyfilepassword, new Dictionary<string, string>()))
using (var fs = System.IO.File.OpenWrite(keyfile))
using (var ms = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(newkey)))
enc.Encrypt(ms, fs);
}
if (!System.IO.Directory.Exists(outputfolder))
System.IO.Directory.CreateDirectory(outputfolder);
privkey = (RSACryptoServiceProvider) RSA.Create();
LoadKeyFromFile();
if (!Boolean.TryParse(allowNewKey, out Boolean newKeyAllowed) || !newKeyAllowed)
{
CompareToManifestPublicKey();
}
Duplicati.Library.AutoUpdater.UpdateInfo updateInfo;
using (var fs = System.IO.File.OpenRead(manifestfile))
using (var sr = new System.IO.StreamReader(fs))
using (var jr = new Newtonsoft.Json.JsonTextReader(sr))
updateInfo = new Newtonsoft.Json.JsonSerializer().Deserialize<Duplicati.Library.AutoUpdater.UpdateInfo>(jr);
var isopts = new Dictionary<string, string>(opts, StringComparer.InvariantCultureIgnoreCase);
foreach (var usedopt in usedoptions)
{
isopts.Remove(usedopt);
}
foreach (var k in updateInfo.GetType().GetFields())
{
if (!isopts.ContainsKey(k.Name))
{
continue;
}
try
{
//Console.WriteLine("Setting {0} to {1}", k.Name, isopts[k.Name]);
if (k.FieldType == typeof(string[]))
k.SetValue(updateInfo, isopts[k.Name].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
else if (k.FieldType == typeof(Version))
k.SetValue(updateInfo, new Version(isopts[k.Name]));
else if (k.FieldType == typeof(int))
k.SetValue(updateInfo, int.Parse(isopts[k.Name]));
else if (k.FieldType == typeof(long))
k.SetValue(updateInfo, long.Parse(isopts[k.Name]));
else
k.SetValue(updateInfo, isopts[k.Name]);
}
catch (Exception ex)
{
Console.WriteLine("Failed setting {0} to {1}: {2}", k.Name, isopts[k.Name], ex.Message);
}
isopts.Remove(k.Name);
}
foreach (var opt in isopts)
{
Console.WriteLine("Warning! unused option: {0} = {1}", opt.Key, opt.Value);
}
using (var tf = new Duplicati.Library.Utility.TempFile())
{
using (var fs = System.IO.File.OpenWrite(tf))
using (var tw = new System.IO.StreamWriter(fs))
new Newtonsoft.Json.JsonSerializer().Serialize(tw, updateInfo);
Duplicati.Library.AutoUpdater.UpdaterManager.CreateUpdatePackage(privkey, inputfolder, outputfolder, tf);
}
return 0;
}
}
}
@@ -260,8 +260,8 @@ namespace Duplicati.Server.Database
{
if (sb.Length != 0)
sb.Append(" OR ");
sb.Append(@" ("","" || ""Tags"" || "","" LIKE ""%,"" || ? || "",%"") ");
sb.Append(@" (',' || ""Tags"" || ',' LIKE '%,' || ? || ',%') ");
var p = cmd.CreateParameter();
p.Value = t;
cmd.Parameters.Add(p);
@@ -330,7 +330,7 @@ namespace Duplicati.Server.Database
{
var usesEncryption = ReadFromDb(
(rd) => ConvertToBoolean(rd, 0),
@"SELECT VALUE != """" FROM ""Option"" WHERE BackupID = ? AND NAME='encryption-module'", id)
@"SELECT VALUE != '' FROM ""Option"" WHERE BackupID = ? AND NAME='encryption-module'", id)
.FirstOrDefault();
if (!usesEncryption)
@@ -340,7 +340,7 @@ namespace Duplicati.Server.Database
return ReadFromDb(
(rd) => ConvertToBoolean(rd, 0),
@"SELECT VALUE != """" FROM ""Option"" WHERE BackupID = ? AND NAME='passphrase'", id)
@"SELECT VALUE != '' FROM ""Option"" WHERE BackupID = ? AND NAME='passphrase'", id)
.FirstOrDefault();
}
}
@@ -359,8 +359,8 @@ namespace Duplicati.Server.Database
{
if (sb.Length != 0)
sb.Append(" OR ");
sb.Append(@" ("","" || ""Tags"" || "","" LIKE ""%,"" || ? || "",%"") ");
sb.Append(@" (',' || ""Tags"" || ',' LIKE '%,' || ? || ',%') ");
var p = cmd.CreateParameter();
p.Value = t;
cmd.Parameters.Add(p);
@@ -59,7 +59,7 @@ namespace Duplicati.Server.WebServer.RESTMethods
try
{
// Add install defaults/overrides, if present
var path = SystemIO.IO_OS.PathCombine(Library.AutoUpdater.UpdaterManager.InstalledBaseDir, "newbackup.json");
var path = SystemIO.IO_OS.PathCombine(Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR, "newbackup.json");
if (System.IO.File.Exists(path))
{
Newtonsoft.Json.Linq.JObject n;
@@ -19,6 +19,7 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
namespace Duplicati.Server.WebServer.RESTMethods
{
@@ -26,7 +27,16 @@ namespace Duplicati.Server.WebServer.RESTMethods
{
public void GET(string key, RequestInfo info)
{
var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(Duplicati.Library.Utility.Utility.getEntryAssembly().Location), "licenses");
var exefolder = System.IO.Path.GetDirectoryName(Duplicati.Library.Utility.Utility.getEntryAssembly().Location);
var path = System.IO.Path.Combine(exefolder, "licenses");
if (Duplicati.Library.Common.Platform.IsClientOSX && !Directory.Exists(path))
{
// Go up one, as the licenses cannot be in the binary folder in MacOS Packages
exefolder = Path.GetDirectoryName(exefolder);
var test = Path.Combine(exefolder, "Licenses");
if (Directory.Exists(test))
path = test;
}
info.OutputOK(Duplicati.License.LicenseReader.ReadLicenses(path));
}
}
@@ -57,7 +57,6 @@ namespace Duplicati.Server.WebServer.RESTMethods
ServerVersionName = Duplicati.License.VersionNumbers.Version,
ServerVersionType = Duplicati.Library.AutoUpdater.UpdaterManager.SelfVersion.ReleaseType,
StartedBy = FIXMEGlobal.Origin,
BaseVersionName = Duplicati.Library.AutoUpdater.UpdaterManager.BaseVersion.Displayname,
DefaultUpdateChannel = Duplicati.Library.AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel,
DefaultUsageReportLevel = Duplicati.Library.UsageReporter.Reporter.DefaultReportLevel,
ServerTime = DateTime.Now,
@@ -65,8 +64,8 @@ namespace Duplicati.Server.WebServer.RESTMethods
DirectorySeparator = System.IO.Path.DirectorySeparatorChar,
PathSeparator = System.IO.Path.PathSeparator,
CaseSensitiveFilesystem = Duplicati.Library.Utility.Utility.IsFSCaseSensitive,
MonoVersion = Duplicati.Library.Utility.Utility.IsMono ? Duplicati.Library.Utility.Utility.MonoVersion.ToString() : null,
MachineName = System.Environment.MachineName,
PackageTypeId = Duplicati.Library.AutoUpdater.UpdaterManager.PackageTypeId,
UserName = System.Environment.UserName,
NewLine = System.Environment.NewLine,
CLRVersion = System.Environment.Version.ToString(),
@@ -33,23 +33,6 @@ namespace Duplicati.Server.WebServer.RESTMethods
FIXMEGlobal.UpdatePoller.CheckNow();
info.OutputOK();
return;
case "install":
FIXMEGlobal.UpdatePoller.InstallUpdate();
info.OutputOK();
return;
case "activate":
if (FIXMEGlobal.WorkThread.CurrentTask != null || FIXMEGlobal.WorkThread.CurrentTasks.Count != 0)
{
info.ReportServerError("Cannot activate update while task is running or scheduled");
}
else
{
FIXMEGlobal.UpdatePoller.ActivateUpdate();
info.OutputOK();
}
return;
default:
info.ReportClientError("No such action", System.Net.HttpStatusCode.NotFound);
+43 -46
View File
@@ -61,7 +61,7 @@ namespace Duplicati.Server
{
Controller = controller;
}
public void Stop(bool allowCurrentFileToFinish)
{
var c = Controller;
@@ -162,7 +162,8 @@ namespace Duplicati.Server
public static IRunnerData CreateTask(Duplicati.Server.Serialization.DuplicatiOperation operation, Duplicati.Server.Serialization.Interface.IBackup backup, IDictionary<string, string> extraOptions = null, string[] filterStrings = null)
{
return new RunnerData() {
return new RunnerData()
{
Operation = operation,
Backup = backup,
ExtraOptions = extraOptions,
@@ -189,14 +190,14 @@ namespace Duplicati.Server
filters);
}
public static IRunnerData CreateRestoreTask(Duplicati.Server.Serialization.Interface.IBackup backup, string[] filters,
DateTime time, string restoreTarget, bool overwrite, bool restore_permissions,
public static IRunnerData CreateRestoreTask(Duplicati.Server.Serialization.Interface.IBackup backup, string[] filters,
DateTime time, string restoreTarget, bool overwrite, bool restore_permissions,
bool skip_metadata, string passphrase)
{
var dict = new Dictionary<string, string>
{
["time"] = Library.Utility.Utility.SerializeDateTime(time.ToUniversalTime()),
["overwrite"] = overwrite? Boolean.TrueString : Boolean.FalseString,
["overwrite"] = overwrite ? Boolean.TrueString : Boolean.FalseString,
["restore-permissions"] = restore_permissions ? Boolean.TrueString : Boolean.FalseString,
["skip-metadata"] = skip_metadata ? Boolean.TrueString : Boolean.FalseString,
["allow-passphrase-change"] = Boolean.TrueString
@@ -263,7 +264,7 @@ namespace Duplicati.Server
public long CurrentFilesize { get { return m_currentFilesize; } }
public long CurrentFileoffset { get { return m_currentFileoffset; } }
public bool CurrentFilecomplete { get { return m_currentFilecomplete; } }
public string Phase { get { return m_phase.ToString(); } }
public string Phase { get { return m_phase.ToString(); } }
public float OverallProgress { get { return m_overallProgress; } }
public long ProcessedFileCount { get { return m_processedFileCount; } }
public long ProcessedFileSize { get { return m_processedFileSize; } }
@@ -285,7 +286,7 @@ namespace Duplicati.Server
public Server.Serialization.Interface.IProgressEventData Copy()
{
lock(m_lock)
lock (m_lock)
{
if (m_backendProgress != null)
m_backendProgress.Update(out m_state.m_backendAction, out m_state.m_backendPath, out m_state.m_backendFileSize, out m_state.m_backendFileProgress, out m_state.m_backendSpeed, out m_state.m_backendIsBlocking);
@@ -302,7 +303,7 @@ namespace Duplicati.Server
#region IMessageSink implementation
public void BackendEvent(Duplicati.Library.Main.BackendActionType action, Duplicati.Library.Main.BackendEventType type, string path, long size)
{
lock(m_lock)
lock (m_lock)
{
m_state.m_backendAction = action;
m_state.m_backendPath = path;
@@ -344,7 +345,7 @@ namespace Duplicati.Server
var options = ApplyOptions(backup, GetCommonOptions());
if (data.ExtraOptions != null)
foreach(var k in data.ExtraOptions)
foreach (var k in data.ExtraOptions)
options[k.Key] = k.Value;
var cf = FIXMEGlobal.DataConnection.Filters;
@@ -352,37 +353,31 @@ namespace Duplicati.Server
var sources =
(from n in backup.Sources
let p = SpecialFolders.ExpandEnvironmentVariables(n)
where !string.IsNullOrWhiteSpace(p)
select p).ToArray();
let p = SpecialFolders.ExpandEnvironmentVariables(n)
where !string.IsNullOrWhiteSpace(p)
select p).ToArray();
var exe =
System.IO.Path.Combine(
Library.AutoUpdater.UpdaterManager.InstalledBaseDir,
System.IO.Path.GetFileName(
typeof(Duplicati.CommandLine.Commands).Assembly.Location
)
);
var exe = System.IO.Path.Combine(
Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR,
Library.AutoUpdater.PackageHelper.GetExecutableName(Library.AutoUpdater.PackageHelper.NamedExecutable.CommandLine)
);
var cmd = new System.Text.StringBuilder();
if (Library.Utility.Utility.IsMono)
cmd.Append("mono ");
cmd.Append(Library.Utility.Utility.WrapAsCommandLine(new string[] { exe, "backup", backup.TargetURL }, false));
cmd.Append(" ");
cmd.Append(Library.Utility.Utility.WrapAsCommandLine(sources, true));
// TODO: We should check each option to see if it is a path, and allow expansion on that
foreach(var opt in options)
foreach (var opt in options)
cmd.AppendFormat(" --{0}={1}", opt.Key, Library.Utility.Utility.WrapCommandLineElement(opt.Value, false));
if (cf != null)
foreach(var f in cf)
foreach (var f in cf)
cmd.AppendFormat(" --{0}={1}", f.Include ? "include" : "exclude", Library.Utility.Utility.WrapCommandLineElement(f.Expression, true));
if (bf != null)
foreach(var f in bf)
foreach (var f in bf)
cmd.AppendFormat(" --{0}={1}", f.Include ? "include" : "exclude", Library.Utility.Utility.WrapCommandLineElement(f.Expression, true));
return cmd.ToString();
@@ -438,7 +433,7 @@ namespace Duplicati.Server
task.Run(sink);
}
catch(Exception ex)
catch (Exception ex)
{
FIXMEGlobal.DataConnection.LogError(string.Empty, "Failed while executing custom task", ex);
}
@@ -465,7 +460,7 @@ namespace Duplicati.Server
var options = ApplyOptions(backup, GetCommonOptions());
if (data.ExtraOptions != null)
foreach(var k in data.ExtraOptions)
foreach (var k in data.ExtraOptions)
options[k.Key] = k.Value;
// Pack in the system or task config for easy restore
@@ -475,13 +470,14 @@ namespace Duplicati.Server
}
// Attach a log scope that tags all messages to relay the TaskID and BackupID
using (Library.Logging.Log.StartScope(log => {
using (Library.Logging.Log.StartScope(log =>
{
log[LogWriteHandler.LOG_EXTRA_TASKID] = data.TaskID.ToString();
log[LogWriteHandler.LOG_EXTRA_BACKUPID] = data.BackupID;
}))
using(tempfolder)
using(var controller = new Duplicati.Library.Main.Controller(backup.TargetURL, options, sink))
using (tempfolder)
using (var controller = new Duplicati.Library.Main.Controller(backup.TargetURL, options, sink))
{
try
{
@@ -513,9 +509,9 @@ namespace Duplicati.Server
var filter = ApplyFilter(backup, GetCommonFilter());
var sources =
(from n in backup.Sources
let p = SpecialFolders.ExpandEnvironmentVariables(n)
where !string.IsNullOrWhiteSpace(p)
select p).ToArray();
let p = SpecialFolders.ExpandEnvironmentVariables(n)
where !string.IsNullOrWhiteSpace(p)
select p).ToArray();
var r = controller.Backup(sources, filter);
UpdateMetadata(backup, r);
@@ -565,7 +561,7 @@ namespace Duplicati.Server
}
case DuplicatiOperation.CreateReport:
{
using(var tf = new Duplicati.Library.Utility.TempFile())
using (var tf = new Duplicati.Library.Utility.TempFile())
{
var r = controller.CreateLogDatabase(tf);
var tempid = FIXMEGlobal.DataConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3));
@@ -706,7 +702,8 @@ namespace Duplicati.Server
null,
messageid,
null,
(n, a) => {
(n, a) =>
{
return a.FirstOrDefault(x => x.BackupID == backup.ID) ?? n;
}
);
@@ -909,12 +906,12 @@ namespace Duplicati.Server
options["backup-id"] = $"DB-{backup.ID}";
// Apply normal options
foreach(var o in backup.Settings)
foreach (var o in backup.Settings)
if (!o.Name.StartsWith("--", StringComparison.Ordinal) && TestIfOptionApplies())
options[o.Name] = o.Value;
// Apply override options
foreach(var o in backup.Settings)
foreach (var o in backup.Settings)
if (o.Name.StartsWith("--", StringComparison.Ordinal) && TestIfOptionApplies())
options[o.Name.Substring(2)] = o.Value;
@@ -931,12 +928,12 @@ namespace Duplicati.Server
{
var nf =
(from n in f2
let exp =
n.Expression.StartsWith("[", StringComparison.Ordinal) && n.Expression.EndsWith("]", StringComparison.Ordinal)
? SpecialFolders.ExpandEnvironmentVariablesRegexp(n.Expression)
: SpecialFolders.ExpandEnvironmentVariables(n.Expression)
orderby n.Order
select (Library.Utility.IFilter)(new Library.Utility.FilterExpression(exp, n.Include)))
let exp =
n.Expression.StartsWith("[", StringComparison.Ordinal) && n.Expression.EndsWith("]", StringComparison.Ordinal)
? SpecialFolders.ExpandEnvironmentVariablesRegexp(n.Expression)
: SpecialFolders.ExpandEnvironmentVariables(n.Expression)
orderby n.Order
select (Library.Utility.IFilter)(new Library.Utility.FilterExpression(exp, n.Include)))
.Aggregate((a, b) => Library.Utility.FilterExpression.Combine(a, b));
filter = Library.Utility.FilterExpression.Combine(filter, nf);
@@ -961,9 +958,9 @@ namespace Duplicati.Server
return
(from n in filters
orderby n.Order
let exp = Environment.ExpandEnvironmentVariables(n.Expression)
select (Duplicati.Library.Utility.IFilter)(new Duplicati.Library.Utility.FilterExpression(exp, n.Include)))
orderby n.Order
let exp = Environment.ExpandEnvironmentVariables(n.Expression)
select (Duplicati.Library.Utility.IFilter)(new Duplicati.Library.Utility.FilterExpression(exp, n.Include)))
.Aggregate((a, b) => Duplicati.Library.Utility.FilterExpression.Combine(a, b));
}
}
@@ -55,9 +55,9 @@ namespace Duplicati.Server.Serializable
}
}
public UpdatePollerStates UpdaterState { get { return FIXMEGlobal.UpdatePoller.ThreadState; } }
public string UpdateDownloadLink => FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion?.GetUpdateUrls()?.FirstOrDefault();
public bool UpdateReady { get { return Duplicati.Library.AutoUpdater.UpdaterManager.HasUpdateInstalled; } }
public UpdatePollerStates UpdaterState { get { return FIXMEGlobal.UpdatePoller.ThreadState; } }
public double UpdateDownloadProgress { get { return FIXMEGlobal.UpdatePoller.DownloadProgess; } }
+18 -50
View File
@@ -30,7 +30,6 @@ namespace Duplicati.Server
{
private Thread m_thread;
private volatile bool m_terminated = false;
private volatile bool m_download = false;
private volatile bool m_forceCheck = false;
private readonly object m_lock = new object();
private AutoResetEvent m_waitSignal;
@@ -41,7 +40,7 @@ namespace Duplicati.Server
public UpdatePollerStates ThreadState { get; private set; }
public double DownloadProgess
{
get { return m_downloadProgress ; }
get { return m_downloadProgress; }
private set
{
@@ -66,35 +65,16 @@ namespace Duplicati.Server
public void CheckNow()
{
lock(m_lock)
lock (m_lock)
{
m_forceCheck = true;
m_waitSignal.Set();
}
}
public void InstallUpdate()
{
lock(m_lock)
{
m_forceCheck = true;
m_download = true;
m_waitSignal.Set();
}
}
public void ActivateUpdate()
{
if (Duplicati.Library.AutoUpdater.UpdaterManager.SetRunUpdate())
{
IsUpdateRequested = true;
FIXMEGlobal.ApplicationExitEvent.Set();
}
}
public void Terminate()
{
lock(m_lock)
lock (m_lock)
{
m_terminated = true;
m_waitSignal.Set();
@@ -130,12 +110,12 @@ namespace Duplicati.Server
if (nextCheck < DateTime.UtcNow || m_forceCheck)
{
lock(m_lock)
lock (m_lock)
m_forceCheck = false;
ThreadState = UpdatePollerStates.Checking;
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
DateTime started = DateTime.UtcNow;
FIXMEGlobal.DataConnection.ApplicationSettings.LastUpdateCheck = started;
nextCheck = FIXMEGlobal.DataConnection.ApplicationSettings.NextUpdateCheck;
@@ -148,7 +128,7 @@ namespace Duplicati.Server
rt = rt == Duplicati.Library.AutoUpdater.ReleaseType.Unknown ? Duplicati.Library.AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel : rt;
try
{
{
var update = Duplicati.Library.AutoUpdater.UpdaterManager.CheckForUpdate(rt);
if (update != null)
FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion = update;
@@ -166,52 +146,40 @@ namespace Duplicati.Server
var updatertstring = FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion.ReleaseType;
if (string.Equals(updatertstring, "preview", StringComparison.OrdinalIgnoreCase))
updatertstring = Library.AutoUpdater.ReleaseType.Experimental.ToString();
if (!Enum.TryParse<Library.AutoUpdater.ReleaseType>(updatertstring, true, out updatert))
updatert = Duplicati.Library.AutoUpdater.ReleaseType.Nightly;
if (updatert == Duplicati.Library.AutoUpdater.ReleaseType.Unknown)
updatert = Duplicati.Library.AutoUpdater.ReleaseType.Nightly;
if (updatert > rt)
FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion = null;
}
if (FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion != null && Duplicati.Library.AutoUpdater.UpdaterManager.TryParseVersion(FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion.Version) > System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)
var updatedinfo = FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion;
if (updatedinfo != null && Duplicati.Library.AutoUpdater.UpdaterManager.TryParseVersion(updatedinfo.Version) > System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)
{
var package = updatedinfo.FindPackage();
FIXMEGlobal.DataConnection.RegisterNotification(
NotificationType.Information,
"Found update",
FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion.Displayname,
updatedinfo.Displayname,
null,
null,
"update:new",
null,
"NewUpdateFound",
null,
(self, all) => {
(self, all) =>
{
return all.FirstOrDefault(x => x.Action == "update:new") ?? self;
}
);
}
}
if (m_download)
{
lock(m_lock)
m_download = false;
var v = FIXMEGlobal.DataConnection.ApplicationSettings.UpdatedVersion;
if (v != null)
{
ThreadState = UpdatePollerStates.Downloading;
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
if (Duplicati.Library.AutoUpdater.UpdaterManager.DownloadAndUnpackUpdate(v, (pg) => { DownloadProgess = pg; }))
FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
}
}
DownloadProgess = 0;
if (ThreadState != UpdatePollerStates.Waiting)
@@ -225,14 +193,14 @@ namespace Duplicati.Server
// Guard against spin-loop
if (waitTime.TotalSeconds < 5)
waitTime = TimeSpan.FromSeconds(5);
// Guard against year-long waits
// A re-check does not cause an update check
if (waitTime.TotalDays > 1)
waitTime = TimeSpan.FromDays(1);
m_waitSignal.WaitOne(waitTime, true);
}
}
}
}
}
+5 -11
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -224,12 +224,12 @@ namespace Duplicati.Server.WebServer
server.Add(new RESTHandler());
string webroot = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string install_webroot = System.IO.Path.Combine(Library.AutoUpdater.UpdaterManager.InstalledBaseDir, "webroot");
string webroot = Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR;
string install_webroot = System.IO.Path.Combine(webroot, "webroot");
#if DEBUG
// Easy test for extensions while debugging
install_webroot = Library.AutoUpdater.UpdaterManager.InstalledBaseDir;
install_webroot = Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR;
if (!System.IO.Directory.Exists(System.IO.Path.Combine(webroot, "webroot")))
{
@@ -260,13 +260,7 @@ namespace Duplicati.Server.WebServer
// in the same folders as the running application, to avoid users
// that inadvertently expose top level folders
if (!string.IsNullOrWhiteSpace(userroot)
&&
(
userroot.StartsWith(Util.AppendDirSeparator(System.Reflection.Assembly.GetExecutingAssembly().Location), Library.Utility.Utility.ClientFilenameStringComparison)
||
userroot.StartsWith(Util.AppendDirSeparator(Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir), Library.Utility.Utility.ClientFilenameStringComparison)
)
)
&& userroot.StartsWith(Util.AppendDirSeparator(Duplicati.Library.Utility.Utility.getEntryAssembly().Location), Library.Utility.Utility.ClientFilenameStringComparison))
#endif
{
webroot = userroot;
+1 -11
View File
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32210.238
@@ -101,11 +101,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.AutoU
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Storj", "Duplicati\Library\Backend\Storj\Duplicati.Library.Backend.Storj.csproj", "{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Tardigrade", "Duplicati\Library\Backend\Tardigrade\Duplicati.Library.Backend.Tardigrade.csproj", "{11A8D03B-8CAE-4AD6-A5C6-E15D34DA2BDE}"
ProjectSection(ProjectDependencies) = postProject
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489} = {E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.TencentCOS", "Duplicati\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj", "{545DD6D4-9476-42D6-B51C-A28E000C489E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Executables", "Executables", "{FA88A246-EF8E-46E3-90AF-539B8C0A6ADE}"
@@ -357,10 +352,6 @@ Global
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}.Release|Any CPU.Build.0 = Release|Any CPU
{11A8D03B-8CAE-4AD6-A5C6-E15D34DA2BDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11A8D03B-8CAE-4AD6-A5C6-E15D34DA2BDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11A8D03B-8CAE-4AD6-A5C6-E15D34DA2BDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11A8D03B-8CAE-4AD6-A5C6-E15D34DA2BDE}.Release|Any CPU.Build.0 = Release|Any CPU
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -467,7 +458,6 @@ Global
{B93E3BF0-DAA7-49B7-B07D-0559C5816735} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{4D1B198B-F773-44F2-872B-2B283053323C} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{11A8D03B-8CAE-4AD6-A5C6-E15D34DA2BDE} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{545DD6D4-9476-42D6-B51C-A28E000C489E} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3} = {FA88A246-EF8E-46E3-90AF-539B8C0A6ADE}
{95B7DD83-2C5A-4F1E-8EA7-39654B2B236A} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Description>A backend debugging tool for Duplicati</Description>
<AssemblyName>Duplicati.CommandLine.BackendTester.Implementation</AssemblyName>
</PropertyGroup>
+26 -30
View File
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
@@ -31,7 +31,6 @@ namespace Duplicati.CommandLine.BackendTester
{
public class Program
{
/// <summary>
/// Used to maintain a reference to initialized system settings.
/// </summary>
@@ -63,13 +62,7 @@ namespace Duplicati.CommandLine.BackendTester
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
public static void RealMain(string[] _args)
public static int Main(string[] _args)
{
try
{
@@ -107,7 +100,7 @@ namespace Duplicati.CommandLine.BackendTester
foreach (string s in lines)
Console.WriteLine(s);
return;
return 0;
}
if (options.ContainsKey("tempdir") && !string.IsNullOrEmpty(options["tempdir"]))
@@ -129,14 +122,17 @@ namespace Duplicati.CommandLine.BackendTester
{
Console.WriteLine("Starting run no {0}", i);
if (!Run(args, options, i == 0))
return;
return 1;
}
Console.WriteLine("Unittest complete!");
return 0;
}
catch (Exception ex)
{
Console.WriteLine("Unittest failed: " + ex);
}
return 1;
}
static bool Run(List<string> args, Dictionary<string, string> options, bool first)
@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<AssemblyName>Duplicati.CommandLine.BackendTool.Implementation</AssemblyName>
</PropertyGroup>
+1 -7
View File
@@ -34,13 +34,7 @@ namespace Duplicati.CommandLine.BackendTool
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
public static int RealMain(string[] _args)
public static int Main(string[] _args)
{
bool debugoutput = false;
try
+2 -2
View File
@@ -24,6 +24,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Duplicati.Library.AutoUpdater;
using Duplicati.Library.Common;
using FilterGroup = Duplicati.Library.Utility.FilterGroup;
@@ -117,8 +118,7 @@ namespace Duplicati.CommandLine
tp = tp.Replace("%VERSION%", License.VersionNumbers.Version);
tp = tp.Replace("%BACKENDS%", string.Join(", ", Library.DynamicLoader.BackendLoader.Keys));
tp = tp.Replace("%MONO%", Library.Utility.Utility.IsMono ? "mono " : "");
tp = tp.Replace("%APP_PATH%", System.IO.Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location));
tp = tp.Replace("%APP_PATH%", Path.Combine(UpdaterManager.INSTALLATIONDIR, PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.CommandLine)));
tp = tp.Replace("%PATH_SEPARATOR%", System.IO.Path.PathSeparator.ToString());
tp = tp.Replace("%EXAMPLE_SOURCE_PATH%", Platform.IsClientPosix ? "/source" : @"D:\source");
tp = tp.Replace("%EXAMPLE_SOURCE_FILE%", Platform.IsClientPosix ? "/source/myfile.txt" : @"D:\source\file.txt");
+42 -38
View File
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
@@ -25,7 +25,7 @@ using System.Linq;
using Duplicati.Library.Localization.Short;
using System.IO;
using Duplicati.Library.Interface;
using Duplicati.Library.Logging;
using Duplicati.Library.AutoUpdater;
namespace Duplicati.CommandLine
{
@@ -39,12 +39,6 @@ namespace Duplicati.CommandLine
/// The main entry point for the application.
/// </summary>
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
public static int RealMain(string[] args)
{
Library.UsageReporter.Reporter.Initialize();
FROM_COMMANDLINE = true;
@@ -100,7 +94,7 @@ namespace Duplicati.CommandLine
private static int ShowChangeLog(TextWriter outwriter)
{
var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "changelog.txt");
var path = System.IO.Path.Combine(UpdaterManager.INSTALLATIONDIR, "changelog.txt");
outwriter.WriteLine(System.IO.File.ReadAllText(path));
return 0;
}
@@ -113,18 +107,28 @@ namespace Duplicati.CommandLine
if (update != null && update.Version != Library.AutoUpdater.UpdaterManager.SelfVersion.Version)
{
outwriter.WriteLine("Found update \"{0}\", downloading ...", update.Displayname);
long lastpg = 0;
Library.AutoUpdater.UpdaterManager.DownloadAndUnpackUpdate(update, f =>
var package = update.FindPackage();
if (package == null)
{
var npg = (long)(f * 100);
if (Math.Abs(npg - lastpg) >= 5 || (npg == 100 && lastpg != 100))
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 =>
{
lastpg = npg;
outwriter.WriteLine("Downloading {0}% ...", npg);
}
});
outwriter.WriteLine("Update \"{0}\" ({1}) installed, using on next launch", update.Displayname, update.Version);
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);
}
}
}
+7 -7
View File
@@ -19,7 +19,7 @@ See duplicati.commandline.exe help <topic> for more information.
Formats: date, time, size, encryption, compression
Advanced: mail, advanced, returncodes, filter, filter-groups, <option>
http://www.duplicati.com/ Version: %VERSION%
https://duplicati.com/ Version: %VERSION%
@@ -30,7 +30,7 @@ Show the last changes to Duplicati
==================================
Duplicati updates automatically when a newer version is available. The following command will show a list of versions and changes.
%MONO%"%APP_PATH%" changelog
"%APP_PATH%" changelog
# =============================================================================
> duplicati.commandline.exe help system-info
@@ -40,7 +40,7 @@ Show system information as seen by Duplicati
============================================
Issue the following command to see a variety of system information relevant to Duplicati.
%MONO%"%APP_PATH%" system-info
"%APP_PATH%" system-info
# =============================================================================
> duplicati.commandline.exe help example
@@ -50,19 +50,19 @@ Make an encrypted backup
========================
The following command will make an encrypted backup and store it on an FTP server. As no passphrase is specified, the user is prompted for a password. The upload speed is throttled to 500kB/s. Backups older than one month are considered old. Old backups are deleted automatically. The maximum file size is limited to 50MB (default).
%MONO%"%APP_PATH%" backup ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_PATH%" --throttle=500kB
"%APP_PATH%" backup ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_PATH%" --throttle=500kB
Search for files in a backup
============================
The backup can be searched for specific files. The following command returns all files "%EXAMPLE_SOURCE_FILE%" of the last backup. File names can also contain the wildcards * and ?.
%MONO%"%APP_PATH%" find ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%"
"%APP_PATH%" find ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%"
Restore a specific file
=======================
The following command will restore "%EXAMPLE_SOURCE_FILE%" from the backup in the latest version to its original destination and overwrite an existing file.
%MONO%"%APP_PATH%" restore ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%" --overwrite
"%APP_PATH%" restore ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%" --overwrite
# =============================================================================
@@ -469,7 +469,7 @@ Duplicati has several built-in groups of filters, which include commonly exclude
Duplicati can send email notifications after each operation. Use the send-mail command to test this:
%MONO%"%APP_PATH%" send-mail --send-mail-to=<email-address>
"%APP_PATH%" send-mail --send-mail-to=<email-address>
--send-mail-to=<email-address>
+4 -10
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2024, The Duplicati Team
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
@@ -27,19 +27,13 @@ namespace Duplicati.CommandLine.RecoveryTool
{
public static class Program
{
private delegate int CommandRunner(List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
private delegate int CommandRunner(List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter);
public static int RealMain(string[] _args)
public static int Main(string[] _args)
{
try
{
@@ -59,18 +59,12 @@ namespace Duplicati.GUI.TrayIcon
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.RequiresRespawn = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, Duplicati.Library.AutoUpdater.AutoUpdateStrategy.Never);
}
public static void RealMain(string[] _args)
public static int Main(string[] _args)
{
List<string> args = new List<string>(_args);
Dictionary<string, string> options = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args);
if (Platform.IsClientWindows && (Duplicati.Library.AutoUpdater.UpdaterManager.IsRunningInUpdateEnvironment || !Duplicati.Library.Utility.Utility.ParseBoolOption(options, DETACHED_PROCESS)))
if (Platform.IsClientWindows && !Duplicati.Library.Utility.Utility.ParseBoolOption(options, DETACHED_PROCESS))
Duplicati.Library.Utility.Win32.AttachConsole(Duplicati.Library.Utility.Win32.ATTACH_PARENT_PROCESS);
foreach (string s in args)
@@ -97,7 +91,7 @@ namespace Duplicati.GUI.TrayIcon
foreach (Library.Interface.ICommandLineArgument arg in Duplicati.Server.Program.SupportedCommands)
Console.WriteLine("--{0}: {1}", arg.Name, arg.LongDescription);
return;
return 0;
}
options.TryGetValue(BROWSER_COMMAND_OPTION, out _browser_command);
@@ -115,7 +109,7 @@ namespace Duplicati.GUI.TrayIcon
}
catch (Server.SingleInstance.MultipleInstanceException)
{
return;
return 1;
}
// We have a hosted server, if this is the first run,
@@ -176,6 +170,8 @@ namespace Duplicati.GUI.TrayIcon
serverURL = new Uri(url);
StartTray(_args, options, hosted, password, saltedpassword);
return 0;
}
private static void StartTray(string[] _args, Dictionary<string, string> options, HostedInstanceKeeper hosted, string password, bool saltedpassword)
@@ -143,10 +143,6 @@ namespace Duplicati.Library.AutoUpdater
if (string.IsNullOrWhiteSpace(channelstring))
channelstring = BuildUpdateChannel;
if (string.IsNullOrWhiteSpace(channelstring))
channelstring = UpdaterManager.BaseVersion.ReleaseType;
// Update from older builds
if (string.Equals(channelstring, "preview", StringComparison.OrdinalIgnoreCase))
channelstring = ReleaseType.Experimental.ToString();
@@ -181,21 +177,42 @@ namespace Duplicati.Library.AutoUpdater
get { return string.Format(ReadResourceText(UPDATE_INSTALL_FILE, OEM_UPDATE_INSTALL_FILE), Guid.NewGuid().ToString("N")); }
}
public static System.Security.Cryptography.RSACryptoServiceProvider SignKey
public static System.Security.Cryptography.RSACryptoServiceProvider[] SignKeys
{
get
{
{
var keys = new List<System.Security.Cryptography.RSACryptoServiceProvider>();
try
{
var key = System.Security.Cryptography.RSA.Create();
key.FromXmlString(ReadResourceText(UPDATE_KEY, OEM_UPDATE_KEY));
return (System.Security.Cryptography.RSACryptoServiceProvider)key;
var src = ReadResourceText(UPDATE_KEY, OEM_UPDATE_KEY);
// Allow multiple keys, one per line
// For fallback, read the whole string as a key, in case there are old ones with line breaks
var keystrings = src.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Prepend(src.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct();
foreach (var str in keystrings)
{
try
{
var key = System.Security.Cryptography.RSA.Create();
key.FromXmlString(str);
keys.Add((System.Security.Cryptography.RSACryptoServiceProvider)key);
}
catch
{ }
}
}
catch
{
}
return null;
return keys.ToArray();
}
}
}
@@ -17,7 +17,7 @@
<ItemGroup>
<EmbeddedResource Include="AutoUpdateAppName.txt" />
<EmbeddedResource Include="AutoUpdateSignKey.txt" />
<EmbeddedResource Include="AutoUpdateSignKeys.txt" />
<EmbeddedResource Include="AutoUpdateURL.txt" />
<EmbeddedResource Include="AutoUpdateFolderReadme.txt" />
<EmbeddedResource Include="AutoUpdateInstallIDTemplate.txt" />
@@ -1,35 +1,66 @@
// 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.
using System;
namespace Duplicati.Library.AutoUpdater
{
public class FileEntry
{
public string Path;
public string MD5;
public string SHA256;
public DateTime LastWriteTime;
public bool Ignore;
}
}
// 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.
using System;
using System.IO;
namespace Duplicati.Library.AutoUpdater
{
/// <summary>
/// An installer entry, describing an architecture specific package
/// </summary>
public class PackageEntry
{
/// <summary>
/// The urls for the updater payload
/// </summary>
public string[] RemoteUrls;
/// <summary>
/// The length of the payload
/// </summary>
public long Length;
/// <summary>
/// The MD5 hash of the payload
/// </summary>
public string MD5;
/// <summary>
/// The SHA256 hash of the payload
/// </summary>
public string SHA256;
/// <summary>
/// The package type id
/// </summary>
public string PackageTypeId;
/// <summary>
/// Gets the name of the package file
/// </summary>
/// <returns>The filename of the package</returns>
public string GetFilename()
{
var guess = Path.GetFileName(new Uri(RemoteUrls[0]).LocalPath);
if (string.IsNullOrWhiteSpace(guess))
guess = "update.bin";
return guess;
}
}
}
@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Duplicati.Library.Common;
using Duplicati.Library.Utility;
namespace Duplicati.Library.AutoUpdater
{
/// <summary>
/// Helper method for various packaging related settings
/// </summary>
public static class PackageHelper
{
/// <summary>
/// The named executables that are installed via packages
/// </summary>
public enum NamedExecutable
{
/// <summary>
/// The primary executable, with an embedded GUI
/// </summary>
TrayIcon,
/// <summary>
/// The primary commandline application
/// </summary>
CommandLine,
/// <summary>
/// The autoupdater
/// </summary>
AutoUpdater,
/// <summary>
/// The server runner
/// </summary>
Server,
/// <summary>
/// The windows service helper wrapping the server
/// </summary>
WindowsService,
/// <summary>
/// The backend manipulation tool
/// </summary>
BackendTool,
/// <summary>
/// The recovery tool
/// </summary>
RecoveryTool,
/// <summary>
/// The backend testing tool
/// </summary>
BackendTester
}
/// <summary>
/// Returns the operating system mappings of project executables
/// </summary>
/// <param name="exe">The executable to get then name for</param>
/// <returns>The name of the executable on the current operating system</returns>
/// <remarks>Note that the values here mirror the values in the ReleaseBuilder tool, so changes should be coordinated between the two</remarks>
public static string GetExecutableName(NamedExecutable exe)
=> exe switch
{
NamedExecutable.TrayIcon => Platform.IsClientWindows ? "Duplicati.GUI.TrayIcon.exe" : "duplicati",
NamedExecutable.CommandLine => Platform.IsClientWindows ? "Duplicati.CommandLine.exe" : "duplicati-cli",
NamedExecutable.AutoUpdater => Platform.IsClientWindows ? "Duplicati.CommandLine.AutoUpdater.exe" : "duplicati-autoupdater",
NamedExecutable.Server => Platform.IsClientWindows ? "Duplicati.Server.exe" : "duplicati-server",
NamedExecutable.WindowsService => "Duplicati.WindowsServer.exe",
NamedExecutable.BackendTool => Platform.IsClientWindows ? "Duplicati.CommandLine.BackendTool.exe" : "duplicati-backend-tool",
NamedExecutable.RecoveryTool => Platform.IsClientWindows ? "Duplicati.Command.RecoveryTool.exe" : "duplicati-recovery-tool",
NamedExecutable.BackendTester => Platform.IsClientWindows ? "Duplicati.CommandLine.BackendTester.exe" : "duplicati-backend-tester",
_ => throw new ArgumentException($"Named executable not known: {exe}", nameof(exe))
};
}
}
+29 -51
View File
@@ -20,27 +20,16 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Duplicati.Library.Common.IO;
namespace Duplicati.Library.AutoUpdater
{
public static class Program
{
public static int Main(string[] args)
public static int Main(string[] _args)
{
// Ignore webroot during startup verification
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, AutoUpdateStrategy.Never);
}
public static int RealMain(string[] _args)
{
// Enable webroot checks for the verifier
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = false;
var args = new List<string>(_args);
var args = _args.ToList();
Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args);
if (args.Count == 0)
@@ -62,26 +51,6 @@ namespace Duplicati.Library.AutoUpdater
case "help":
WriteUsage();
return 0;
case "list":
{
var versions = UpdaterManager.FindInstalledVersions();
var selfdir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (string.Equals(Util.AppendDirSeparator(selfdir), Util.AppendDirSeparator(UpdaterManager.InstalledBaseDir)))
versions = versions.Union(new KeyValuePair<string, UpdateInfo>[] { new KeyValuePair<string, UpdateInfo>(selfdir, UpdaterManager.SelfVersion) });
Console.WriteLine(string.Join(Environment.NewLine, versions.Select(x => string.Format(" {0} {1} ({2})", (x.Value.Version == UpdaterManager.SelfVersion.Version ? "*" : "-"), x.Value.Displayname, x.Value.Version))));
return 0;
}
case "verify":
{
var versions = UpdaterManager.FindInstalledVersions();
var selfdir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (string.Equals(Util.AppendDirSeparator(selfdir), Util.AppendDirSeparator(UpdaterManager.InstalledBaseDir)))
versions = versions.Union(new KeyValuePair<string, UpdateInfo>[] { new KeyValuePair<string, UpdateInfo>(selfdir, UpdaterManager.SelfVersion) });
Console.WriteLine(string.Join(Environment.NewLine, versions.Select(x => string.Format(" {0} {1} ({2}): {3}", (x.Value.Version == UpdaterManager.SelfVersion.Version ? "*" : "-"), x.Value.Displayname, x.Value.Version, UpdaterManager.VerifyUnpackedFolder(x.Key, x.Value) ? "Valid" : "*** Modified ***"))));
return 0;
}
case "check":
{
var update = UpdaterManager.CheckForUpdate();
@@ -94,7 +63,7 @@ namespace Duplicati.Library.AutoUpdater
return 0;
}
case "install":
case "download":
{
var update = UpdaterManager.CheckForUpdate();
if (update == null || update.Version == UpdaterManager.SelfVersion.Version)
@@ -102,19 +71,30 @@ namespace Duplicati.Library.AutoUpdater
Console.WriteLine("You are running the latest version: {0} ({1})", UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
return 0;
}
Console.WriteLine("Downloading update \"{0}\" ...", update.Displayname);
long lastpg = 0;
UpdaterManager.DownloadAndUnpackUpdate(update, f => {
var npg = (long)(f*100);
if (Math.Abs(npg - lastpg) >= 5 || (npg == 100 && lastpg != 100))
var package = update.FindPackage();
if (package == null)
{
Console.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());
Console.WriteLine("Downloading update \"{0}\" to {1} ...", update.Displayname, filename);
long lastpg = 0;
UpdaterManager.DownloadUpdate(update, package, filename, f =>
{
lastpg = npg;
Console.WriteLine("Downloading {0}% ...", npg);
}
});
var npg = (long)(f * 100);
if (Math.Abs(npg - lastpg) >= 5 || (npg == 100 && lastpg != 100))
{
lastpg = npg;
Console.WriteLine("Downloading {0}% ...", npg);
}
});
}
Console.WriteLine("New version \"{0}\" installed!", update.Displayname);
Console.WriteLine("New version \"{0}\" downloaded!", update.Displayname);
return 0;
}
default:
@@ -127,19 +107,17 @@ namespace Duplicati.Library.AutoUpdater
private static void WriteUsage()
{
Console.WriteLine("Usage:{0}\t{1}{2} [LIST|VERIFY|CHECK|INSTALL|HELP]", Environment.NewLine, Duplicati.Library.Utility.Utility.IsMono ? "mono " : "", System.IO.Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location));
Console.WriteLine("Usage:{0}\t{1}{2} [CHECK|DOWNLOAD|HELP]", Environment.NewLine, PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.AutoUpdater));
Console.WriteLine();
Console.WriteLine("Environment variables:");
Console.WriteLine();
Console.WriteLine("{0} - Disables updates completely", string.Format(UpdaterManager.SKIPUPDATE_ENVNAME_TEMPLATE, AutoUpdateSettings.AppName));
Console.WriteLine("{0} - Choose how to handle updates, valid settings: {1}", string.Format(UpdaterManager.UPDATE_STRATEGY_ENVNAME_TEMPLATE, AutoUpdateSettings.AppName), string.Join(", ", Enum.GetNames(typeof(AutoUpdateStrategy))));
Console.WriteLine("{0} - Use alternate updates urls", string.Format(AutoUpdateSettings.UPDATEURL_ENVNAME_TEMPLATE, AutoUpdateSettings.AppName));
Console.WriteLine("{0} - Choose different channel than the default {1}, valid settings: {2}", string.Format(AutoUpdateSettings.UPDATECHANNEL_ENVNAME_TEMPLATE, AutoUpdateSettings.AppName), AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel, string.Join(",", Enum.GetNames(typeof(ReleaseType)).Where( x => x != ReleaseType.Unknown.ToString())));
Console.WriteLine("{0} - Choose different channel than the default {1}, valid settings: {2}", string.Format(AutoUpdateSettings.UPDATECHANNEL_ENVNAME_TEMPLATE, AutoUpdateSettings.AppName), AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel, string.Join(",", Enum.GetNames(typeof(ReleaseType)).Where(x => x != ReleaseType.Unknown.ToString())));
Console.WriteLine();
Console.WriteLine("Updates are downloaded from: {0}", string.Join(";", AutoUpdateSettings.URLs));
Console.WriteLine("Updates are installed in: {0}", UpdaterManager.INSTALLDIR);
Console.WriteLine("The base version is \"{0}\" ({1}) and is installed in: {2}", UpdaterManager.BaseVersion.Displayname, UpdaterManager.BaseVersion.Version, UpdaterManager.InstalledBaseDir);
Console.WriteLine("This version is \"{0}\" ({1}) and is installed in: {2}", UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version, System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
Console.WriteLine("Machine settings are installed in: {0}", UpdaterManager.UPDATEDIR);
Console.WriteLine("This version is \"{0}\" ({1}) and is installed in: {2}", UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version, UpdaterManager.INSTALLATIONDIR);
Console.WriteLine();
}
}
+78 -9
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2024, The Duplicati Team
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
@@ -20,6 +20,7 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
namespace Duplicati.Library.AutoUpdater
{
@@ -48,23 +49,91 @@ namespace Duplicati.Library.AutoUpdater
public class UpdateInfo
{
public string UpdateFromV1Url;
public string UpdateFromV2Url;
public string Displayname;
public string Version;
public DateTime ReleaseTime;
public string ReleaseType;
public string UpdateSeverity;
public string ChangeInfo;
public long CompressedSize;
public long UncompressedSize;
public string SHA256;
public string MD5;
public string[] RemoteURLS;
public FileEntry[] Files;
public int PackageUpdaterVersion;
/// <summary>
/// Legacy entry, do not use
/// </summary>
[Obsolete("Only kept to avoid v2.0.7.x and earlier releases from crashing on nulls")]
public string[] RemoteURLS = Array.Empty<string>();
/// <summary>
/// List of installer packages
/// </summary>
public PackageEntry[] Packages;
/// <summary>
/// Link to a generic download page
/// </summary>
public string GenericUpdatePageUrl = string.Empty;
public UpdateInfo Clone()
/// <summary>
/// Finds a package that matches the <paramref name="packageTypeId"/>
/// </summary>
/// <param name="packageTypeId">The package type id; <c>null</c> uses the currently installed package type id</param>
/// <returns>The matching package or <c>null</c></returns>
public PackageEntry? FindPackage(string packageTypeId = null)
{
return (UpdateInfo)this.MemberwiseClone();
if (!string.IsNullOrWhiteSpace(UpdateFromV2Url) || PackageUpdaterVersion != UpdaterManager.SUPPORTED_PACKAGE_UPDATER_VERSION)
return null;
packageTypeId ??= UpdaterManager.PackageTypeId;
if (string.IsNullOrWhiteSpace(packageTypeId) || Packages == null)
return null;
return Packages.FirstOrDefault(x => string.Equals(x.PackageTypeId, packageTypeId, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Gets the generic update page url for the <paramref name="packageTypeId"/>
/// </summary>
/// <param name="packageTypeId">The package type id; <c>null</c> uses the currently installed package type id</param>
/// <returns>The generic update page url</returns>
public string GetGenericUpdatePageUrl(string packageTypeId = null)
{
var baseurl = string.IsNullOrWhiteSpace(UpdateFromV2Url)
? GenericUpdatePageUrl
: UpdateFromV2Url;
packageTypeId ??= UpdaterManager.PackageTypeId;
if (string.IsNullOrWhiteSpace(packageTypeId))
return GenericUpdatePageUrl;
return GenericUpdatePageUrl + $"{(GenericUpdatePageUrl.IndexOf('?') > 0 ? "&" : "?")}packagetypeid={Uri.EscapeDataString(packageTypeId ?? "")}";
}
/// <summary>
/// Gets the updated package urls for the <paramref name="packageTypeId"/>
/// </summary>
/// <param name="packageTypeId">The package type id; <c>null</c> uses the currently installed package type id</param>
/// <returns>The matching update urls</returns>
public string[] GetUpdateUrls(string packageTypeId = null)
{
packageTypeId ??= UpdaterManager.PackageTypeId;
var package = FindPackage(packageTypeId);
if (package != null)
return package.RemoteUrls;
var generic = GetGenericUpdatePageUrl(packageTypeId);
if (!string.IsNullOrWhiteSpace(generic))
return [generic];
return Array.Empty<string>();
}
/// <summary>
/// Creates a copy of the instance by serializing and deseriaizing the data
/// </summary>
/// <returns>A cloned copy</returns>
public UpdateInfo Clone()
=> System.Text.Json.JsonSerializer.Deserialize<UpdateInfo>(
System.Text.Json.JsonSerializer.Serialize(this)
);
}
}
File diff suppressed because it is too large Load Diff
@@ -13,7 +13,6 @@
<PackageReference Include="Microsoft.Azure.Storage.Queue" Version="11.0.1" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.24" />
<PackageReference Include="Microsoft.Rest.ClientRuntime.Azure" Version="3.3.19" />
<PackageReference Include="System.Spatial" Version="5.8.4" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
@@ -168,8 +168,12 @@ namespace Duplicati.Library.Backend
{
get
{
var defaults = new Amazon.S3.AmazonS3Config();
var defaults = new Amazon.S3.AmazonS3Config()
{
// If this is not set, accessing the property will trigger an expensive operation (~30 seconds)
// to get the region endpoint. This stalls the supported commands list.
UseArnRegion = false
};
var exts =
typeof(Amazon.S3.AmazonS3Config).GetProperties().Where(x => x.CanRead && x.CanWrite && (x.PropertyType == typeof(string) || x.PropertyType == typeof(bool) || x.PropertyType == typeof(int) || x.PropertyType == typeof(long) || x.PropertyType.IsEnum))
@@ -182,16 +186,12 @@ namespace Duplicati.Library.Backend
null,
x.PropertyType.IsEnum ? Enum.GetNames(x.PropertyType) : null));
var normal = new ICommandLineArgument[] {
new CommandLineArgument("access_key_secret", CommandLineArgument.ArgumentType.Password, Strings.Idrivee2Backend.KeySecretDescriptionShort, Strings.Idrivee2Backend.KeySecretDescriptionLong, null, new[]{"auth-password"}, null),
new CommandLineArgument("access_key_id", CommandLineArgument.ArgumentType.String, Strings.Idrivee2Backend.KeyIDDescriptionShort, Strings.Idrivee2Backend.KeyIDDescriptionLong,null, new[]{"auth-username"}, null)
};
return normal.Union(exts).ToList();
}
}
@@ -1,4 +1,4 @@
// Copyright (C) 2024, The Duplicati Team
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
@@ -36,19 +36,6 @@ namespace Duplicati.Library
private readonly OAuthHttpMessageHandler m_authenticator;
static OAuthHttpClient()
{
// There is a regression in some versions of Mono (6.0) where an HttpClient
// with a BaseAddress cannot make requests to URLs that begin with '/'.
// https://github.com/mono/mono/issues/14630
// https://www.mono-project.com/docs/faq/known-issues/urikind-relativeorabsolute/.
if (Utility.Utility.IsMono)
{
FieldInfo field = typeof(System.Uri).GetField("useDotNetRelativeOrAbsolute", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic);
field?.SetValue(null, true);
}
}
public OAuthHttpClient(string authid, string protocolKey)
: this(CreateMessageHandler(authid, protocolKey))
{
@@ -127,7 +114,7 @@ namespace Duplicati.Library
throw new TimeoutException($"HTTP timeout {this.Timeout} exceeded.");
}
}
/// <summary>
/// Prevents authentication from being applied on the given request
/// </summary>
@@ -1,23 +1,23 @@
// 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.
// 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.
using Duplicati.Library.Backend.MicrosoftGraph;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Interface;
@@ -93,12 +93,13 @@ namespace Duplicati.Library.Backend
private const int UPLOAD_SESSION_FRAGMENT_MULTIPLE_SIZE = 320 * 1024;
/// <summary>
/// Whether to use the HttpClient class for HTTP requests.
/// Default is false when running under Mono (as it seems it might be causing a memory leak in some environments / versions)
/// but true in other cases (where these memory leaks haven't been reproduced).
/// Whether to use the HttpClient class for HTTP requests as default.
/// </summary>
private static readonly bool USE_HTTP_CLIENT_DEFAULT = Utility.Utility.IsMono ? false : true;
private static readonly bool USE_HTTP_CLIENT_DEFAULT = true;
/// <summary>
/// Cached copy of the PATH method
/// </summary>
private static readonly HttpMethod PatchMethod = new HttpMethod("PATCH");
/// <summary>
@@ -203,8 +204,8 @@ namespace Duplicati.Library.Backend
return this.DescriptionTemplate(
"Microsoft Service Agreement",
SERVICES_AGREEMENT,
"Microsoft Online Privacy Statement",
PRIVACY_STATEMENT);
"Microsoft Online Privacy Statement",
PRIVACY_STATEMENT);
}
}
@@ -554,7 +555,7 @@ namespace Duplicati.Library.Backend
cancelToken).ConfigureAwait(false);
}
}
// Error handling based on recommendations here:
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession#best-practices
if (attempt >= retryCount - 1)
@@ -941,7 +942,7 @@ namespace Duplicati.Library.Backend
return this.ParseResponse<T>(response);
}
}
private IEnumerable<T> Enumerate<T>(string url)
{
string nextUrl = url;
@@ -9,6 +9,13 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="uplink.NET" Version="2.12.3328" />
<!--
Disabled bundling binary components for now, as they crash Duplicati
<PackageReference Include="uplink.NET.Win" Version="2.12.3328" />
<PackageReference Include="uplink.NET.Linux" Version="2.12.3328" />
<PackageReference Include="uplink.NET.Mac" Version="2.12.3328" />
-->
</ItemGroup>
<ItemGroup>
@@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="uplink.NET" Version="2.12.3328" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Interface\Duplicati.Library.Interface.csproj" />
<ProjectReference Include="..\..\Common\Duplicati.Library.Common.csproj" />
<ProjectReference Include="..\..\Localization\Duplicati.Library.Localization.csproj" />
<ProjectReference Include="..\..\Utility\Duplicati.Library.Utility.csproj" />
<ProjectReference Include="..\Storj\Duplicati.Library.Backend.Storj.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
@@ -1,35 +0,0 @@
// 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.
using Duplicati.Library.Localization.Short;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Duplicati.Library.Backend.Strings
{
internal static class Tardigrade
{
public static string DisplayName { get { return LC.L(@"Tardigrade Decentralised Cloud Storage (Deprecated)"); } }
public static string Description { get { return LC.L(@"This backend can read and write data to the Tardigrade Decentralized Cloud Storage. It is deprecated - please move over to the new Storj DCS. (Deprecated)"); } }
}
}
@@ -1,100 +0,0 @@
// 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.
using Duplicati.Library.Interface;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using uplink.NET.Interfaces;
using uplink.NET.Models;
using uplink.NET.Services;
namespace Duplicati.Library.Backend.Tardigrade
{
/// <summary>
/// This backend is deprecated! It will be removed in the future.
/// Tardigrade renamed to Storj DCS in Spring 2021 - but existing Tardigrade-Configurations could not be easily renamed.
/// So we decided to "copy" Tardigrade over to the new name Storj DCS. In order to reduce duplicate code, the old
/// Tardigrade-Backend hands it's logic over to Storj DCS. Only the UI-specific part, the config-parameters and
/// the protocol-key stay here named for Tardigrade.
/// </summary>
public class Tardigrade : Duplicati.Library.Backend.Storj.Storj, IStreamingBackend
{
private const string TARDIGRADE_AUTH_METHOD = "tardigrade-auth-method";
private const string TARDIGRADE_SATELLITE = "tardigrade-satellite";
private const string TARDIGRADE_API_KEY = "tardigrade-api-key";
private const string TARDIGRADE_SECRET = "tardigrade-secret";
private const string TARDIGRADE_SHARED_ACCESS = "tardigrade-shared-access";
private const string TARDIGRADE_BUCKET = "tardigrade-bucket";
private const string TARDIGRADE_FOLDER = "tardigrade-folder";
private const string PROTOCOL_KEY = "tardigrade";
// ReSharper disable once UnusedMember.Global
// This constructor is needed by the BackendLoader.
public Tardigrade():base()
{
}
// ReSharper disable once UnusedMember.Global
// This constructor is needed by the BackendLoader.
public Tardigrade(string url, Dictionary<string, string> options) :base(url, options)
{
}
public new string DisplayName
{
get { return Strings.Tardigrade.DisplayName; }
}
public new string ProtocolKey => PROTOCOL_KEY;
public new IList<ICommandLineArgument> SupportedCommands
{
get
{
return new List<ICommandLineArgument>(new ICommandLineArgument[] {
new CommandLineArgument(TARDIGRADE_AUTH_METHOD, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjAuthMethodDescriptionShort, Strings.Storj.StorjAuthMethodDescriptionLong, "API key", null, null),
new CommandLineArgument(TARDIGRADE_SATELLITE, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjSatelliteDescriptionShort, Strings.Storj.StorjSatelliteDescriptionLong, "us1.storj.io:7777", null, null),
new CommandLineArgument(TARDIGRADE_API_KEY, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjAPIKeyDescriptionShort, Strings.Storj.StorjAPIKeyDescriptionLong, null, null, null),
new CommandLineArgument(TARDIGRADE_SECRET, CommandLineArgument.ArgumentType.Password, Strings.Storj.StorjSecretDescriptionShort, Strings.Storj.StorjSecretDescriptionLong, null, null, null),
new CommandLineArgument(TARDIGRADE_SHARED_ACCESS, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjSharedAccessDescriptionShort, Strings.Storj.StorjSharedAccessDescriptionLong, null, null, null),
new CommandLineArgument(TARDIGRADE_BUCKET, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjBucketDescriptionShort, Strings.Storj.StorjBucketDescriptionLong, null, null, null),
new CommandLineArgument(TARDIGRADE_FOLDER, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjFolderDescriptionShort, Strings.Storj.StorjFolderDescriptionLong, null, null, null),
});
}
}
public new string Description
{
get
{
return Strings.Tardigrade.Description;
}
}
}
}
@@ -26,7 +26,6 @@
<ProjectReference Include="..\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj" />
<ProjectReference Include="..\Backend\Storj\Duplicati.Library.Backend.Storj.csproj" />
<ProjectReference Include="..\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj" />
<ProjectReference Include="..\Backend\Tardigrade\Duplicati.Library.Backend.Tardigrade.csproj" />
<ProjectReference Include="..\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj" />
<ProjectReference Include="..\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj" />
</ItemGroup>
@@ -7,18 +7,8 @@
</PropertyGroup>
<ItemGroup>
<!-- <PackageReference Include="AlphaFS" Version="2.2.6" />
<PackageReference Include="AlphaVSS" Version="2.0.0" /> -->
<Reference Include="AlphaFS">
<HintPath>..\..\..\thirdparty\alphafs\AlphaFS.dll</HintPath>
</Reference>
<Reference Include="AlphaVSS">
<HintPath>..\..\..\thirdparty\alphavss\AlphaVSS.Common.dll</HintPath>
</Reference>
<!-- TODO: Include the native files -->
<!-- <Import Project="..\..\..\packages\AlphaVSS.1.4.0\build\net45\AlphaVSS.targets" Condition="Exists('..\..\..\packages\AlphaVSS.1.4.0\build\net45\AlphaVSS.targets')" /> -->
<PackageReference Include="AlphaFS" Version="2.2.6" />
<PackageReference Include="AlphaVSS" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.7.0" />
<PackageReference Include="Mono.Posix" Version="7.1.0-final.1.21458.1" />
@@ -38,6 +38,7 @@ namespace Duplicati.Library.Interface
/// Called when the operation finishes
/// </summary>
/// <param name="result">The result object, if this derives from an exception, the operation failed</param>
void OnFinish(object result);
/// <param name="exception">The exception that stopped the backup, or null</param>
void OnFinish(object result, Exception exception);
}
}
+40 -32
View File
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
@@ -441,7 +441,7 @@ namespace Duplicati.Library.Main
r.Interrupted = false;
}
OnOperationComplete(result);
OnOperationComplete(result, null);
Logging.Log.WriteInformationMessage(LOGTAG, "CompletedOperation", Strings.Controller.CompletedOperationMessage(m_options.MainAction));
@@ -469,23 +469,26 @@ namespace Duplicati.Library.Main
db.WriteResults();
}
OnOperationComplete(result);
// Do not propagate the cancel exception
OnOperationComplete(result, null);
}
catch { }
}
else
{
// Perform the module shutdown
OnOperationComplete(ex);
OnOperationComplete(ex, ex);
}
return result;
}
else
{
try
Logging.Log.WriteErrorMessage(LOGTAG, "FailedOperation", ex, Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message));
if (result is BasicResults basicResults)
{
if (result is BasicResults basicResults)
try
{
basicResults.OperationProgressUpdater.UpdatePhase(OperationPhase.Error);
basicResults.Fatal = true;
@@ -498,13 +501,18 @@ namespace Duplicati.Library.Main
db.WriteResults();
}
}
// Report the result, and the failure
OnOperationComplete(result, ex);
}
catch { }
}
else
{
// Perform the module shutdown
OnOperationComplete(ex, ex);
}
catch { }
OnOperationComplete(ex);
Logging.Log.WriteErrorMessage(LOGTAG, "FailedOperation", ex, Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message));
throw;
}
@@ -538,13 +546,13 @@ namespace Duplicati.Library.Main
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = uiLocale;
}
private void OnOperationComplete(object result)
private void OnOperationComplete(object result, Exception exception)
{
if (m_options != null && m_options.LoadedModules != null)
{
foreach (KeyValuePair<bool, Library.Interface.IGenericModule> mx in m_options.LoadedModules)
if (mx.Key && mx.Value is IGenericCallbackModule module)
try { module.OnFinish(result); }
try { module.OnFinish(result, exception); }
catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, $"OnFinishError{mx.Key}", ex, "OnFinish callback {0} failed: {1}", mx.Key, ex.Message); }
foreach (KeyValuePair<bool, Library.Interface.IGenericModule> mx in m_options.LoadedModules)
@@ -31,9 +31,9 @@ CREATE UNIQUE INDEX "FileLookupPath" ON "FileLookup" ("PrefixID", "Path", "Block
INSERT INTO "PathPrefix" ("Prefix")
SELECT DISTINCT
CASE SUBSTR("Path", LENGTH("Path")) WHEN '/' THEN
rtrim(SUBSTR("Path", 1, LENGTH("Path")-1), replace(replace(SUBSTR("Path", 1, LENGTH("Path")-1), "\", "/"), '/', ''))
rtrim(SUBSTR("Path", 1, LENGTH("Path")-1), replace(replace(SUBSTR("Path", 1, LENGTH("Path")-1), '\', '/'), '/', ''))
ELSE
rtrim("Path", replace(replace("Path", "\", "/"), '/', ''))
rtrim("Path", replace(replace("Path", '\', '/'), '/', ''))
END AS "Prefix"
FROM "File";
@@ -50,9 +50,9 @@ FROM
(SELECT "ID", "Path", "BlocksetID", "MetadataID",
CASE SUBSTR("Path", LENGTH("Path")) WHEN '/' THEN
rtrim(SUBSTR("Path", 1, LENGTH("Path")-1), replace(replace(SUBSTR("Path", 1, LENGTH("Path")-1), "\", "/"), '/', ''))
rtrim(SUBSTR("Path", 1, LENGTH("Path")-1), replace(replace(SUBSTR("Path", 1, LENGTH("Path")-1), '\', '/'), '/', ''))
ELSE
rtrim("Path", replace(replace("Path", "\", "/"), '/', ''))
rtrim("Path", replace(replace("Path", '\', '/'), '/', ''))
END AS "ParentFolder"
FROM "File") "A" INNER JOIN "PathPrefix" "B" ON "A"."ParentFolder" = "B"."Prefix";
@@ -621,8 +621,8 @@ namespace Duplicati.Library.Main.Database
cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS " + subqueryFiles, tmpName1), lastFilesetId);
cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS " + subqueryFiles, tmpName2), m_filesetId);
cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""nn_tmpName1"" ON ""{0}"" (""Path"")",tmpName1));
cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""nn_tmpName2"" ON ""{0}"" (""Path"")",tmpName2));
cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""nn_tmpName1"" ON ""{0}"" (""Path"")", tmpName1));
cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""nn_tmpName2"" ON ""{0}"" (""Path"")", tmpName2));
results.AddedFiles = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" != ? AND ""File"".""BlocksetID"" != ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""{0}"")", tmpName1), 0, m_filesetId, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID);
@@ -778,7 +778,7 @@ namespace Duplicati.Library.Main.Database
{
using (var cmd = m_connection.CreateCommand(transaction))
{
using (var rd = cmd.ExecuteReader(@"SELECT DISTINCT ""Fileset"".""ID"", ""Fileset"".""Timestamp"" FROM ""Fileset"", ""RemoteVolume"" WHERE ""RemoteVolume"".""ID"" = ""Fileset"".""VolumeID"" AND ""Fileset"".""ID"" IN (SELECT ""FilesetID"" FROM ""FilesetEntry"") AND (""RemoteVolume"".""State"" = ""Uploading"" OR ""RemoteVolume"".""State"" = ""Temporary"")"))
using (var rd = cmd.ExecuteReader(@$"SELECT DISTINCT ""Fileset"".""ID"", ""Fileset"".""Timestamp"" FROM ""Fileset"", ""RemoteVolume"" WHERE ""RemoteVolume"".""ID"" = ""Fileset"".""VolumeID"" AND ""Fileset"".""ID"" IN (SELECT ""FilesetID"" FROM ""FilesetEntry"") AND (""RemoteVolume"".""State"" = '{RemoteVolumeState.Uploading}' OR ""RemoteVolume"".""State"" = '{RemoteVolumeState.Temporary}')"))
while (rd.Read())
{
yield return new KeyValuePair<long, DateTime>(
@@ -975,6 +975,5 @@ namespace Duplicati.Library.Main.Database
tr.Commit();
}
}
}
}
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
using System.Data;
@@ -180,14 +180,14 @@ namespace Duplicati.Library.Main.Database
m_selectremotevolumesCommand.CommandText = @"SELECT ""ID"", ""Name"", ""Type"", ""Size"", ""Hash"", ""State"", ""DeleteGraceTime"" FROM ""Remotevolume""";
m_selectremotevolumeCommand.CommandText = m_selectremotevolumesCommand.CommandText + @" WHERE ""Name"" = ?";
m_selectduplicateRemoteVolumesCommand.CommandText = string.Format(@"SELECT DISTINCT ""Name"", ""State"" FROM ""Remotevolume"" WHERE ""Name"" IN (SELECT ""Name"" FROM ""Remotevolume"" WHERE ""State"" IN (""{0}"", ""{1}"")) AND NOT ""State"" IN (""{0}"", ""{1}"")", RemoteVolumeState.Deleted.ToString(), RemoteVolumeState.Deleting.ToString());
m_selectduplicateRemoteVolumesCommand.CommandText = string.Format(@"SELECT DISTINCT ""Name"", ""State"" FROM ""Remotevolume"" WHERE ""Name"" IN (SELECT ""Name"" FROM ""Remotevolume"" WHERE ""State"" IN ('{0}', '{1}')) AND NOT ""State"" IN ('{0}', '{1}')", RemoteVolumeState.Deleted.ToString(), RemoteVolumeState.Deleting.ToString());
m_selectremotevolumeCommand.AddParameter();
m_removeremotevolumeCommand.CommandText = @"DELETE FROM ""Remotevolume"" WHERE ""Name"" = ? AND (""DeleteGraceTime"" < ? OR ""State"" != ?)";
m_removeremotevolumeCommand.AddParameters(3);
m_removedeletedremotevolumeCommand.CommandText = $@"DELETE FROM ""Remotevolume"" WHERE ""State"" == ""{RemoteVolumeState.Deleted.ToString()}"" AND (""DeleteGraceTime"" < ? OR LENGTH(""DeleteGraceTime"") > 12) "; // >12 is to handle removal of old records that were in ticks
m_removedeletedremotevolumeCommand.CommandText = $@"DELETE FROM ""Remotevolume"" WHERE ""State"" == '{RemoteVolumeState.Deleted.ToString()}' AND (""DeleteGraceTime"" < ? OR LENGTH(""DeleteGraceTime"") > 12) "; // >12 is to handle removal of old records that were in ticks
m_removedeletedremotevolumeCommand.AddParameter();
m_selectremotevolumeIdCommand.CommandText = @"SELECT ""ID"" FROM ""Remotevolume"" WHERE ""Name"" = ?";
@@ -119,7 +119,7 @@ namespace Duplicati.Library.Main.Database
public IEnumerable<Duplicati.Library.Interface.IListResultRemoteLog> GetLogLines(IEnumerable<string> items)
{
var sql = string.Format(
@"SELECT ""TimeStamp"", ""Message"" || "" "" || CASE WHEN ""Exception"" IS NULL THEN """" ELSE ""Exception"" END FROM ""LogData"" WHERE {0}" +
@"SELECT ""TimeStamp"", ""Message"" || ' ' || CASE WHEN ""Exception"" IS NULL THEN '' ELSE ""Exception"" END FROM ""LogData"" WHERE {0}" +
@" UNION " +
@"SELECT ""Timestamp"", ""Data"" FROM ""RemoteOperation"" WHERE ""Path"" IN ({1})",
string.Join(" OR ", items.Select(x => @"""Message"" LIKE ?")),
@@ -255,12 +255,12 @@ namespace Duplicati.Library.Main.Database
// If we had instr support this would work:
/*var distinctPaths = @"SELECT DISTINCT :1 || " +
@"CASE(INSTR(SUBSTR(""Path"", :2), ""/"")) " +
@"CASE(INSTR(SUBSTR(""Path"", :2), '/')) " +
@"WHEN 0 THEN SUBSTR(""Path"", :2) " +
@"ELSE SUBSTR(""Path"", :2, INSTR(SUBSTR(path, :2), ""/"")) " +
@"ELSE SUBSTR(""Path"", :2, INSTR(SUBSTR(path, :2), '/')) " +
@"END AS ""Path"", ""FilesetID"" " +
@" FROM (" + cartesianPathFileset + @")";*/
// Instead we manually iterate the paths
cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)", tbname));
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
@@ -416,14 +416,14 @@ namespace Duplicati.Library.Main.Database
if (Platform.IsClientPosix && dirsep == "\\")
{
// For Win -> Linux, we remove the colon from the drive letter, and use the drive letter as root folder
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == "":"" THEN ""\\"" || SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END", m_tempfiletable));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == ""\\"" THEN ""\\"" || SUBSTR(""Path"", 2) ELSE ""Path"" END", m_tempfiletable));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == ':' THEN '\\' || SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END", m_tempfiletable));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN '\\' || SUBSTR(""Path"", 2) ELSE ""Path"" END", m_tempfiletable));
}
else if (Platform.IsClientWindows && dirsep == "/")
{
// For Linux -> Win, we use the temporary folder's drive as the root path
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 1) == ""/"" THEN ? || SUBSTR(""Path"", 2) ELSE ""Path"" END", m_tempfiletable), Util.AppendDirSeparator(System.IO.Path.GetPathRoot(Library.Utility.TempFolder.SystemTempPath)).Replace("\\", "/"));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 1) == '/' THEN ? || SUBSTR(""Path"", 2) ELSE ""Path"" END", m_tempfiletable), Util.AppendDirSeparator(System.IO.Path.GetPathRoot(Library.Utility.TempFolder.SystemTempPath)).Replace("\\", "/"));
}
else
{
@@ -438,10 +438,10 @@ namespace Duplicati.Library.Main.Database
//Special case, restoring to new folder, but files are from different drives (no shared root on Windows)
// We use the format <restore path> / <drive letter> / <source path>
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == "":"" THEN SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END", m_tempfiletable));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == ':' THEN SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END", m_tempfiletable));
// For UNC paths, we use \\server\folder -> <restore path> / <servername> / <source path>
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == ""\\"" THEN SUBSTR(""Path"", 2) ELSE ""TargetPath"" END", m_tempfiletable));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN SUBSTR(""Path"", 2) ELSE ""TargetPath"" END", m_tempfiletable));
}
else
{
@@ -453,10 +453,10 @@ namespace Duplicati.Library.Main.Database
// Cross-os path remapping support
if (Platform.IsClientPosix && dirsep == "\\")
// For Win paths on Linux
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(""TargetPath"", ""\"", ""/"")", m_tempfiletable));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(""TargetPath"", '\', '/')", m_tempfiletable));
else if (Platform.IsClientWindows && dirsep == "/")
// For Linux paths on Windows
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(REPLACE(""TargetPath"", ""\"", ""_""), ""/"", ""\"")", m_tempfiletable));
cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(REPLACE(""TargetPath"", '\', '_'), '/', '\')", m_tempfiletable));
if (!string.IsNullOrEmpty(destination))
{
@@ -121,7 +121,7 @@ namespace Duplicati.Library.Main.Database
using(var cmd = m_connection.CreateCommand())
{
// Select any broken items
using(var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE (""State"" = ? OR ""State"" = ?) AND (""Hash"" = """" OR ""Hash"" IS NULL OR ""Size"" <= 0) ", RemoteVolumeState.Verified.ToString(), RemoteVolumeState.Uploaded.ToString()))
using (var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE (""State"" = ? OR ""State"" = ?) AND (""Hash"" = '' OR ""Hash"" IS NULL OR ""Size"" <= 0) ", RemoteVolumeState.Verified.ToString(), RemoteVolumeState.Uploaded.ToString()))
while (rd.Read())
yield return new RemoteVolume(rd);
@@ -39,8 +39,8 @@ namespace Duplicati.Library.Main.Operation
yield return string.Format("Duplicati: {0} ({1})", Duplicati.Library.Utility.Utility.getEntryAssembly().FullName, System.Reflection.Assembly.GetExecutingAssembly().FullName);
yield return string.Format("Autoupdate urls: {0}", string.Join(";", Duplicati.Library.AutoUpdater.AutoUpdateSettings.URLs));
yield return string.Format("Update folder: {0}", Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLDIR);
yield return string.Format("Base install folder: {0}", Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir);
yield return string.Format("Update folder: {0}", Duplicati.Library.AutoUpdater.UpdaterManager.UPDATEDIR);
yield return string.Format("Install folder: {0}", Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR);
yield return string.Format("Version name: \"{0}\" ({1})", Duplicati.Library.AutoUpdater.UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
yield return string.Format("Current Version folder {0}", System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
@@ -51,7 +51,6 @@ namespace Duplicati.Library.Main.Operation
yield return string.Format("Machinename: {0}", Environment.MachineName);
yield return string.Format("Processors: {0}", Environment.ProcessorCount);
yield return string.Format(".Net Version: {0}", Environment.Version);
yield return string.Format("Mono: {0} ({1}) ({2})", Duplicati.Library.Utility.Utility.IsMono, Duplicati.Library.Utility.Utility.MonoVersion, Duplicati.Library.Utility.Utility.MonoDisplayVersion);
yield return string.Format("Locale: {0}, {1}, {2}", System.Threading.Thread.CurrentThread.CurrentCulture, System.Threading.Thread.CurrentThread.CurrentUICulture, System.Globalization.CultureInfo.InstalledUICulture);
yield return string.Format("Date/time strings: {0} - {1}", System.Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongDatePattern, System.Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.LongTimePattern);
yield return string.Format("Tempdir: {0}", Library.Utility.TempFolder.SystemTempPath);
+21 -21
View File
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
using System.Linq;
@@ -191,7 +191,7 @@ namespace Duplicati.Library.Main
}
private static readonly string DEFAULT_COMPRESSED_EXTENSION_FILE = System.IO.Path.Combine(Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir, "default_compressed_extensions.txt");
private static readonly string DEFAULT_COMPRESSED_EXTENSION_FILE = System.IO.Path.Combine(Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR, "default_compressed_extensions.txt");
/// <summary>
/// Lock that protects the options collection
@@ -36,4 +36,4 @@
</PackageReference>
</ItemGroup>
</Project>
</Project>
@@ -40,8 +40,8 @@ namespace Duplicati.Library.Modules.Builtin
/// </summary>
private static readonly string LOGTAG = Logging.Log.LogTagFromType<ReportHelper>();
/// <summary>
/// The salt used for calculating a backup Id from the remote URL
/// <summary>
/// The salt used for calculating a backup Id from the remote URL
/// </summary>
private const string SALT = "DUPL";
@@ -297,8 +297,9 @@ namespace Duplicati.Library.Modules.Builtin
/// <returns>The expanded template.</returns>
/// <param name="input">The input template.</param>
/// <param name="result">The result object.</param>
/// <param name="exception">An optional exception that has stopped the backup</param>
/// <param name="subjectline">If set to <c>true</c>, the result is intended for a subject or title line.</param>
protected virtual string ReplaceTemplate(string input, object result, bool subjectline)
protected virtual string ReplaceTemplate(string input, object result, Exception exception, bool subjectline)
{
// For JSON, ignore the template and just use the contents
if (ExportFormat == ResultExportFormat.Json && !subjectline)
@@ -312,21 +313,21 @@ namespace Duplicati.Library.Modules.Builtin
if (input.IndexOf("%LOCALPATH%", StringComparison.OrdinalIgnoreCase) >= 0 && m_localpath != null)
extra["LocalPath"] = string.Join(System.IO.Path.PathSeparator.ToString(), m_localpath);
if (input.IndexOf("%PARSEDRESULT%", StringComparison.OrdinalIgnoreCase) >= 0)
extra["ParsedResult"] = m_parsedresultlevel;
// If the options contains the key, it is captured by the loop over m_options
// so we only patch it in case it is missing
if (input.IndexOf("%machine-id%", StringComparison.OrdinalIgnoreCase) >= 0)
{
if (!m_options.ContainsKey("machine-id"))
extra["machine-id"] = "";
extra["ParsedResult"] = m_parsedresultlevel;
// If the options contains the key, it is captured by the loop over m_options
// so we only patch it in case it is missing
if (input.IndexOf("%machine-id%", StringComparison.OrdinalIgnoreCase) >= 0)
{
if (!m_options.ContainsKey("machine-id"))
extra["machine-id"] = Library.AutoUpdater.UpdaterManager.InstallID;
}
if (input.IndexOf("%backup-id%", StringComparison.OrdinalIgnoreCase) >= 0)
{
if (!m_options.ContainsKey("backup-id"))
extra["backup-id"] = Library.Utility.Utility.ByteArrayAsHexString(Library.Utility.Utility.RepeatedHashWithSalt(m_remoteurl, SALT));
if (input.IndexOf("%backup-id%", StringComparison.OrdinalIgnoreCase) >= 0)
{
if (!m_options.ContainsKey("backup-id"))
extra["backup-id"] = Library.Utility.Utility.ByteArrayAsHexString(Library.Utility.Utility.RepeatedHashWithSalt(m_remoteurl, SALT));
}
if (input.IndexOf("%backup-name%", StringComparison.OrdinalIgnoreCase) >= 0)
@@ -339,7 +340,7 @@ namespace Duplicati.Library.Modules.Builtin
if (input.IndexOf($"%{kv.Key}%", StringComparison.OrdinalIgnoreCase) >= 0)
extra[kv.Key] = kv.Value;
return m_resultFormatSerializer.Serialize(result, LogLines, extra);
return m_resultFormatSerializer.Serialize(result, exception, LogLines, extra);
}
else
{
@@ -355,20 +356,20 @@ namespace Duplicati.Library.Modules.Builtin
else
{
if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0)
input = Regex.Replace(input, "\\%RESULT\\%", m_resultFormatSerializer.Serialize(result, LogLines, null), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
input = Regex.Replace(input, "\\%RESULT\\%", m_resultFormatSerializer.Serialize(result, exception, LogLines, null), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
foreach (KeyValuePair<string, string> kv in m_options)
input = Regex.Replace(input, "\\%" + kv.Key + "\\%", kv.Value ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!m_options.ContainsKey("backup-name"))
input = Regex.Replace(input, "\\%backup-name\\%", System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.getEntryAssembly().Location) ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!m_options.ContainsKey("backup-id"))
input = Regex.Replace(input, "\\%backup-id\\%", Library.Utility.Utility.ByteArrayAsHexString(Library.Utility.Utility.RepeatedHashWithSalt(m_remoteurl, SALT)), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
input = Regex.Replace(input, "\\%backup-name\\%", System.IO.Path.GetFileNameWithoutExtension(Duplicati.Library.Utility.Utility.getEntryAssembly().Location) ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!m_options.ContainsKey("backup-id"))
input = Regex.Replace(input, "\\%backup-id\\%", Library.Utility.Utility.ByteArrayAsHexString(Library.Utility.Utility.RepeatedHashWithSalt(m_remoteurl, SALT)), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!m_options.ContainsKey("machine-id"))
input = Regex.Replace(input, "\\%machine-id\\%", "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
input = Regex.Replace(input, "\\%machine-id\\%", Library.AutoUpdater.UpdaterManager.InstallID, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
input = Regex.Replace(input, "\\%[^\\%]+\\%", "");
return input;
@@ -394,7 +395,7 @@ namespace Duplicati.Library.Modules.Builtin
}
}
public void OnFinish(object result)
public void OnFinish(object result, Exception exception)
{
// Dispose the current log scope
if (m_logscope != null)
@@ -412,7 +413,7 @@ namespace Duplicati.Library.Modules.Builtin
return;
ParsedResultType level;
if (result is Exception)
if (result is Exception || exception != null)
level = ParsedResultType.Fatal;
else if (result != null && result is IBasicResults results)
level = results.ParsedResult;
@@ -438,8 +439,8 @@ namespace Duplicati.Library.Modules.Builtin
if (body != DEFAULT_BODY && System.IO.Path.IsPathRooted(body) && System.IO.File.Exists(body))
body = System.IO.File.ReadAllText(body);
body = ReplaceTemplate(body, result, false);
subject = ReplaceTemplate(subject, result, true);
body = ReplaceTemplate(body, result, exception, false);
subject = ReplaceTemplate(subject, result, exception, true);
SendMessage(subject, body);
}
@@ -37,12 +37,19 @@ namespace Duplicati.Library.Modules.Builtin.ResultSerialization
/// </summary>
/// <returns>The serialized result string.</returns>
/// <param name="result">The result to serialize.</param>
/// <param name="failException">The exception, if any</param>
/// <param name="loglines">The log lines to serialize.</param>
/// <param name="additional">Additional parameters to include</param>
public string Serialize(object result, IEnumerable<string> loglines, Dictionary<string, string> additional)
public string Serialize(object result, Exception failException, IEnumerable<string> loglines, Dictionary<string, string> additional)
{
StringBuilder sb = new StringBuilder();
// Prepend the error message as the first two lines, to mimic previous behavior with only the exception text
if (failException != null && result != failException)
{
sb.AppendLine(Serialize(failException, null, null, null));
}
if (result == null)
{
sb.Append("null?");
@@ -105,7 +112,7 @@ namespace Duplicati.Library.Modules.Builtin.ResultSerialization
}
else
{
var ignore = new string[] {
var ignore = new string[] {
nameof(IBasicResults.Warnings),
nameof(IBasicResults.Errors),
nameof(IBasicResults.Messages)
@@ -115,7 +122,7 @@ namespace Duplicati.Library.Modules.Builtin.ResultSerialization
}
if (additional != null && additional.Count > 0)
sb.AppendLine(Serialize(additional, null, null));
sb.AppendLine(Serialize(additional, null, null, null));
if (loglines != null && loglines.Any())
{
@@ -18,6 +18,7 @@
// 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.
using System;
using System.Collections.Generic;
namespace Duplicati.Library.Modules.Builtin
@@ -32,9 +33,10 @@ namespace Duplicati.Library.Modules.Builtin
/// </summary>
/// <returns>The serialized result string.</returns>
/// <param name="result">The result to serialize.</param>
/// <param name="exception">An optional failure exception, or null</param>
/// <param name="loglines">The log lines to serialize.</param>
/// <param name="additional">Additional parameters to include</param>
string Serialize(object result, IEnumerable<string> loglines, Dictionary<string, string> additional);
string Serialize(object result, Exception exception, IEnumerable<string> loglines, Dictionary<string, string> additional);
/// <summary>
/// Returns the format that the serializer represents
@@ -76,16 +76,18 @@ namespace Duplicati.Library.Modules.Builtin.ResultSerialization
/// </summary>
/// <returns>The serialized result string.</returns>
/// <param name="result">The result to serialize.</param>
/// <param name="exception">The exception, if any</param>
/// <param name="loglines">The log lines to serialize.</param>
/// <param name="additional">Additional parameters to include</param>
public string Serialize(object result, IEnumerable<string> loglines, Dictionary<string, string> additional)
public string Serialize(object result, Exception exception, IEnumerable<string> loglines, Dictionary<string, string> additional)
{
return JsonConvert.SerializeObject(
new
{
Data = result,
Extra = additional,
LogLines = loglines
LogLines = loglines,
Exception = exception?.ToString()
},
new JsonSerializerSettings()
@@ -170,7 +170,7 @@ namespace Duplicati.Library.Modules.Builtin
m_localpath = localpath;
}
public void OnFinish (object result)
public void OnFinish (object result, Exception exception)
{
// Dispose the current log scope
if (m_logscope != null)
@@ -184,7 +184,8 @@ namespace Duplicati.Library.Modules.Builtin
return;
ParsedResultType level;
if (result is OperationAbortException oae)
OperationAbortException oae = result as OperationAbortException ?? exception as OperationAbortException;
if (oae != null)
{
switch (oae.AbortReason)
{
@@ -202,7 +203,7 @@ namespace Duplicati.Library.Modules.Builtin
break;
}
}
else if (result is Exception)
else if (result is Exception || exception != null)
level = ParsedResultType.Fatal;
else if (result != null && result is IBasicResults results)
level = results.ParsedResult;
@@ -212,7 +213,7 @@ namespace Duplicati.Library.Modules.Builtin
using (TempFile tmpfile = new TempFile())
{
using (var streamWriter = new StreamWriter(tmpfile))
streamWriter.Write(resultFormatSerializer.Serialize(result, m_logstorage, null));
streamWriter.Write(resultFormatSerializer.Serialize(result, exception, m_logstorage, null));
Execute(m_finishScript, "AFTER", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, tmpfile, level);
}
@@ -206,13 +206,13 @@ namespace Duplicati.Library.Modules.Builtin {
#endregion
protected override string ReplaceTemplate(string input, object result, bool subjectline)
protected override string ReplaceTemplate(string input, object result, Exception exception, bool subjectline)
{
// No need to do the expansion as we throw away the result
if (subjectline)
return string.Empty;
return base.ReplaceTemplate(input, result, subjectline);
return base.ReplaceTemplate(input, result, exception, subjectline);
}
protected override void SendMessage(string subject, string body) {
@@ -185,12 +185,12 @@ namespace Duplicati.Library.Modules.Builtin
#endregion
protected override string ReplaceTemplate(string input, object result, bool subjectline)
protected override string ReplaceTemplate(string input, object result, Exception exception, bool subjectline)
{
// No need to do the expansion as we throw away the result
if (subjectline)
return string.Empty;
return base.ReplaceTemplate(input, result, subjectline);
return base.ReplaceTemplate(input, result, exception, subjectline);
}
protected override async void SendMessage(string subject, string body)
@@ -20,9 +20,20 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.115" />
<PackageReference Include="System.Data.SQLite.Core.MSIL" Version="1.0.115" />
<PackageReference Include="System.Data.SQLite.Core.osx.arm64" Version="1.0.117" />
<!--
MS Data.Sqlite seems much more portable, but does not support unnamed parameters,
which are used extensively in Duplicati.
Issue tracking progress on the unnamed parameters:
https://github.com/dotnet/efcore/issues/24480
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.3" />
-->
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.115.0" />
<PackageReference Include="System.Data.SQLite.Core.MSIL" Version="1.0.115.0" />
<PackageReference Include="System.Data.SQLite.Core.Duplicati.macos.arm64" Version="1.0.116.1" />
<PackageReference Include="System.Data.SQLite.Core.Duplicati.linux.arm64" Version="1.0.116" />
<PackageReference Include="System.Data.SQLite.Core.Duplicati.linux.armv7" Version="1.0.116" />
</ItemGroup>
</Project>
@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
+21 -21
View File
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
@@ -218,7 +218,7 @@ namespace Duplicati.Library.Snapshots
/// <returns>A string with the combined output of the stdout and stderr</returns>
private static string ExecuteCommand(string program, string commandline, int expectedExitCode)
{
program = System.IO.Path.Combine(System.IO.Path.Combine(Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir, "lvm-scripts"), program);
program = System.IO.Path.Combine(System.IO.Path.Combine(Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR, "lvm-scripts"), program);
var inf = new ProcessStartInfo(program, commandline)
{
CreateNoWindow = true,
+5 -14
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2024, The Duplicati Team
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
@@ -21,9 +21,7 @@
using System;
using Newtonsoft.Json;
using System.Linq;
using System.Collections.Generic;
using Duplicati.Library.Common;
namespace Duplicati.Library.UsageReporter
{
@@ -50,15 +48,11 @@ namespace Duplicati.Library.UsageReporter
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private static void DoInitOS()
{
if (Platform.IsClientOSX)
if (OperatingSystem.IsMacOS())
Cached_OSType = "OSX";
else if (Platform.IsClientPosix)
else if (OperatingSystem.IsLinux())
Cached_OSType = "Linux";
else if (new PlatformID[] {
PlatformID.Win32NT,
PlatformID.Win32S,
PlatformID.Win32Windows
}.Contains(Environment.OSVersion.Platform))
else if (OperatingSystem.IsWindows())
Cached_OSType = "Windows";
else
Cached_OSType = Environment.OSVersion.Platform.ToString();
@@ -69,10 +63,7 @@ namespace Duplicati.Library.UsageReporter
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private static void DoInitCLR()
{
if (Utility.Utility.IsMono)
Cached_CLRVersion = string.Format("Mono {0} ({1}), CLR: {2}", Utility.Utility.MonoDisplayVersion, Utility.Utility.MonoVersion, Environment.Version);
else
Cached_CLRVersion = string.Format(".Net {0}", Environment.Version);
Cached_CLRVersion = string.Format(".Net {0}", Environment.Version);
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
@@ -19,6 +19,8 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace Duplicati.Library.Utility
{
@@ -39,15 +41,50 @@ namespace Duplicati.Library.Utility
{
}
public SignatureReadingStream(System.IO.Stream stream, System.Security.Cryptography.RSACryptoServiceProvider key)
/// <summary>
/// Creates a new stream that reads from the given stream and verifies the signature using any of the given keys
/// </summary>
/// <param name="stream">The stream with a signature</param>
/// <param name="keys">The allowed keys</param>
public SignatureReadingStream(System.IO.Stream stream, IEnumerable<System.Security.Cryptography.RSA> keys)
{
if (!VerifySignature(stream, key))
if (!VerifySignature(stream, keys))
throw new System.IO.InvalidDataException("Unable to verify signature");
m_stream = stream;
this.Position = 0;
}
private static bool VerifySignature(System.IO.Stream stream, System.Security.Cryptography.RSACryptoServiceProvider key)
/// <summary>
/// Wraps trying the keys one by one, returning true if any of the keys validate
/// </summary>
/// <param name="stream">The stream to verify</param>
/// <param name="keys">The keys to try</param>
/// <returns><c>true</c> if the stream is valid; <c>false</c> otherwise</returns>
private static bool VerifySignature(System.IO.Stream stream, IEnumerable<System.Security.Cryptography.RSA> keys)
{
if (keys == null)
return false;
foreach (var key in keys)
try
{
if (VerifySignature(stream, key))
return true;
}
catch
{
}
return false;
}
/// <summary>
/// Verifies the signature of the stream using the given key
/// </summary>
/// <param name="stream">The stream to verify</param>
/// <param name="key">The key to validate with</param>
/// <returns><c>true</c> if the stream signature matches the key; <c>false</c> otherwise</returns>
private static bool VerifySignature(System.IO.Stream stream, System.Security.Cryptography.RSA key)
{
stream.Position = 0;
var signature = new byte[SIGNED_HASH_SIZE];
@@ -69,11 +106,16 @@ namespace Duplicati.Library.Utility
sha256.TransformFinalBlock(buf, 0, 0);
var hash = sha256.Hash;
var OID = System.Security.Cryptography.CryptoConfig.MapNameToOID("SHA256");
return key.VerifyHash(hash, OID, signature);
return key.VerifyHash(hash, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public static void CreateSignedStream(System.IO.Stream datastream, System.IO.Stream signedstream, System.Security.Cryptography.RSACryptoServiceProvider key)
/// <summary>
/// Creates a signed stream from the given data stream and writes the signature to the signed stream
/// </summary>
/// <param name="datastream">The stream to sign</param>
/// <param name="signedstream">The stream with the signature</param>
/// <param name="key">The key used to sign it</param>
public static void CreateSignedStream(System.IO.Stream datastream, System.IO.Stream signedstream, System.Security.Cryptography.RSA key)
{
var sha256 = System.Security.Cryptography.SHA256.Create();
@@ -97,9 +139,9 @@ namespace Duplicati.Library.Utility
sha256.TransformFinalBlock(buf, 0, 0);
var hash = sha256.Hash;
var OID = System.Security.Cryptography.CryptoConfig.MapNameToOID("SHA256");
var signature = key.SignHash(hash, OID);
var signature = key.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
if (signature.Length != SIGNED_HASH_SIZE)
throw new System.IO.InvalidDataException("Unexpected signature length");
signedstream.Position = 0;
signedstream.Write(signature, 0, signature.Length);
+1 -59
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2024, The Duplicati Team
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
@@ -880,64 +880,6 @@ namespace Duplicati.Library.Utility
}
}
/// <summary>
/// Returns a value indicating if the app is running under Mono
/// </summary>
public static bool IsMono => Type.GetType("Mono.Runtime") != null;
/// <summary>
/// Gets the current Mono runtime version, will return 0.0 if not running Mono
/// </summary>
public static Version MonoVersion
{
get
{
try
{
var v = MonoDisplayVersion;
if (v != null)
{
var regex = new Regex(@"\d+\.\d+(\.\d+)?(\.\d+)?");
var match = regex.Match(v);
if (match.Success)
return new Version(match.Value);
}
}
catch
{
// ignored
}
return new Version();
}
}
/// <summary>
/// Gets the Mono display version, or null if not running Mono
/// </summary>
public static string MonoDisplayVersion
{
get
{
try
{
var t = Type.GetType("Mono.Runtime");
if (t != null)
{
var mi = t.GetMethod("GetDisplayName", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
if (mi != null)
return (string)mi.Invoke(null, null);
}
}
catch
{
// ignored
}
return null;
}
}
/// <summary>
/// Gets a string comparer that matches the client filesystems case sensitivity
/// </summary>
@@ -1,27 +1,25 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Duplicati.Server.Serialization.Implementations
{
@@ -39,8 +37,8 @@ namespace Duplicati.Server.Serialization.Implementations
public long LastNotificationUpdateID { get; set; }
public string UpdatedVersion { get; set; }
public string UpdateDownloadLink { get; set; }
public UpdatePollerStates UpdaterState { get; set; }
public bool UpdateReady { get; set; }
public double UpdateDownloadProgress { get; set; }
}
}
@@ -1,23 +1,23 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
@@ -27,7 +27,7 @@ namespace Duplicati.Server.Serialization.Interface
{
Tuple<long, string> ActiveTask { get; }
LiveControlState ProgramState { get; }
System.Collections.Generic.IList<Tuple<long,string>> SchedulerQueueIds { get; }
IList<Tuple<long,string>> SchedulerQueueIds { get; }
bool HasWarning { get; }
bool HasError { get; }
SuggestedStatusIcon SuggestedStatusIcon { get; }
@@ -37,8 +37,8 @@ namespace Duplicati.Server.Serialization.Interface
long LastNotificationUpdateID { get; }
string UpdatedVersion { get; }
string UpdateDownloadLink { get; }
UpdatePollerStates UpdaterState { get; }
bool UpdateReady { get; }
double UpdateDownloadProgress { get; }
}
+8 -10
View File
@@ -48,7 +48,7 @@ namespace Duplicati.Server
/// <summary>
/// The path to the directory that contains the main executable
/// </summary>
public static readonly string StartupPath = Duplicati.Library.AutoUpdater.UpdaterManager.InstalledBaseDir;
public static readonly string StartupPath = Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR;
/// <summary>
/// The name of the environment variable that holds the path to the data folder used by Duplicati
@@ -201,7 +201,7 @@ namespace Duplicati.Server
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
public static int Main(string[] _args)
{
// var methodInfo = typeof(TemporaryIoCAccessor).Assembly.EntryPoint;
// var program = Activator.CreateInstance(methodInfo!.DeclaringType!);
@@ -229,7 +229,7 @@ namespace Duplicati.Server
}
//If this executable is invoked directly, write to console, otherwise throw exceptions
var writeToConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly();
var writeToConsole = System.Reflection.Assembly.GetEntryAssembly().GetName().FullName.StartsWith("Duplicati.Server,", StringComparison.OrdinalIgnoreCase);
//Find commandline options here for handling special startup cases
var args = new List<string>(_args);
@@ -327,7 +327,8 @@ namespace Duplicati.Server
{
StatusEventNotifyer.SignalNewEvent();
ShutdownModernWebserver();
if (ShutdownModernWebserver != null)
ShutdownModernWebserver();
UpdatePoller?.Terminate();
Scheduler?.Terminate(true);
FIXMEGlobal.WorkThread?.Terminate(true);
@@ -342,9 +343,6 @@ namespace Duplicati.Server
LogHandler?.Dispose();
}
if (UpdatePoller != null && UpdatePoller.IsUpdateRequested)
return Library.AutoUpdater.UpdaterManager.MAGIC_EXIT_CODE;
return 0;
}
@@ -364,7 +362,7 @@ namespace Duplicati.Server
FIXMEGlobal.WorkThread.StartingWork += (worker, task) => { SignalNewEvent(null, null); };
FIXMEGlobal.WorkThread.CompletedWork += (worker, task) => { SignalNewEvent(null, null); };
FIXMEGlobal.WorkThread.WorkQueueChanged += (worker) => { SignalNewEvent(null, null); };
FIXMEGlobal.Scheduler.NewSchedule += new EventHandler(SignalNewEvent);
FIXMEGlobal.Scheduler.NewSchedule += new EventHandler(SignalNewEvent);
FIXMEGlobal.WorkThread.OnError += (worker, task, exception) =>
{
Program.DataConnection.LogError(task?.BackupID, "Error in worker", exception);
@@ -397,8 +395,8 @@ namespace Duplicati.Server
}
}
FIXMEGlobal.WorkThread.CompletedWork += (worker, task) => { RegisterTaskResult(task.TaskID, null); };
FIXMEGlobal.WorkThread.OnError += (worker, task, exception) => { RegisterTaskResult(task.TaskID, exception); };
FIXMEGlobal.WorkThread.CompletedWork += (worker, task) => { RegisterTaskResult(task.TaskID, null); };
FIXMEGlobal.WorkThread.OnError += (worker, task, exception) => { RegisterTaskResult(task.TaskID, exception); };
}
private static void SetLiveControls()
@@ -46,14 +46,6 @@ backupApp.controller('AboutController', function($scope, $location, BrandingServ
$location.path('/updatechangelog');
};
$scope.doStartUpdateDownload = function() {
AppService.post('/updates/install');
};
$scope.doStartUpdateActivate = function() {
AppService.post('/updates/activate').then(function() {}, AppUtils.connectionError(gettextCatalog.getString('Activate failed:') + ' '));
};
$scope.doCheckForUpdates = function() {
AppService.post('/updates/check');
@@ -10,14 +10,6 @@ backupApp.controller('UpdateChangelogController', function($scope, BrandingServi
});
};
$scope.doInstall = function() {
AppService.post('/updates/install').then(function() {}, AppUtils.connectionError(gettextCatalog.getString('Install failed:') + ' '));
};
$scope.doActivate = function() {
AppService.post('/updates/activate').then(function() {}, AppUtils.connectionError(gettextCatalog.getString('Activate failed:') + ' '));
};
$scope.doCheck = function() {
AppService.post('/updates/check').then(function() {
reloadChangeLog();
@@ -54,14 +54,6 @@ backupApp.directive('notificationArea', function() {
$location.path('/');
};
$scope.doInstallUpdate = function(id) {
AppService.post('/updates/install');
};
$scope.doActivateUpdate = function(id) {
AppService.post('/updates/activate').then(function() { $scope.doDismiss(id); }, AppUtils.connectionError('Activate failed: '));
};
$scope.doShowUpdate = function(id) {
$location.path('/updatechangelog');
};
@@ -18,8 +18,8 @@ backupApp.service('ServerStatus', function ($rootScope, $timeout, AppService, Ap
failedConnectionAttempts: 0,
lastPgEvent: null,
updaterState: 'Waiting',
updateDownloadLink: null,
updatedVersion: null,
updateReady: false,
updateDownloadProgress: 0,
proposedSchedule: [],
schedulerQueueIds: []
@@ -205,7 +205,7 @@ backupApp.service('ServerStatus', function ($rootScope, $timeout, AppService, Ap
notifyIfChanged(response.data, 'programState', 'programState') |
notifyIfChanged(response.data, 'estimatedPauseEnd', 'estimatedPauseEnd') |
notifyIfChanged(response.data, 'updaterState', 'updaterState') |
notifyIfChanged(response.data, 'updateReady', 'updateReady') |
notifyIfChanged(response.data, 'updateDownloadLink', 'updateDownloadLink') |
notifyIfChanged(response.data, 'updatedVersion', 'updatedVersion') |
notifyIfChanged(response.data, 'updateDownloadProgress', 'updateDownloadProgress');
@@ -26,11 +26,8 @@
<div>&nbsp;</div>
<div translate translate-params-appname="brandingService.appName" translate-params-version="sysinfo.ServerVersionName || 'unknown'">You are currently running {{appname}} {{version}}</div>
<div ng-show="state.updatedVersion != null &amp;&amp; !state.updateReady &amp;&amp; state.updaterState == 'Waiting'">
Update <a href ng-click="doShowUpdateChangelog()">{{state.updatedVersion}}</a> is available, <a href ng-click="doStartUpdateDownload()">download now</a>
</div>
<div ng-show="state.updatedVersion != null &amp;&amp; state.updateReady &amp;&amp; state.updaterState == 'Waiting'">
Update <a href ng-click="doShowUpdateChangelog()">{{state.updatedVersion}}</a> is installed, <a href ng-click="doStartUpdateActivate()">activate now</a>
<div ng-show="state.updatedVersion != null &amp;&amp; state.updateDownloadLink != null &amp;&amp; !state.updateReady &amp;&amp; state.updaterState == 'Waiting'">
Update <a href ng-click="doShowUpdateChangelog()">{{state.updatedVersion}}</a> is available, <a href="{{state.updateDownloadLink}}">download now</a>
</div>
<a ng-show="state.updaterState == 'Waiting'" href ng-click="doCheckForUpdates()" translate>Check for updates now</a>
@@ -2,11 +2,11 @@
<li ng-repeat="item in Notifications track by item.ID" ng-show="$index < 4" class="{{item.Type.toLowerCase()}}">
<div class="title">{{item.Title}}</div>
<div class="content">
<div ng-if="item.Action != 'update:new'" class="message">{{item.Message}}</div>
<div ng-if="item.Action != 'update:new' &amp;&amp; item.Action != 'update:manual'" class="message">{{item.Message}}</div>
<div ng-show="item.DownloadLink != null">
<div translate>If the backup file was not downloaded automatically, <a href="{{item.DownloadLink}}" target="_blank">right click and choose &quot;Save as …&quot;</a></div>
</div>
<div ng-if="item.Action != 'update:new'" class="buttons">
<div ng-if="item.Action != 'update:new' &amp;&amp; item.Action != 'update:manual'" class="buttons">
<a href ng-click="doDismiss(item.ID)" class="button dismiss" translate>Dismiss</a>
<a href ng-show="item.Action == 'backup:show-log'" ng-click="doShowLog(item.BackupID)" class="button showlog" translate>Show</a>
@@ -17,22 +17,28 @@
<div class="clear"></div>
</div>
<div ng-if="item.Action == 'update:new'">
<div class="message" translate translate-params-message="item.Message">New update found: {{message}}</div>
<div class="message" translate translate-params-message="item.Message">New update found: <a href="{{state.updateDownloadLink}}">{{message}}</a></div>
<progress-bar ng-show="state.updaterState == 'Downloading'" ng-text="'Downloading update…' | translate" ng-progress="state.updateDownloadProgress"></progress-bar>
<div class="buttons">
<a href ng-click="doDismiss(item.ID)" class="button dismiss" translate>Dismiss</a>
<a href ng-show="!state.updateReady &amp;&amp; state.updaterState == 'Waiting'" ng-click="doInstallUpdate(item.ID)" class="button installupdate" translate>Install</a>
<a href ng-show="state.updateReady &amp;&amp; state.updaterState == 'Waiting'" ng-click="doActivateUpdate(item.ID)" class="button installupdate" translate>Activate</a>
<a href ng-show="state.updaterState == 'Downloading'" class="button" ng-disabled="true" ng-click="" translate>Downloading …</a>
<a href ng-show="state.updaterState == 'Checking'" class="button" ng-disabled="true" ng-click="" translate>Verifying …</a>
<a href="{{state.updateDownloadLink}}" class="button installupdate" translate>Download</a>
<a href ng-click="doShowUpdate()" class="button showupdate" translate>Show</a>
<div class="clear"></div>
</div>
</div>
<div ng-if="item.Action == 'update:manual'">
<div class="message"><span translate>Manual update found:</span> <a href="{{item.Message}}" target="_blank" rel="extenal">{{item.Message}}</a></div>
<div class="buttons">
<a href ng-click="doDismiss(item.ID)" class="button dismiss" translate>Dismiss</a>
<a href="{{item.Message}}" target="_blank" rel="extenal" class="button showupdate" translate>Show</a>
<div class="clear"></div>
</div>
</div>
</div>
</li>
<li class="error" ng-if="Notifications.length > 1">
@@ -7,8 +7,7 @@
<div class="prewrapped-text fixed-width-font" ng-hide="ChangeLog" translate>Loading …</div>
<div class="buttons">
<a href class="button" ng-show="serverstate.UpdaterState == 'Waiting' &amp;&amp; serverstate.UpdatedVersion != null" ng-click="doInstall()" translate>Install</a>
<a href class="button" ng-show="serverstate.UpdateReady" ng-click="doActivate()" translate>Activate</a>
<a href="{{state.updateDownloadLink}}" class="button" ng-show="serverstate.UpdaterState == 'Waiting' &amp;&amp; serverstate.UpdatedVersion != null &amp;&amp; state.updateDownloadLink != null" translate>Download</a>
</div>
+2 -6
View File
@@ -1,4 +1,4 @@
// Copyright (C) 2024, The Duplicati Team
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
@@ -27,15 +27,11 @@ namespace Duplicati.Service
{
[STAThread]
public static int Main(string[] args)
{
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, Duplicati.Library.AutoUpdater.AutoUpdateStrategy.Never);
}
public static void RealMain(string[] args)
{
using(var runner = new Runner(args))
runner.Wait();
return 0;
}
}
}
+2 -2
View File
@@ -66,7 +66,7 @@ namespace Duplicati.UnitTest
{
// Absolute path
var downloadFileName = Path.Combine(absoluteDownloadFolder, Path.GetFileName(targetFile));
var status = CommandLine.BackendTool.Program.RealMain(new[] { "GET", $"{backendURL}", $"{downloadFileName}" });
var status = CommandLine.BackendTool.Program.Main(new[] { "GET", $"{backendURL}", $"{downloadFileName}" });
Assert.AreEqual(0, status);
Assert.IsTrue(File.Exists(downloadFileName));
TestUtils.AssertFilesAreEqual(targetFile, downloadFileName, false, downloadFileName);
@@ -83,7 +83,7 @@ namespace Duplicati.UnitTest
{
// Relative path
var downloadFileName = Path.GetFileName(targetFile);
var status = CommandLine.BackendTool.Program.RealMain(new[] { "GET", $"{backendURL}", $"{downloadFileName}" });
var status = CommandLine.BackendTool.Program.Main(new[] { "GET", $"{backendURL}", $"{downloadFileName}" });
Assert.AreEqual(0, status);
Assert.IsTrue(File.Exists(downloadFileName));
TestUtils.AssertFilesAreEqual(targetFile, downloadFileName, false, downloadFileName);
@@ -1,4 +1,4 @@
// Copyright (C) 2024, The Duplicati Team
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
@@ -24,7 +24,9 @@ using NUnit.Framework;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Net.Http;
using Duplicati.Library.Utility;
namespace Duplicati.UnitTest
{
@@ -44,7 +46,7 @@ namespace Duplicati.UnitTest
private readonly string zipFilename = "data.zip";
private string zipFilepath => Path.Combine(BASEFOLDER, this.zipFilename);
private readonly string zipAlternativeFilename = "data-alternative.zip";
private string zipAlternativeFilepath => Path.Combine(BASEFOLDER, this.zipAlternativeFilename);
@@ -74,56 +76,59 @@ namespace Duplicati.UnitTest
}
}
private void DownloadS3FileIfNewer(string destinationFilePath, string url)
private void DownloadS3FileIfNewer(string destinationFilePath, string url, int retries = 5)
=> DownloadS3FileIfNewerAsync(destinationFilePath, url, retries).Wait();
private async Task DownloadS3FileIfNewerAsync(string destinationFilePath, string url, int retries = 5)
{
var webRequest = (HttpWebRequest)WebRequest.Create(url);
if (systemIO.FileExists(destinationFilePath))
do
{
webRequest.IfModifiedSince = systemIO.FileGetLastWriteTimeUtc(destinationFilePath);
}
try
{
// check if the file should be downloaded, exception if not
using (var wr = (HttpWebResponse)webRequest.GetResponse())
using (WebClient client = new WebClient())
{ // try to workaround a weird intermittent bug where downloaded size
// differs from real size but no error is thrown
Console.WriteLine("downloading test file to: {0}, length: {1}", destinationFilePath, wr.ContentLength);
var maxAttempts = 5;
while (maxAttempts-- > 0) {
DateTime beginTime = DateTime.Now;
client.DownloadFile(url, destinationFilePath);
long length = new System.IO.FileInfo(destinationFilePath).Length;
Console.WriteLine("downloaded test file: {0}: length {1}, duration {2}", destinationFilePath, length, (DateTime.Now - beginTime).TotalSeconds);
if (length == wr.ContentLength) {
maxAttempts = -1;
} else {
Console.WriteLine("invalid downloaded length {0}, should be {1}...", length, wr.ContentLength);
System.Threading.Thread.Sleep(120000);
}
}
if (maxAttempts == 0) {
throw new Exception(string.Format("Unable to download test file from {0}", url));
}
}
}
catch (WebException ex)
{
if (ex.Response == null){
Console.WriteLine("ex.Response is null !");
throw;
}
if (((HttpWebResponse) ex.Response).StatusCode != HttpStatusCode.NotModified)
try
{
Console.WriteLine("file to download {0} already exists and is up to date", destinationFilePath);
throw;
using var httpClient = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, url);
if (systemIO.FileExists(destinationFilePath))
request.Headers.IfModifiedSince = systemIO.FileGetLastWriteTimeUtc(destinationFilePath);
using var response = await httpClient.SendAsync(request);
using var tmpFile = new TempFile();
if (response.StatusCode == System.Net.HttpStatusCode.NotModified)
{
Console.WriteLine("File has not been modified since last download.");
return;
}
else
{
Console.WriteLine($"Downloading file from {url} to: {tmpFile}");
var contentStream = await response.Content.ReadAsStreamAsync();
var fileInfo = new FileInfo(tmpFile);
using (var fileStream = fileInfo.OpenWrite())
await contentStream.CopyToAsync(fileStream);
// After download, check if the file length matches the response length
long responseLength = response.Content.Headers.ContentLength ?? 0;
long fileLength = new FileInfo(tmpFile).Length;
if (responseLength != fileLength)
throw new Exception($"Downloaded file length {fileLength} does not match response length {responseLength}");
Console.WriteLine($"Download completed, moving to {destinationFilePath}");
File.Move(tmpFile, destinationFilePath, true);
return;
}
}
Console.WriteLine("download exception: {0}", ex.Response);
}
catch (Exception ex)
{
retries--;
Console.WriteLine($"Download failed: {ex.Message}");
if (retries <= 0)
throw;
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
Console.WriteLine($"Retrying download, {retries} retries left.");
}
} while (retries > 0);
}
[OneTimeTearDown]
@@ -174,17 +179,17 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Running backup with {0} data added ...", Duplicati.Library.Utility.Utility.FormatSizeString(size));
using (new Library.Logging.Timer(LOGTAG, "BackupWithDataAdded", string.Format("Backup with {0} data added", Duplicati.Library.Utility.Utility.FormatSizeString(size))))
Duplicati.CommandLine.Program.RealMain(backupargs);
Duplicati.CommandLine.Program.Main(backupargs);
ProgressWriteLine("Testing data ...");
using (new Library.Logging.Timer(LOGTAG, "TestRemoteData", "Test remote data"))
if (Duplicati.CommandLine.Program.RealMain((new string[] { "test", target, "all" }.Union(opts)).ToArray()) != 0)
if (Duplicati.CommandLine.Program.Main((new string[] { "test", target, "all" }.Union(opts)).ToArray()) != 0)
throw new Exception("Failed during remote verification");
}
ProgressWriteLine("Running unchanged backup ...");
using (new Library.Logging.Timer(LOGTAG, "UnchangedBackup", "Unchanged backup"))
Duplicati.CommandLine.Program.RealMain(backupargs);
Duplicati.CommandLine.Program.Main(backupargs);
var datafolders = systemIO.EnumerateDirectories(DATAFOLDER);
@@ -195,7 +200,7 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Running backup with renamed folder...");
using (new Library.Logging.Timer(LOGTAG, "BackupWithRenamedFolder", "Backup with renamed folder"))
Duplicati.CommandLine.Program.RealMain(backupargs);
Duplicati.CommandLine.Program.Main(backupargs);
datafolders = systemIO.EnumerateDirectories(DATAFOLDER);
@@ -212,11 +217,11 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Running backup with deleted data...");
using (new Library.Logging.Timer(LOGTAG, "BackupWithDeletedData", "Backup with deleted data"))
Duplicati.CommandLine.Program.RealMain(backupargs);
Duplicati.CommandLine.Program.Main(backupargs);
ProgressWriteLine("Testing the compare method ...");
using (new Library.Logging.Timer(LOGTAG, "CompareMethod", "Compare method"))
Duplicati.CommandLine.Program.RealMain((new string[] { "compare", target, "0", "1" }.Union(opts)).ToArray());
Duplicati.CommandLine.Program.Main((new string[] { "compare", target, "0", "1" }.Union(opts)).ToArray());
for (var i = 0; i < 5; i++)
{
@@ -224,12 +229,12 @@ namespace Duplicati.UnitTest
systemIO.FileCopy(LOGFILE, Path.Combine(SOURCEFOLDER, Path.GetFileName(LOGFILE)), true);
using (new Library.Logging.Timer(LOGTAG, "BackupWithLogfileChange", string.Format("Backup with logfilechange {0}", i + 1)))
Duplicati.CommandLine.Program.RealMain(backupargs);
Duplicati.CommandLine.Program.Main(backupargs);
}
ProgressWriteLine("Compacting data ...");
using (new Library.Logging.Timer(LOGTAG, "Compacting", "Compacting"))
Duplicati.CommandLine.Program.RealMain((new string[] { "compact", target, "--small-file-max-count=2" }.Union(opts)).ToArray());
Duplicati.CommandLine.Program.Main((new string[] { "compact", target, "--small-file-max-count=2" }.Union(opts)).ToArray());
datafolders = systemIO.EnumerateDirectories(DATAFOLDER);
@@ -237,7 +242,7 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Partial restore of {0} ...", Path.GetFileName(rf));
using (new Library.Logging.Timer(LOGTAG, "PartialRestore", "Partial restore"))
Duplicati.CommandLine.Program.RealMain((new string[] { "restore", target, rf + "*", "--restore-path=\"" + RESTOREFOLDER + "\"" }.Union(opts)).ToArray());
Duplicati.CommandLine.Program.Main((new string[] { "restore", target, rf + "*", "--restore-path=\"" + RESTOREFOLDER + "\"" }.Union(opts)).ToArray());
ProgressWriteLine("Verifying partial restore ...");
using (new Library.Logging.Timer(LOGTAG, "VerificationOfPartialRestore", "Verification of partial restored files"))
@@ -247,7 +252,7 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Partial restore of {0} without local db...", Path.GetFileName(rf));
using (new Library.Logging.Timer(LOGTAG, "PartialRestoreWithoutLocalDb", "Partial restore without local db"))
Duplicati.CommandLine.Program.RealMain((new string[] { "restore", target, rf + "*", "--restore-path=\"" + RESTOREFOLDER + "\"", "--no-local-db" }.Union(opts)).ToArray());
Duplicati.CommandLine.Program.Main((new string[] { "restore", target, rf + "*", "--restore-path=\"" + RESTOREFOLDER + "\"", "--no-local-db" }.Union(opts)).ToArray());
ProgressWriteLine("Verifying partial restore ...");
using (new Library.Logging.Timer(LOGTAG, "VerificationOfPartialRestore", "Verification of partial restored files"))
@@ -257,7 +262,7 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Full restore ...");
using (new Library.Logging.Timer(LOGTAG, "FullRestore", "Full restore"))
Duplicati.CommandLine.Program.RealMain((new string[] { "restore", target, "*", "--restore-path=\"" + RESTOREFOLDER + "\"" }.Union(opts)).ToArray());
Duplicati.CommandLine.Program.Main((new string[] { "restore", target, "*", "--restore-path=\"" + RESTOREFOLDER + "\"" }.Union(opts)).ToArray());
ProgressWriteLine("Verifying full restore ...");
using (new Library.Logging.Timer(LOGTAG, "VerificationOfFullRestore", "Verification of restored files"))
@@ -268,7 +273,7 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Full restore without local db...");
using (new Library.Logging.Timer(LOGTAG, "FullRestoreWithoutDb", "Full restore without local db"))
Duplicati.CommandLine.Program.RealMain((new string[] { "restore", target, "*", "--restore-path=\"" + RESTOREFOLDER + "\"", "--no-local-db" }.Union(opts)).ToArray());
Duplicati.CommandLine.Program.Main((new string[] { "restore", target, "*", "--restore-path=\"" + RESTOREFOLDER + "\"", "--no-local-db" }.Union(opts)).ToArray());
ProgressWriteLine("Verifying full restore ...");
using (new Library.Logging.Timer(LOGTAG, "VerificationOfFullRestoreWithoutDb", "Verification of restored files"))
@@ -277,7 +282,7 @@ namespace Duplicati.UnitTest
ProgressWriteLine("Testing data ...");
using (new Library.Logging.Timer(LOGTAG, "TestRemoteData", "Test remote data"))
if (Duplicati.CommandLine.Program.RealMain((new string[] { "test", target, "all" }.Union(opts)).ToArray()) != 0)
if (Duplicati.CommandLine.Program.Main((new string[] { "test", target, "all" }.Union(opts)).ToArray()) != 0)
throw new Exception("Failed during final remote verification");
}
}
+46 -29
View File
@@ -25,6 +25,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Duplicati.Library.Interface;
using Duplicati.Library.Logging;
using Duplicati.Library.Main;
using Duplicati.Library.Main.Database;
using Duplicati.Library.Main.Volumes;
@@ -37,7 +38,7 @@ namespace Duplicati.UnitTest
public class DisruptionTests : BasicSetupHelper
{
// Files to create in MB.
private readonly int[] fileSizes = {10, 20, 30};
private readonly int[] fileSizes = { 10, 20, 30 };
private void ModifySourceFiles()
{
@@ -55,11 +56,24 @@ namespace Duplicati.UnitTest
this.ModifySourceFiles();
// ReSharper disable once AccessToDisposedClosure
Task<IBackupResults> backupTask = Task.Run(() => controller.Backup(new[] {this.DATAFOLDER}));
Task<IBackupResults> backupTask = Task.Run(() => controller.Backup(new[] { this.DATAFOLDER }));
// Block for a small amount of time to allow the ITaskControl to be associated
// with the Controller. Otherwise, the call to Stop will simply be a no-op.
Thread.Sleep(1000);
IBackupResults taskControl = null;
// Wait for the controller to have the task associated
while (!backupTask.IsCompleted)
{
taskControl = controller.GetType().GetField("m_currentTask", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(controller) as IBackupResults;
if (taskControl != null)
break;
Thread.Sleep(100);
}
// Wait for the first file to be processed
while (!backupTask.IsCompleted && taskControl.ExaminedFiles == 0)
Thread.Sleep(100);
if (backupTask.IsCompleted)
throw new Exception("Backup task completed before we could stop it");
controller.Stop(true);
return await backupTask.ConfigureAwait(false);
@@ -87,11 +101,14 @@ namespace Duplicati.UnitTest
// Run a full backup.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
}
// Inject some spacing to allow for the purged fileset
Thread.Sleep(2000);
// Run a partial backup.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
@@ -175,14 +192,14 @@ namespace Duplicati.UnitTest
public async Task KeepTimeRetention()
{
// Choose a dblock size that is small enough so that more than one volume is needed.
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) {["dblock-size"] = "10mb"};
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) { ["dblock-size"] = "10mb" };
// First, run two complete backups followed by a partial backup. We will then set the keep-time
// option so that the threshold lies between the first and second backups.
DateTime firstBackupTime;
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
firstBackupTime = c.List().Filesets.First().Time;
@@ -195,7 +212,7 @@ namespace Duplicati.UnitTest
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
this.ModifySourceFiles();
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
secondBackupTime = c.List().Filesets.First().Time;
@@ -215,7 +232,7 @@ namespace Duplicati.UnitTest
// and run the delete operation.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
options["keep-time"] = $"{(int) ((DateTime.Now - firstBackupTime).TotalSeconds - (secondBackupTime - firstBackupTime).TotalSeconds / 2)}s";
options["keep-time"] = $"{(int)((DateTime.Now - firstBackupTime).TotalSeconds - (secondBackupTime - firstBackupTime).TotalSeconds / 2)}s";
IDeleteResults deleteResults = c.Delete();
Assert.AreEqual(0, deleteResults.Errors.Count());
Assert.AreEqual(0, deleteResults.Warnings.Count());
@@ -260,13 +277,13 @@ namespace Duplicati.UnitTest
public async Task KeepVersionsRetention()
{
// Choose a dblock size that is small enough so that more than one volume is needed.
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) {["dblock-size"] = "10mb"};
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) { ["dblock-size"] = "10mb" };
// Run a full backup.
DateTime firstBackupTime;
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
firstBackupTime = c.List().Filesets.First().Time;
@@ -293,7 +310,7 @@ namespace Duplicati.UnitTest
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
this.ModifySourceFiles();
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
fourthBackupTime = c.List().Filesets.First().Time;
@@ -323,7 +340,7 @@ namespace Duplicati.UnitTest
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
this.ModifySourceFiles();
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
DateTime sixthBackupTime = c.List().Filesets.First().Time;
@@ -352,7 +369,7 @@ namespace Duplicati.UnitTest
// Run a full backup.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
}
@@ -388,7 +405,7 @@ namespace Duplicati.UnitTest
DateTime firstBackupTime;
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
firstBackupTime = c.List().Filesets.First().Time;
@@ -397,7 +414,7 @@ namespace Duplicati.UnitTest
Assert.AreEqual(1, filesets.Count);
this.ModifySourceFiles();
backupResults = c.Backup(new[] {this.DATAFOLDER});
backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
DateTime secondBackupTime = c.List().Filesets.First().Time;
@@ -438,7 +455,7 @@ namespace Duplicati.UnitTest
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
this.ModifySourceFiles();
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
fourthBackupTime = c.List().Filesets.First().Time;
@@ -456,7 +473,7 @@ namespace Duplicati.UnitTest
Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup);
this.ModifySourceFiles();
backupResults = c.Backup(new[] {this.DATAFOLDER});
backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
DateTime fifthBackupTime = c.List().Filesets.First().Time;
@@ -483,7 +500,7 @@ namespace Duplicati.UnitTest
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
this.ModifySourceFiles();
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
DateTime sixthBackupTime = c.List().Filesets.First().Time;
@@ -509,12 +526,12 @@ namespace Duplicati.UnitTest
public async Task StopAfterCurrentFile()
{
// Choose a dblock size that is small enough so that more than one volume is needed.
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) {["dblock-size"] = "10mb"};
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) { ["dblock-size"] = "10mb" };
// Run a complete backup.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
@@ -536,7 +553,7 @@ namespace Duplicati.UnitTest
}
// Restore files from the partial backup set.
Dictionary<string, string> restoreOptions = new Dictionary<string, string>(options) {["restore-path"] = this.RESTOREFOLDER};
Dictionary<string, string> restoreOptions = new Dictionary<string, string>(options) { ["restore-path"] = this.RESTOREFOLDER };
using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
{
IListResults lastResults = c.List("*");
@@ -567,7 +584,7 @@ namespace Duplicati.UnitTest
// Run a complete backup. Listing the Filesets should include both full and partial backups.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
Assert.AreEqual(3, c.List().Filesets.Count());
@@ -602,12 +619,12 @@ namespace Duplicati.UnitTest
public async Task StopNow()
{
// Choose a dblock size that is small enough so that more than one volume is needed.
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) {["dblock-size"] = "10mb", ["disable-synthetic-filelist"] = "true"};
Dictionary<string, string> options = new Dictionary<string, string>(this.TestOptions) { ["dblock-size"] = "10mb", ["disable-synthetic-filelist"] = "true" };
// Run a complete backup.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
@@ -621,7 +638,7 @@ namespace Duplicati.UnitTest
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
// ReSharper disable once AccessToDisposedClosure
Task backupTask = Task.Run(() => c.Backup(new[] {this.DATAFOLDER}));
Task backupTask = Task.Run(() => c.Backup(new[] { this.DATAFOLDER }));
// Block for a small amount of time to allow the ITaskControl to be associated
// with the Controller. Otherwise, the call to Stop will simply be a no-op.
@@ -634,7 +651,7 @@ namespace Duplicati.UnitTest
// The next backup should proceed without issues.
using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null))
{
IBackupResults backupResults = c.Backup(new[] {this.DATAFOLDER});
IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER });
Assert.AreEqual(0, backupResults.Errors.Count());
Assert.AreEqual(0, backupResults.Warnings.Count());
@@ -645,7 +662,7 @@ namespace Duplicati.UnitTest
}
// Restore from the backup that followed the interruption.
Dictionary<string, string> restoreOptions = new Dictionary<string, string>(options) {["restore-path"] = this.RESTOREFOLDER};
Dictionary<string, string> restoreOptions = new Dictionary<string, string>(options) { ["restore-path"] = this.RESTOREFOLDER };
using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null))
{
IListResults lastResults = c.List("*");
+3 -3
View File
@@ -90,17 +90,17 @@ namespace Duplicati.UnitTest
// Download the backend files.
string downloadFolder = Path.Combine(this.RESTOREFOLDER, "downloadedFiles");
Directory.CreateDirectory(downloadFolder);
int status = CommandLine.RecoveryTool.Program.RealMain(new[] {"download", $"{backendURL}", $"{downloadFolder}", $"--passphrase={options["passphrase"]}"});
int status = CommandLine.RecoveryTool.Program.Main(new[] {"download", $"{backendURL}", $"{downloadFolder}", $"--passphrase={options["passphrase"]}"});
Assert.AreEqual(0, status);
// Create the index.
status = CommandLine.RecoveryTool.Program.RealMain(new[] {"index", $"{downloadFolder}", $"--build-index-with-files={buildIndexWithFiles}"});
status = CommandLine.RecoveryTool.Program.Main(new[] {"index", $"{downloadFolder}", $"--build-index-with-files={buildIndexWithFiles}"});
Assert.AreEqual(0, status);
// Restore to a different folder.
string restoreFolder = Path.Combine(this.RESTOREFOLDER, "restoredFiles");
Directory.CreateDirectory(restoreFolder);
status = CommandLine.RecoveryTool.Program.RealMain(new[] {"restore", $"{downloadFolder}", $"--targetpath={restoreFolder}"});
status = CommandLine.RecoveryTool.Program.Main(new[] {"restore", $"{downloadFolder}", $"--targetpath={restoreFolder}"});
Assert.AreEqual(0, status);
// Since this.DATAFOLDER is a folder, Path.GetFileName will return the name of the
+27 -32
View File
@@ -1,32 +1,28 @@
// 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.
// 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.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
namespace Duplicati.WindowsService
{
@@ -34,11 +30,6 @@ namespace Duplicati.WindowsService
{
[STAThread]
public static int Main(string[] args)
{
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, Duplicati.Library.AutoUpdater.AutoUpdateStrategy.Never);
}
public static void RealMain(string[] args)
{
var install = args != null && args.Any(x => string.Equals("install", x, StringComparison.OrdinalIgnoreCase));
var uninstall = args != null && args.Any(x => string.Equals("uninstall", x, StringComparison.OrdinalIgnoreCase));
@@ -66,8 +57,8 @@ namespace Duplicati.WindowsService
{
// Remove the install and uninstall flags if they are present
var commandline = Library.Utility.Utility.WrapAsCommandLine(args.Where(x => !(string.Equals("install", x, StringComparison.OrdinalIgnoreCase) || string.Equals("uninstall", x, StringComparison.OrdinalIgnoreCase))));
var selfexec = Assembly.GetExecutingAssembly().Location;
var selfexec = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Duplicati.WindowsService.exe");
// --uninstall + --install = reinstall
if (uninstall)
{
@@ -79,6 +70,7 @@ namespace Duplicati.WindowsService
catch (Exception ex)
{
Console.WriteLine("Duplicati service delete failed. Exception: {0}", ex.Message);
return 1;
}
}
if (install)
@@ -93,6 +85,7 @@ namespace Duplicati.WindowsService
catch (Exception ex)
{
Console.WriteLine("Duplicati service installation failed. Exception: {0}", ex.Message);
return 1;
}
}
}
@@ -100,6 +93,8 @@ namespace Duplicati.WindowsService
{
ServiceBase.Run(new ServiceBase[] { new ServiceControl(args) });
}
return 0;
}
}
}
-59
View File
@@ -1,59 +0,0 @@
<configuration>
<runtime>
<loadFromRemoteSources enabled="true"/>
<AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- Support for loading older backends with the current executable version -->
<dependentAssembly>
<assemblyIdentity name="Duplicati.Library.Utility" publicKeyToken="8bfe994a39631a7b" />
<bindingRedirect oldVersion="0.0.0.0 - 99.9.9.9" newVersion="2.0.0.7" />
</dependentAssembly>
<!-- Support for loading older backends with the current executable version -->
<dependentAssembly>
<assemblyIdentity name="Duplicati.Library.Interface" publicKeyToken="8bfe994a39631a7b" />
<bindingRedirect oldVersion="0.0.0.0 - 99.9.9.9" newVersion="2.0.0.7" />
</dependentAssembly>
<!-- Support for backends that wants older versions of NewtonSoft.Json -->
<!-- The spaces near the equation sign are used to prevent the version being set to the current build by the VersionStamp tool -->
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0 - 99.9.9.9" newVersion = "13.0.0.0"/>
</dependentAssembly>
<!-- Support for backends that wants older versions of KeyVault.Core and System.Net.Http -->
<!-- The spaces near the equation sign are used to prevent the version being set to the current build by the VersionStamp tool -->
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.4.0" newVersion = "3.0.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.0" newVersion = "4.0.0.0" />
</dependentAssembly>
<!-- Support for backends that wants older version of System.Memory and System.Runtime.CompilerServices.Unsafe -->
<!-- The spaces near the equation sign are used to prevent the version being set to the current build by the VersionStamp tool -->
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.4.1" newVersion = "6.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion = "4.0.1.2"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion = "4.0.3.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/>
</startup>
</configuration>
+21
View File
@@ -0,0 +1,21 @@
FROM --platform=$TARGETPLATFORM debian:latest
RUN apt update && apt install tini
ENTRYPOINT [ "/usr/bin/tini", "--" ]
RUN apt update && apt install libssl3
ENV XDG_CONFIG_HOME=/data
VOLUME /data
ARG CHANNEL=
ARG VERSION=
ENV DUPLICATI_CHANNEL=${CHANNEL}
ENV DUPLICATI_VERSION=${VERSION}
ARG TARGETARCH
COPY ./${TARGETARCH} /opt/duplicati
EXPOSE 8200
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
CMD ["/opt/duplicati/duplicati-server", "--webservice-port=8200", "--webservice-interface=any"]
-67
View File
@@ -1,67 +0,0 @@
#!/bin/bash
if [ ! -f "$1" ]; then
echo "Please provide the filename of an existing zip build as the first argument"
exit
fi
PLATFORMS="linux/amd64,linux/arm/v7,linux/arm64"
DEFAULT_CHANNEL=beta
REPOSITORY=duplicati/duplicati
PUSH_TO_REGISTRY=${PUSH_TO_REGISTRY:-true}
ARCHIVE_NAME=$(basename -s .zip $1)
VERSION=$(echo "${ARCHIVE_NAME}" | cut -d "-" -f 2-)
CHANNEL=$(echo "${ARCHIVE_NAME}" | cut -d "_" -f 2)
DIRNAME=duplicati
if [ -d "${DIRNAME}" ]; then
rm -rf "${DIRNAME}"
fi
unzip -d "${DIRNAME}" "$1"
for n in "../oem" "../../oem" "../../../oem"
do
if [ -d $n ]; then
echo "Installing OEM files"
cp -R $n "${DIRNAME}/webroot/"
fi
done
for n in "oem-app-name.txt" "oem-update-url.txt" "oem-update-key.txt" "oem-update-readme.txt" "oem-update-installid.txt"
do
for p in "../$n" "../../$n" "../../../$n"
do
if [ -f $p ]; then
echo "Installing OEM override file"
cp $p "${DIRNAME}"
fi
done
done
tags="${VERSION} ${CHANNEL}"
if [ ${CHANNEL} = ${DEFAULT_CHANNEL} ]; then
tags="latest ${tags}"
fi
args=""
for tag in ${tags}; do
args="-t ${REPOSITORY}:${tag} ${args}"
done
docker buildx create --use --name duplicati-multiarch
docker buildx build \
${args} \
--platform ${PLATFORMS} \
--build-arg PARENT_IMAGE="$(cat mono_image.txt)-slim" \
--build-arg VERSION=${VERSION} \
--build-arg CHANNEL=${CHANNEL} \
--file context/Dockerfile \
--output type=image,push=${PUSH_TO_REGISTRY} \
.
docker buildx rm duplicati-multiarch
rm -rf "${DIRNAME}"
-37
View File
@@ -1,37 +0,0 @@
ARG PARENT_IMAGE
FROM --platform=$TARGETPLATFORM ${PARENT_IMAGE}
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
mono-complete \
libmono-sqlite4.0-cil \
libmono-system-drawing4.0-cil \
libmono-system-net-http-webrequest4.0-cil \
libmono-system-web4.0-cil \
referenceassemblies-pcl && \
rm -rf /var/lib/apt/lists && \
cert-sync /etc/ssl/certs/ca-certificates.crt && \
# this obsolete cert can mess the certificate chain because of a Mono bug
rm -f /usr/share/ca-certificates/mozilla/DST_Root_CA_X3.crt && \
update-ca-certificates
ENV TINI_VERSION v0.16.1
RUN curl -L -o /usr/sbin/tini https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-$(dpkg --print-architecture) && \
chmod 0755 /usr/sbin/tini
ENTRYPOINT ["/usr/sbin/tini", "--"]
ENV XDG_CONFIG_HOME=/data
VOLUME /data
COPY context/duplicati-cli context/duplicati-server /usr/bin/
RUN chmod 0755 /usr/bin/duplicati-cli /usr/bin/duplicati-server
ARG CHANNEL=
ARG VERSION=
ENV DUPLICATI_CHANNEL=${CHANNEL}
ENV DUPLICATI_VERSION=${VERSION}
COPY duplicati /opt/duplicati
EXPOSE 8200
CMD ["/usr/bin/duplicati-server", "--webservice-port=8200", "--webservice-interface=any"]
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
EXE_FILE=/opt/duplicati/Duplicati.CommandLine.exe
APP_NAME=Duplicati.CommandLine
exec -a "$APP_NAME" mono "$EXE_FILE" "$@"
@@ -1,4 +0,0 @@
#!/bin/bash
EXE_FILE=/opt/duplicati/Duplicati.Server.exe
APP_NAME=DuplicatiServer
exec -a "$APP_NAME" mono "$EXE_FILE" "$@"
-1
View File
@@ -1 +0,0 @@
mono:6
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array/>
</plist>
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<body>
<p>
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.
</p>
</body>
</html>
@@ -5,12 +5,14 @@
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Duplicati.GUI.TrayIcon</string>
<string>duplicati</string>
<key>CFBundleIconFile</key>
<string>Duplicati.icns</string>
<key>CFBundleIdentifier</key>
<string>com.duplicati.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<key>NSHighResolutionCapable</key>
<true/>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Duplicati</string>

Some files were not shown because too many files have changed in this diff Show More