Fixed running server in-process from the WindowsService. This fixes the MSI not cleanly restarting a running service.
This commit is contained in:
@@ -90,6 +90,15 @@ namespace Duplicati.Server
|
||||
/// <summary>The commandline argument name for help.</summary>
|
||||
private const string HELP_OPTION = "help";
|
||||
|
||||
/// <summary>
|
||||
/// The exit code for init password success
|
||||
/// </summary>
|
||||
public const int EXITCODE_INITPASSWORD_SUCCESS = 102;
|
||||
/// <summary>
|
||||
/// The exit code for init password failure
|
||||
/// </summary>
|
||||
public const int EXITCODE_INITPASSWORD_FAILED = 103;
|
||||
|
||||
/// <summary>
|
||||
/// The name used in the secret provider for the database encryption key
|
||||
/// </summary>
|
||||
@@ -263,7 +272,9 @@ namespace Duplicati.Server
|
||||
connection.UpdateUsageReporterCallback(applicationSettings.StartOrStopUsageReporter);
|
||||
applicationSettings.StartOrStopUsageReporter?.Invoke();
|
||||
|
||||
AdjustApplicationSettings(connection, commandlineOptions);
|
||||
var adjustres = AdjustApplicationSettings(connection, commandlineOptions);
|
||||
if (adjustres.HasValue)
|
||||
return adjustres.Value;
|
||||
|
||||
UpdaterManager.OnError += obj =>
|
||||
{
|
||||
@@ -622,7 +633,8 @@ namespace Duplicati.Server
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection to use</param>
|
||||
/// <param name="commandlineOptions">The commandline options</param>
|
||||
private static void AdjustApplicationSettings(Connection connection, Dictionary<string, string> commandlineOptions)
|
||||
/// <returns>The exit code, or null if no exit is required</returns>
|
||||
private static int? AdjustApplicationSettings(Connection connection, Dictionary<string, string> commandlineOptions)
|
||||
{
|
||||
// This clears the JWT config, and a new will be generated, invalidating all existing tokens
|
||||
if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_RESET_JWT_CONFIG))
|
||||
@@ -652,14 +664,15 @@ namespace Duplicati.Server
|
||||
var initPassword = commandlineOptions.GetValueOrDefault(WebServerLoader.OPTION_WEBSERVICE_PASSWORD_INIT);
|
||||
if (!string.IsNullOrWhiteSpace(initPassword))
|
||||
{
|
||||
// If initPassword is set, we want the initialization to stop now, so we throw an exception
|
||||
if (connection.ApplicationSettings.AutogeneratedPassphrase)
|
||||
{
|
||||
connection.ApplicationSettings.SetWebserverPassword(initPassword);
|
||||
Environment.Exit(0);
|
||||
return EXITCODE_INITPASSWORD_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
Environment.Exit(2);
|
||||
return EXITCODE_INITPASSWORD_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,6 +706,8 @@ namespace Duplicati.Server
|
||||
connection.ApplicationSettings.UpdatedVersion = null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Library\Utility\Duplicati.Library.Utility.csproj" />
|
||||
<ProjectReference Include="..\Server\Duplicati.Server.csproj" />
|
||||
<ProjectReference Include="..\Agent\Duplicati.Agent.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
// Copyright (C) 2026, 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
|
||||
// Copyright (C) 2026, 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;
|
||||
@@ -31,7 +31,7 @@ namespace Duplicati.Service
|
||||
{
|
||||
PreloadSettingsLoader.ConfigurePreloadSettings(ref args, PackageHelper.NamedExecutable.Service);
|
||||
|
||||
using (var runner = new Runner(PackageHelper.NamedExecutable.Server, args))
|
||||
using (var runner = new Runner(PackageHelper.NamedExecutable.Server, args, inProcess: false))
|
||||
runner.Wait();
|
||||
|
||||
return 0;
|
||||
|
||||
+51
-10
@@ -43,8 +43,19 @@ namespace Duplicati.Service
|
||||
|
||||
private readonly int WAIT_POLL_TIME = (int)TimeSpan.FromMinutes(15).TotalMilliseconds;
|
||||
|
||||
public Runner(PackageHelper.NamedExecutable executable, string[] cmdargs, Action onStartedAction = null, Action onStoppedAction = null, Action<string, bool> logMessage = null, Action<System.Diagnostics.ProcessStartInfo> onBeforeFirstStart = null)
|
||||
private readonly bool m_inProcess;
|
||||
|
||||
public Runner(
|
||||
PackageHelper.NamedExecutable executable,
|
||||
string[] cmdargs,
|
||||
bool inProcess,
|
||||
Action onStartedAction = null,
|
||||
Action onStoppedAction = null,
|
||||
Action<string, bool> logMessage = null,
|
||||
Action<System.Diagnostics.ProcessStartInfo> onBeforeFirstStart = null
|
||||
)
|
||||
{
|
||||
m_inProcess = inProcess;
|
||||
m_onStartedAction = onStartedAction;
|
||||
m_onStoppedAction = onStoppedAction;
|
||||
m_reportMessage = logMessage;
|
||||
@@ -54,13 +65,46 @@ namespace Duplicati.Service
|
||||
m_reportMessage = (x, y) => Console.WriteLine(x);
|
||||
|
||||
m_cmdargs = cmdargs;
|
||||
m_thread = new System.Threading.Thread(Run);
|
||||
m_thread = new System.Threading.Thread(m_inProcess ? RunInProcess : RunSpawned);
|
||||
m_thread.IsBackground = true;
|
||||
m_thread.Name = "Server Runner";
|
||||
m_thread.Start();
|
||||
}
|
||||
|
||||
private void Run()
|
||||
private void RunInProcess()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_reportMessage(string.Format("Starting in-process {0} with cmd args {1}", m_executable, string.Join(Environment.NewLine, m_cmdargs ?? new string[0])), false);
|
||||
|
||||
m_onBeforeFirstStart?.Invoke(null);
|
||||
m_onStartedAction?.Invoke();
|
||||
|
||||
if (!m_terminate && !m_softstop)
|
||||
{
|
||||
if (m_executable == PackageHelper.NamedExecutable.Server)
|
||||
Server.Program.Main(m_cmdargs ?? []);
|
||||
else if (m_executable == PackageHelper.NamedExecutable.Agent)
|
||||
Agent.Program.MainAsync(m_cmdargs ?? []).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
else
|
||||
throw new NotImplementedException($"In-process execution of {m_executable} is not implemented");
|
||||
}
|
||||
|
||||
if (!m_terminate && !m_softstop)
|
||||
m_reportMessage("In-process execution ended unexpectedly", true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_reportMessage(string.Format("In-process execution has failed with error message: {0}", ex), true);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_onStoppedAction?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void RunSpawned()
|
||||
{
|
||||
var path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
var exec = System.IO.Path.Combine(path, PackageHelper.GetExecutableName(m_executable));
|
||||
@@ -184,18 +228,15 @@ namespace Duplicati.Service
|
||||
m_thread.Join();
|
||||
}
|
||||
|
||||
public void Stop(bool force = true)
|
||||
public void Stop()
|
||||
{
|
||||
if (force)
|
||||
m_softstop = true;
|
||||
if (m_inProcess)
|
||||
{
|
||||
m_terminate = true;
|
||||
var p = m_process;
|
||||
if (p != null)
|
||||
p.Kill();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
m_softstop = true;
|
||||
lock (m_writelock)
|
||||
{
|
||||
if (m_process != null)
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj" />
|
||||
<ProjectReference Include="..\Library\Utility\Duplicati.Library.Utility.csproj" />
|
||||
<ProjectReference Include="..\Server\Duplicati.Server.csproj" />
|
||||
<ProjectReference Include="..\Agent\Duplicati.Agent.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -175,6 +175,7 @@ namespace Duplicati.WindowsService
|
||||
m_runner = new Runner(
|
||||
m_executable,
|
||||
startargs,
|
||||
true,
|
||||
() =>
|
||||
{
|
||||
if (m_verbose_messages)
|
||||
@@ -206,7 +207,7 @@ namespace Duplicati.WindowsService
|
||||
(startInfo) =>
|
||||
{
|
||||
// If we have a reset password, pass it to the server once
|
||||
if (!string.IsNullOrEmpty(resetPassword))
|
||||
if (!string.IsNullOrEmpty(resetPassword) && startInfo != null)
|
||||
{
|
||||
startInfo.EnvironmentVariables["DUPLICATI__WEBSERVICE_PASSWORD"] = resetPassword;
|
||||
resetPassword = null;
|
||||
@@ -234,7 +235,7 @@ namespace Duplicati.WindowsService
|
||||
|
||||
if (m_verbose_messages)
|
||||
m_eventLog.WriteEntry("Soft stop invoked...");
|
||||
m_runner.Stop(false);
|
||||
m_runner.Stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -402,35 +403,22 @@ namespace Duplicati.WindowsService
|
||||
{
|
||||
m_eventLog.WriteEntry("Found init password in registry, applying...", System.Diagnostics.EventLogEntryType.Information);
|
||||
|
||||
var installDir = UpdaterManager.INSTALLATIONDIR;
|
||||
var exeName = PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.Server);
|
||||
var serverExe = System.IO.Path.Combine(installDir, exeName);
|
||||
if (m_verbose_messages)
|
||||
m_eventLog.WriteEntry($"Starting server in-process to apply init password...", System.Diagnostics.EventLogEntryType.Information);
|
||||
|
||||
try
|
||||
{
|
||||
var res = Server.Program.Main(["--webservice-password-init=" + password]);
|
||||
if (res == Server.Program.EXITCODE_INITPASSWORD_SUCCESS)
|
||||
success = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_eventLog.WriteEntry($"Exception from applying init password: {ex.Message}", System.Diagnostics.EventLogEntryType.Warning);
|
||||
}
|
||||
|
||||
if (m_verbose_messages)
|
||||
m_eventLog.WriteEntry($"Starting server at {serverExe} to apply init password...", System.Diagnostics.EventLogEntryType.Information);
|
||||
|
||||
if (System.IO.File.Exists(serverExe))
|
||||
{
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = serverExe,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
startInfo.EnvironmentVariables["DUPLICATI__WEBSERVICE_PASSWORD_INIT"] = password;
|
||||
|
||||
using (var process = System.Diagnostics.Process.Start(startInfo))
|
||||
{
|
||||
if (process.WaitForExit(30000) && process.ExitCode == 0)
|
||||
success = true;
|
||||
|
||||
if (m_verbose_messages)
|
||||
m_eventLog.WriteEntry($"Result from applying init password: {success}, code={(process.HasExited ? process.ExitCode.ToString() : "-alive-")}", System.Diagnostics.EventLogEntryType.Information);
|
||||
if (!process.HasExited)
|
||||
try { process.Kill(); } catch { }
|
||||
}
|
||||
}
|
||||
m_eventLog.WriteEntry($"Result from applying init password: {success}", System.Diagnostics.EventLogEntryType.Information);
|
||||
}
|
||||
|
||||
// Always delete the InitPassword key if found
|
||||
|
||||
@@ -60,7 +60,7 @@ function IsBootstrapApplied() {
|
||||
inParams.sValueName = "BootstrapApplied";
|
||||
|
||||
var outParams = reg.provider.ExecMethod_("GetStringValue", inParams, 0, reg.ctx);
|
||||
if (outParams.ReturnValue !== 0) {
|
||||
if (outParams.ReturnValue !== 0 || outParams.sValue === "unset") {
|
||||
return null;
|
||||
}
|
||||
if (outParams.sValue === "0") {
|
||||
|
||||
Reference in New Issue
Block a user