diff --git a/src/fiskaltrust.Launcher.Common/fiskaltrust.Launcher.Common.csproj b/src/fiskaltrust.Launcher.Common/fiskaltrust.Launcher.Common.csproj
index 7a3316a6..0645ad06 100644
--- a/src/fiskaltrust.Launcher.Common/fiskaltrust.Launcher.Common.csproj
+++ b/src/fiskaltrust.Launcher.Common/fiskaltrust.Launcher.Common.csproj
@@ -7,11 +7,11 @@
-
-
+
+
-
+
-
+
\ No newline at end of file
diff --git a/src/fiskaltrust.Launcher/Commands/Common.cs b/src/fiskaltrust.Launcher/Commands/Common.cs
index 9f80679d..37eb8915 100644
--- a/src/fiskaltrust.Launcher/Commands/Common.cs
+++ b/src/fiskaltrust.Launcher/Commands/Common.cs
@@ -157,12 +157,23 @@ public static async Task HandleAsync(
ECDiffieHellman? clientEcdh = null;
try
{
- clientEcdh = await LoadCurve(launcherConfiguration.CashboxId!.Value, launcherConfiguration.AccessToken!, launcherConfiguration.ServiceFolder!, launcherConfiguration.UseOffline!.Value, useFallback: launcherConfiguration.UseLegacyDataProtection!.Value);
- using var downloader = new ConfigurationDownloader(launcherConfiguration);
- var exists = await downloader.DownloadConfigurationAsync(clientEcdh);
- if (launcherConfiguration.UseOffline!.Value && !exists)
+ clientEcdh = await LoadCurve(launcherConfiguration.CashboxId!.Value, launcherConfiguration.AccessToken!, launcherConfiguration.ServiceFolder!, launcherConfiguration.UseOffline!.Value);
+ }
+ catch (Exception e)
+ {
+ Log.Fatal(e, "Could not load client curve.");
+ }
+
+ try
+ {
+ if (clientEcdh is not null)
{
- Log.Warning("Cashbox configuration was not downloaded because UseOffline is set.");
+ using var downloader = new ConfigurationDownloader(launcherConfiguration);
+ var exists = await downloader.DownloadConfigurationAsync(clientEcdh);
+ if (launcherConfiguration.UseOffline!.Value && !exists)
+ {
+ Log.Warning("Cashbox configuration was not downloaded because UseOffline is set.");
+ }
}
}
catch (Exception e)
@@ -192,7 +203,7 @@ public static async Task HandleAsync(
try
{
cashboxConfiguration = CashBoxConfigurationExt.Deserialize(await File.ReadAllTextAsync(launcherConfiguration.CashboxConfigurationFile!));
- cashboxConfiguration.Decrypt(launcherConfiguration, clientEcdh);
+ if (clientEcdh is not null) { cashboxConfiguration.Decrypt(launcherConfiguration, clientEcdh); }
}
catch (Exception e)
{
@@ -224,6 +235,7 @@ public static async Task HandleAsync(
Log.Debug("Cashbox Configuration File: {CashboxConfigurationFile}", launcherConfiguration.CashboxConfigurationFile);
Log.Debug("Launcher Configuration: {@LauncherConfiguration}", launcherConfiguration.Redacted());
+ Log.Debug("Launcher running as {ServiceType}", Enum.GetName(typeof(ServiceTypes), host.Services.GetRequiredService().Type));
var dataProtectionProvider = DataProtectionExtensions.Create(launcherConfiguration.AccessToken, useFallback: launcherConfiguration.UseLegacyDataProtection!.Value);
@@ -236,12 +248,12 @@ public static async Task HandleAsync(
Log.Warning(e, "Error decrypring launcher configuration file.");
}
- return await handler(options, new CommonProperties(launcherConfiguration, cashboxConfiguration, clientEcdh, dataProtectionProvider), specificOptions, host.Services.GetRequiredService());
+ return await handler(options, new CommonProperties(launcherConfiguration, cashboxConfiguration, clientEcdh!, dataProtectionProvider), specificOptions, host.Services.GetRequiredService());
}
private static async Task EnsureServiceDirectoryExists(LauncherConfiguration config)
{
- var serviceDirectory = config.ServiceFolder;
+ var serviceDirectory = config.ServiceFolder!;
try
{
if (!Directory.Exists(serviceDirectory))
diff --git a/src/fiskaltrust.Launcher/Commands/RunCommand.cs b/src/fiskaltrust.Launcher/Commands/RunCommand.cs
index 014a1400..7eb32b56 100644
--- a/src/fiskaltrust.Launcher/Commands/RunCommand.cs
+++ b/src/fiskaltrust.Launcher/Commands/RunCommand.cs
@@ -1,5 +1,4 @@
using System.CommandLine;
-using System.CommandLine.Invocation;
using fiskaltrust.Launcher.ProcessHost;
using fiskaltrust.Launcher.Services;
using Serilog;
@@ -8,12 +7,6 @@
using fiskaltrust.Launcher.Extensions;
using fiskaltrust.Launcher.Helpers;
using Microsoft.AspNetCore.Server.Kestrel.Core;
-using fiskaltrust.Launcher.Common.Configuration;
-using fiskaltrust.storage.serialization.V0;
-using System.Security.Cryptography;
-using Microsoft.AspNetCore.DataProtection;
-using Microsoft.AspNetCore.Hosting.Server;
-using Microsoft.AspNetCore.Hosting.Server.Features;
namespace fiskaltrust.Launcher.Commands
diff --git a/src/fiskaltrust.Launcher/Extensions/LifetimeExtensions.cs b/src/fiskaltrust.Launcher/Extensions/LifetimeExtensions.cs
index 2ae00daa..0e449510 100644
--- a/src/fiskaltrust.Launcher/Extensions/LifetimeExtensions.cs
+++ b/src/fiskaltrust.Launcher/Extensions/LifetimeExtensions.cs
@@ -1,10 +1,22 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
+using Microsoft.Extensions.Hosting.Systemd;
using Microsoft.Extensions.Hosting.WindowsServices;
using Microsoft.Extensions.Options;
namespace fiskaltrust.Launcher.Extensions
{
+ public record ServiceType(ServiceTypes Type);
+
+ public enum ServiceTypes
+ {
+ WindowsService,
+ SystemdService,
+ ConsoleApplication
+ }
+
static class LifetimeExtensions
{
public static IHostBuilder UseCustomHostLifetime(this IHostBuilder builder)
@@ -15,6 +27,7 @@ public static IHostBuilder UseCustomHostLifetime(this IHostBuilder builder)
return builder.ConfigureServices(services =>
{
+ services.AddSingleton(new ServiceType(ServiceTypes.WindowsService));
var lifetime = services.FirstOrDefault(s => s.ImplementationType == typeof(WindowsServiceLifetime));
if (lifetime != null)
@@ -28,10 +41,29 @@ public static IHostBuilder UseCustomHostLifetime(this IHostBuilder builder)
#pragma warning restore CA1416
});
}
+ else if (SystemdHelpers.IsSystemdService())
+ {
+ builder.UseSystemd();
+
+ return builder.ConfigureServices(services =>
+ {
+ services
+ .AddSingleton(new ServiceType(ServiceTypes.SystemdService))
+ .AddSingleton();
+
+ // #pragma warning disable CA1416
+ // services.AddSingleton();
+ // services.AddSingleton(sp => sp.GetRequiredService());
+ // #pragma warning restore CA1416
+ });
+ }
else
{
Console.OutputEncoding = Encoding.UTF8;
- builder.ConfigureServices(services => services.AddSingleton());
+ builder.ConfigureServices(services => services
+ .AddSingleton()
+ .AddSingleton(new ServiceType(ServiceTypes.ConsoleApplication)));
+
builder.UseConsoleLifetime();
return builder;
}
@@ -96,7 +128,7 @@ public CustomWindowsServiceLifetime(
public void ServiceStartupCompleted()
{
- ApplicationLifetime.ApplicationStarted.Register(() => _started.Set());
+ ApplicationLifetime.ApplicationStarted.Register(_started.Set);
}
public new async Task WaitForStartAsync(CancellationToken cancellationToken)
@@ -133,4 +165,71 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
}
+
+ [SupportedOSPlatform("linux")]
+ public class CustomSystemDServiceLifetime : ILifetime, IHostLifetime, IDisposable
+ {
+ private readonly CancellationTokenSource _started = new();
+ private readonly ISystemdNotifier _systemdNotifier;
+ public IHostApplicationLifetime ApplicationLifetime { get; init; }
+
+ private CancellationTokenRegistration _applicationStartedRegistration;
+ private CancellationTokenRegistration _applicationStoppingRegistration;
+ private PosixSignalRegistration? _sigTermRegistration;
+
+ public CustomSystemDServiceLifetime(
+ IHostApplicationLifetime applicationLifetime,
+ ISystemdNotifier systemdNotifier)
+ {
+ ApplicationLifetime = applicationLifetime;
+ _systemdNotifier = systemdNotifier;
+ }
+
+ public void ServiceStartupCompleted() => _started.Cancel();
+
+ public Task WaitForStartAsync(CancellationToken cancellationToken)
+ {
+ _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(OnApplicationStarted);
+ _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(OnApplicationStopping);
+
+ RegisterShutdownHandlers();
+
+ return Task.CompletedTask;
+ }
+
+ private void OnApplicationStarted()
+ {
+ var cts = CancellationTokenSource.CreateLinkedTokenSource(_started.Token, ApplicationLifetime.ApplicationStopping);
+
+ cts.Token.Register(() =>
+ {
+ _systemdNotifier.Notify(ServiceState.Stopping);
+ });
+ }
+
+ private void OnApplicationStopping() => _systemdNotifier.Notify(ServiceState.Stopping);
+
+ public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+ private void RegisterShutdownHandlers() => _sigTermRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, HandlePosixSignal);
+
+ private void HandlePosixSignal(PosixSignalContext context)
+ {
+ Debug.Assert(context.Signal == PosixSignal.SIGTERM);
+
+ context.Cancel = true;
+ ApplicationLifetime.StopApplication();
+ }
+
+ private void UnregisterShutdownHandlers() => _sigTermRegistration?.Dispose();
+
+ public void Dispose()
+ {
+ _started.Cancel();
+
+ UnregisterShutdownHandlers();
+ _applicationStartedRegistration.Dispose();
+ _applicationStoppingRegistration.Dispose();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/fiskaltrust.Launcher/Helpers/ProcessHelper.cs b/src/fiskaltrust.Launcher/Helpers/ProcessHelper.cs
index 1e1cb9c3..4fbeef36 100644
--- a/src/fiskaltrust.Launcher/Helpers/ProcessHelper.cs
+++ b/src/fiskaltrust.Launcher/Helpers/ProcessHelper.cs
@@ -7,9 +7,9 @@ namespace fiskaltrust.Launcher.Helpers;
public static class ProcessHelper
{
public static async Task<(int exitCode, string output)> RunProcess(
- string fileName,
- IEnumerable arguments,
- LogEventLevel logLevel = LogEventLevel.Information)
+ string fileName,
+ IEnumerable arguments,
+ LogEventLevel? logLevel = LogEventLevel.Information)
{
var process = new Process
{
@@ -30,7 +30,7 @@ public static class ProcessHelper
var stdOut = await process.StandardOutput.ReadToEndAsync();
if (!string.IsNullOrEmpty(stdOut))
{
- Log.Write(logLevel, stdOut);
+ if (logLevel is not null) { Log.Write(logLevel.Value, stdOut); }
}
var stdErr = await process.StandardError.ReadToEndAsync();
diff --git a/src/fiskaltrust.Launcher/ProcessHost/ProcessHostMonarch.cs b/src/fiskaltrust.Launcher/ProcessHost/ProcessHostMonarch.cs
index e1c0768a..64f521a0 100644
--- a/src/fiskaltrust.Launcher/ProcessHost/ProcessHostMonarch.cs
+++ b/src/fiskaltrust.Launcher/ProcessHost/ProcessHostMonarch.cs
@@ -45,11 +45,6 @@ public ProcessHostMonarch(ILogger logger, LauncherConfigurat
_stopped = new TaskCompletionSource();
_started = new TaskCompletionSource();
-
- // if (Debugger.IsAttached)
- // {
- // _process.StartInfo.Arguments += " --debugging";
- // }
}
private void Setup()
@@ -61,11 +56,11 @@ private void Setup()
UseShellExecute = false,
FileName = _launcherExecutablePath.Path,
CreateNoWindow = false,
- Arguments = string.Join(" ", new string[] {
+ Arguments = string.Join(" ", [
"host",
"--plebeian-configuration", $"\"{Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(new PlebeianConfiguration { PackageType = _packageType, PackageId = _packageConfiguration.Id }.Serialize()))}\"",
"--launcher-configuration", $"\"{Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(_launcherConfiguration.Serialize()))}\"",
- }),
+ ]),
RedirectStandardInput = true,
RedirectStandardError = true,
RedirectStandardOutput = true
@@ -75,6 +70,11 @@ private void Setup()
_process.OutputDataReceived += ReceiveStdOut;
_process.ErrorDataReceived += ReceiveStdOut;
+
+ // if (Debugger.IsAttached && _packageType == PackageType.Helper)
+ // {
+ // _process.StartInfo.Arguments += " --debugging";
+ // }
}
private void ReceiveStdOut(object sender, DataReceivedEventArgs e)
diff --git a/src/fiskaltrust.Launcher/Program.cs b/src/fiskaltrust.Launcher/Program.cs
index bd5ab77b..35141f1a 100644
--- a/src/fiskaltrust.Launcher/Program.cs
+++ b/src/fiskaltrust.Launcher/Program.cs
@@ -37,7 +37,7 @@
if (!args.Any())
{
- args = new[] { runCommand.Name };
+ args = [runCommand.Name];
}
var subArguments = new SubArguments(args.SkipWhile(a => a != "--").Skip(1));
diff --git a/src/fiskaltrust.Launcher/ServiceInstallation/LinuxSystemD.cs b/src/fiskaltrust.Launcher/ServiceInstallation/LinuxSystemD.cs
index 97ccb82e..50ba5fb4 100644
--- a/src/fiskaltrust.Launcher/ServiceInstallation/LinuxSystemD.cs
+++ b/src/fiskaltrust.Launcher/ServiceInstallation/LinuxSystemD.cs
@@ -15,49 +15,53 @@ public LinuxSystemD(string? serviceName, LauncherExecutablePath launcherExecutab
public override async Task InstallService(string commandArgs, string? displayName, bool delayedStart = false)
{
- if (!await IsSystemd())
+ if (!await IdSystemdAvailable())
{
+ Log.Error("Systemd is not running on this machine. No service installation is possible.");
+ return -1;
+ }
+
+ if (await IsSystemdServiceInstalled(_serviceName))
+ {
+ Log.Error("Service is already installed and cannot be installed twice for one cashbox.");
return -1;
}
Log.Information("Installing service via systemd.");
var serviceFileContent = GetServiceFileContent(displayName ?? "Service installation of fiskaltrust launcher.", commandArgs);
var serviceFilePath = Path.Combine(_servicePath, $"{_serviceName}.service");
await File.AppendAllLinesAsync(serviceFilePath, serviceFileContent).ConfigureAwait(false);
- await ProcessHelper.RunProcess("systemctl", new[] { "daemon-reload" });
- Log.Information("Starting service.");
- await ProcessHelper.RunProcess("systemctl", new[] { "start", _serviceName });
- Log.Information("Enable service.");
- return (await ProcessHelper.RunProcess("systemctl", new[] { "enable", _serviceName, "-q" })).exitCode;
+ await ProcessHelper.RunProcess("systemctl", ["daemon-reload"]);
+ Log.Information("Starting systemd service.");
+ await ProcessHelper.RunProcess("systemctl", ["start", _serviceName]);
+ Log.Information("Enabling systemd service.");
+ return (await ProcessHelper.RunProcess("systemctl", ["enable", _serviceName, "-q"])).exitCode;
}
public override async Task UninstallService()
{
- if (!await IsSystemd())
+ if (!await IdSystemdAvailable())
{
+ Log.Error("Systemd is not running on this machine. No service uninstallation is possible.");
return -1;
}
- Log.Information("Stop service on systemd.");
- await ProcessHelper.RunProcess("systemctl", new[] { "stop ", _serviceName });
- Log.Information("Disable service.");
- await ProcessHelper.RunProcess("systemctl", new[] { "disable ", _serviceName, "-q" });
- Log.Information("Remove service.");
- var serviceFilePath = Path.Combine(_servicePath, $"{_serviceName}.service");
- await ProcessHelper.RunProcess("rm", new[] { serviceFilePath });
- Log.Information("Reload daemon.");
- await ProcessHelper.RunProcess("systemctl", new[] { "daemon-reload" });
- Log.Information("Reset failed.");
- return (await ProcessHelper.RunProcess("systemctl", new[] { "reset-failed" })).exitCode;
- }
- private static async Task IsSystemd()
- {
- var (exitCode, output) = await ProcessHelper.RunProcess("ps", new[] { "--no-headers", "-o", "comm", "1" });
- if (exitCode != 0 && output.Contains("systemd"))
+ if (!await IsSystemdServiceInstalled(_serviceName))
{
- Log.Error("Service installation works only for systemd setup.");
- return false;
+ Log.Error("Service is not installed!");
+ return -1;
}
- return true;
+
+ Log.Information("Stoppig systemd service.");
+ await ProcessHelper.RunProcess("systemctl", ["stop ", _serviceName]);
+ Log.Information("Disabling systemd service.");
+ await ProcessHelper.RunProcess("systemctl", ["disable ", _serviceName, "-q"]);
+ Log.Information("Removing systemd service.");
+ var serviceFilePath = Path.Combine(_servicePath, $"{_serviceName}.service");
+ await ProcessHelper.RunProcess("rm", [serviceFilePath]);
+ Log.Information("Reloading systemd daemon.");
+ await ProcessHelper.RunProcess("systemctl", ["daemon-reload"]);
+ Log.Information("Reseting state for failed systemd units.");
+ return (await ProcessHelper.RunProcess("systemctl", ["reset-failed"])).exitCode;
}
private string[] GetServiceFileContent(string serviceDescription, string commandArgs)
@@ -65,18 +69,41 @@ private string[] GetServiceFileContent(string serviceDescription, string command
var processPath = _launcherExecutablePath.Path;
var command = $"{processPath} {commandArgs}";
- return new[]
- {
+
+ return [
"[Unit]",
$"Description=\"{serviceDescription}\"",
"",
"[Service]",
- "Type=simple",
- $"ExecStart=\"{command}\"",
+ "Type=notify",
+ $"ExecStart={command}",
+ $"WorkingDirectory={Path.GetDirectoryName(_launcherExecutablePath.Path)}",
"",
"[Install]",
"WantedBy = multi-user.target"
- };
+ ];
+ }
+
+ private static async Task IdSystemdAvailable()
+ {
+ var (exitCode, output) = await ProcessHelper.RunProcess("ps", ["--no-headers", "-o", "comm", "1"], logLevel: null);
+
+ if (exitCode != 0 && output.Contains("systemd"))
+ {
+ Log.Error("Service installation works only for systemd setup.");
+ return false;
+ }
+ return true;
+ }
+
+ private static async Task IsSystemdServiceInstalled(string serviceName)
+ {
+ var (exitCode, _) = await ProcessHelper.RunProcess("systemctl", [$"status {serviceName}"], logLevel: null);
+ if (exitCode == 4)
+ {
+ return false;
+ }
+ return true;
}
}
}
diff --git a/src/fiskaltrust.Launcher/fiskaltrust.Launcher.csproj b/src/fiskaltrust.Launcher/fiskaltrust.Launcher.csproj
index b213e97a..8deb18d9 100644
--- a/src/fiskaltrust.Launcher/fiskaltrust.Launcher.csproj
+++ b/src/fiskaltrust.Launcher/fiskaltrust.Launcher.csproj
@@ -9,12 +9,12 @@
- $(DefineConstants);EnableSelfUpdate
+ $(DefineConstants);EnableSelfUpdate
-
-
+
+
@@ -22,22 +22,24 @@
-
+
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/test/fiskaltrust.Launcher.IntegrationTest/fiskaltrust.Launcher.IntegrationTest.csproj b/test/fiskaltrust.Launcher.IntegrationTest/fiskaltrust.Launcher.IntegrationTest.csproj
index 05cae015..48d35d7c 100644
--- a/test/fiskaltrust.Launcher.IntegrationTest/fiskaltrust.Launcher.IntegrationTest.csproj
+++ b/test/fiskaltrust.Launcher.IntegrationTest/fiskaltrust.Launcher.IntegrationTest.csproj
@@ -10,10 +10,10 @@
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -25,15 +25,19 @@
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/test/fiskaltrust.Launcher.UnitTest/fiskaltrust.Launcher.UnitTest.csproj b/test/fiskaltrust.Launcher.UnitTest/fiskaltrust.Launcher.UnitTest.csproj
index 8eacef9e..8f1987a0 100644
--- a/test/fiskaltrust.Launcher.UnitTest/fiskaltrust.Launcher.UnitTest.csproj
+++ b/test/fiskaltrust.Launcher.UnitTest/fiskaltrust.Launcher.UnitTest.csproj
@@ -11,10 +11,10 @@
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -25,15 +25,19 @@
-
+
-
+
-
+
-
+
\ No newline at end of file