diff --git a/deploy/polytrader.service b/deploy/polytrader.service
index 06ddcf6..85d6dc2 100644
--- a/deploy/polytrader.service
+++ b/deploy/polytrader.service
@@ -29,6 +29,11 @@ WorkingDirectory=/opt/polytrader
ExecStart=/opt/polytrader/PolyTrader.App.Avalonia --headless
Restart=on-failure
RestartSec=10
+# Exit 78 (EX_CONFIG) meldet einen Konfigurationsfehler - etwa einen fehlenden
+# Connection-String. Ein Neustart repariert das nicht, er verdeckt es nur: der Dienst
+# liefe im Zehn-Sekunden-Takt an und wieder aus, und im Journal saehe man endlose
+# Startversuche statt der eigentlichen Ursache. Deshalb hier bewusst nicht neu starten.
+RestartPreventExitStatus=78
# Absturz vom sauberen Beenden unterscheiden: SIGTERM statt SIGKILL, mit Frist fuer den
# geordneten Shutdown-Pfad (offene Positionen, Watchdog-"stopped_graceful", DB-Flush).
diff --git a/src/PolyTrader.App.Avalonia/Program.cs b/src/PolyTrader.App.Avalonia/Program.cs
index 306f7aa..61ffdda 100644
--- a/src/PolyTrader.App.Avalonia/Program.cs
+++ b/src/PolyTrader.App.Avalonia/Program.cs
@@ -35,6 +35,16 @@ namespace PolyTrader.App.Avalonia
{
private const string ServerSettingsPath = "server_settings.xml";
+ ///
+ /// Rueckgabewert bei Konfigurationsfehlern. 78 ist EX_CONFIG aus den BSD-sysexits und
+ /// damit ein etablierter Wert fuer "Konfiguration unbrauchbar". Wichtig fuer den
+ /// Dienstbetrieb: deploy/polytrader.service traegt ihn in
+ /// RestartPreventExitStatus ein, damit systemd bei einem Konfigurationsfehler
+ /// NICHT im Zehn-Sekunden-Takt neu startet - ein fehlender Connection-String repariert
+ /// sich durch Neustarts nicht.
+ ///
+ private const int ExitCodeConfigurationError = 78;
+
[STAThread]
public static int Main(string[] args)
{
@@ -49,6 +59,33 @@ namespace PolyTrader.App.Avalonia
var dcOptions = LoadDeploymentcenterOptions();
+ // Ohne Datenbankverbindung kann die Anwendung nichts Sinnvolles tun - jedes Repository
+ // haengt daran. Bis zum 26.08.2026 lief sie trotzdem los und starb erst tief im
+ // DI-Aufbau mit einer ArgumentException samt 50 Zeilen Stacktrace ("The string argument
+ // 'connectionString' cannot be empty"). Auf einem Server ist das doppelt schlecht:
+ // Der Betreiber sieht nicht, was fehlt, und systemd startet den Dienst wegen
+ // Restart=on-failure endlos neu. Deshalb hier frueh und verstaendlich abbrechen.
+ var connectionString = LoadDatabaseConnectionString();
+ if (string.IsNullOrWhiteSpace(connectionString))
+ {
+ var err = Console.Error;
+ err.WriteLine("FEHLER: Keine Datenbankverbindung konfiguriert.");
+ err.WriteLine();
+ err.WriteLine("Erwartet wird 'Database:MySqlConnectionString' in appsettings.Local.json");
+ err.WriteLine($"neben der Programmdatei ({AppContext.BaseDirectory}).");
+ err.WriteLine();
+ err.WriteLine("Beispiel:");
+ err.WriteLine("{");
+ err.WriteLine(" \"Database\": {");
+ err.WriteLine(" \"MySqlConnectionString\": \"Server=HOST;Port=3306;Database=polytrader;User ID=BENUTZER;Password=PASSWORT;\"");
+ err.WriteLine(" }");
+ err.WriteLine("}");
+ err.WriteLine();
+ err.WriteLine("Die Datei ist bewusst nicht versioniert (Zugangsdaten). Das Schema legt");
+ err.WriteLine("'dotnet ef database update' an - die Anwendung migriert nicht selbst.");
+ return ExitCodeConfigurationError;
+ }
+
// Konstruktionsprüfung (--smoke-ui) testet Fensteraufbau, keine Lizenzlogik - dort
// bewusst ALLE Module registrieren, sonst würde die Prüfung auf einem Build-Server
// (nie lizenziert) still einen Großteil ihrer Abdeckung verlieren.
@@ -113,6 +150,22 @@ namespace PolyTrader.App.Avalonia
return options;
}
+ ///
+ /// Liest den Connection-String, bevor der Generic Host existiert - fuer die fruehe
+ /// Konfigurationspruefung in . Liest dieselben Dateien und denselben
+ /// Schluessel wie der Host spaeter, damit beide nicht auseinanderlaufen koennen.
+ ///
+ internal static string LoadDatabaseConnectionString()
+ {
+ var config = new ConfigurationBuilder()
+ .SetBasePath(AppContext.BaseDirectory)
+ .AddJsonFile("appsettings.json", optional: true)
+ .AddJsonFile("appsettings.Local.json", optional: true)
+ .Build();
+
+ return config["Database:MySqlConnectionString"] ?? string.Empty;
+ }
+
///
/// Baut den Anwendungs-Host: Konfiguration, Master-Key, Persistenz, Core-Dienste und die
/// aktiven Module. Kennt die Oberfläche nicht.
diff --git a/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs
index b1d1fac..d038b3e 100644
--- a/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Core.Configuration;
@@ -17,6 +17,18 @@ namespace PolyTrader.Core.DependencyInjection
public static IServiceCollection AddCorePersistence(this IServiceCollection services, DatabaseOptions options)
{
var conn = options.MySqlConnectionString;
+
+ // Zweite Verteidigungslinie. Der Regelfall wird frueh in Program.Main abgefangen
+ // (verstaendliche Meldung + Exit 78); wer diese Methode direkt aufruft, bekam bisher
+ // erst tief im DI-Aufbau eine ArgumentException aus dem EF-Kern, der man den
+ // eigentlichen Grund nicht ansieht.
+ if (string.IsNullOrWhiteSpace(conn))
+ {
+ throw new InvalidOperationException(
+ "Kein MySQL-Connection-String konfiguriert. Erwartet wird " +
+ "'Database:MySqlConnectionString' in appsettings.Local.json neben der Programmdatei.");
+ }
+
services.AddDbContextFactory(o => o.UseMySql(conn, DatabaseServerVersion.Value));
services.AddSingleton();