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:
+17
-22
@@ -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
@@ -27,4 +27,6 @@ binfiles.wxs
|
||||
|
||||
Installer/debian/*.buildinfo
|
||||
/*.deb
|
||||
/*.rpm
|
||||
/*.rpm
|
||||
|
||||
ReleaseBuilder/build-temp/
|
||||
Vendored
+23
-1
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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; } }
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
+66
-35
@@ -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))
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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; }
|
||||
|
||||
}
|
||||
|
||||
@@ -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> </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 && !state.updateReady && 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 && state.updateReady && 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 && state.updateDownloadLink != null && !state.updateReady && 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' && 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 "Save as …"</a></div>
|
||||
</div>
|
||||
<div ng-if="item.Action != 'update:new'" class="buttons">
|
||||
<div ng-if="item.Action != 'update:new' && 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 && state.updaterState == 'Waiting'" ng-click="doInstallUpdate(item.ID)" class="button installupdate" translate>Install</a>
|
||||
<a href ng-show="state.updateReady && 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' && 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' && serverstate.UpdatedVersion != null && state.updateDownloadLink != null" translate>Download</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("*");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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"]
|
||||
@@ -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}"
|
||||
@@ -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"]
|
||||
@@ -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 +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>
|
||||
@@ -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
Reference in New Issue
Block a user