();
+
+ string subject = $"[Startup] Fuchs Intranet started on {Environment.MachineName}";
+ string html = $"Fuchs Intranet startup probe.
" +
+ $"UTC: {DateTimeOffset.UtcNow:O}
Machine: {Environment.MachineName}
";
+
+ bool sent = await comService.SendEmailAsync(
+ "startup_probe",
+ subject,
+ html,
+ _settings.StartupEmailRecipient,
+ _settings.StartupEmailRecipientName,
+ attachments: null);
+
+ if (!sent)
+ {
+ _logger.LogWarning("Startup mailer check failed - probe email was not accepted by IComService.");
+ return false;
+ }
+
+ _logger.LogInformation("Startup mailer check succeeded.");
+ return true;
+ }
+}
diff --git a/Fuchs/Services/StartupSelfTestSettings.cs b/Fuchs/Services/StartupSelfTestSettings.cs
new file mode 100644
index 0000000..5c54f5d
--- /dev/null
+++ b/Fuchs/Services/StartupSelfTestSettings.cs
@@ -0,0 +1,47 @@
+namespace Fuchs.Services;
+
+///
+/// Optional one-shot startup self-test settings, bound from "Fuchs:StartupChecks".
+/// Disabled by default to avoid accidental startup emails in production.
+///
+public class StartupSelfTestSettings
+{
+ public bool Enabled { get; set; } = false;
+
+ ///
+ /// When enabled, verifies Key Vault access by reading one managed secret.
+ ///
+ public bool CheckKeyVault { get; set; } = true;
+
+ ///
+ /// When enabled, verifies SQL database connectivity by executing SELECT 1.
+ ///
+ public bool CheckDatabase { get; set; } = true;
+
+ ///
+ /// When enabled, verifies MFR API connectivity using the configured client credentials.
+ ///
+ public bool CheckMfr { get; set; } = true;
+
+ ///
+ /// When enabled, verifies that a Spire.PDF license string is configured (present and
+ /// non-empty) and that Spire.PDF is actually licensed (not running in evaluation mode).
+ ///
+ public bool CheckPdfLicense { get; set; } = true;
+
+ ///
+ /// When enabled, sends a startup probe email via .
+ /// This applies to all environments, including Production.
+ ///
+ public bool SendStartupEmail { get; set; } = false;
+
+ ///
+ /// Recipient address for startup probe emails.
+ ///
+ public string StartupEmailRecipient { get; set; } = "";
+
+ ///
+ /// Recipient display name for startup probe emails.
+ ///
+ public string StartupEmailRecipientName { get; set; } = "Startup Monitor";
+}
diff --git a/Fuchs/appsettings.Development.json b/Fuchs/appsettings.Development.json
index d22416c..5cf9ec1 100644
--- a/Fuchs/appsettings.Development.json
+++ b/Fuchs/appsettings.Development.json
@@ -6,15 +6,24 @@
},
"Logging": {
"LogLevel": {
- "Default": "Warning",
- "Microsoft.AspNetCore": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Default": "Debug",
+ "Microsoft.AspNetCore": "Debug",
+ "Microsoft.Hosting.Lifetime": "Debug",
+ "fds": "Debug",
+ "Fuchs.Controllers": "Debug"
}
},
"Fuchs": {
"FDS_Intranet_DebugState": true,
"DevAutoLogin": true,
"DevAutoLoginEmail": "info@processweb.de",
+ "StartupChecks": {
+ "Enabled": true,
+ "CheckKeyVault": false,
+ "CheckDatabase": false,
+ "CheckMfr": false,
+ "CheckPdfLicense": true
+ },
"Email": {
"OverrideRecipient": "service@emails.processweb.de"
},
diff --git a/Fuchs/appsettings.json b/Fuchs/appsettings.json
index 03dbbc9..d546cb8 100644
--- a/Fuchs/appsettings.json
+++ b/Fuchs/appsettings.json
@@ -13,7 +13,8 @@
"Fuchs--fuchs-captcha-TOTP",
"Fuchs--fuchs-intranet-TOTP",
"Fds--MFR-UserName",
- "Fds--MFR-Password"
+ "Fds--MFR-Password",
+ "SpirePdf-License"
]
},
"Logging": {
@@ -23,6 +24,7 @@
}
},
"AllowedHosts": "*",
+ "SpirePdf_License": "MANAGED_BY_KEYVAULT",
"ConnectionStrings": {
"fuchs_fds_ConnectionString": "Data Source=MSSQL4.NBG4.DOMAINXYZ.DE,10439;Initial Catalog=site_fuchs_dev;Persist Security Info=False;TrustServerCertificate=true;Encrypt=true;User ID={username};password='{password}';Connect Timeout=60;Load Balance Timeout=240;Max Pool Size=500;",
"fuchs_fds_username": "MANAGED_BY_KEYVAULT",
@@ -40,13 +42,22 @@
"SMS_APIKey": "MANAGED_BY_KEYVAULT",
"Mailer": {
"BaseUrl": "https://api.processweb.de",
- "AccountId": "",
+ "AccountId": "82d87114-c8c3-4d33-95e5-4c781a9229ab",
"Token": "MANAGED_BY_KEYVAULT",
"Enabled": false
},
"Email": {
"OverrideRecipient": ""
},
+ "StartupChecks": {
+ "Enabled": false,
+ "CheckKeyVault": true,
+ "CheckDatabase": true,
+ "CheckMfr": true,
+ "SendStartupEmail": true,
+ "StartupEmailRecipient": "",
+ "StartupEmailRecipientName": "Startup Monitor"
+ },
"AzureStorage": {
"Enabled": false,
"InvoiceContainer": "fuchs-invoices",
diff --git a/Fuchs/code/FuchsPdf.cs b/Fuchs/code/FuchsPdf.cs
index 0ddd3ed..e2075a8 100644
--- a/Fuchs/code/FuchsPdf.cs
+++ b/Fuchs/code/FuchsPdf.cs
@@ -22,8 +22,23 @@ public static class FuchsPdf
public const string ProjectAbbreviation = "fuchs";
// ── Spire license ─────────────────────────────────────────────────────────
- public static void SetLicense() =>
- Spire.License.LicenseProvider.SetLicenseKey(
+ ///
+ /// Applies the Spire.PDF license. The key is supplied by the caller from the
+ /// SpirePdf-License managed secret (config key SpirePdf_License);
+ /// when no key is provided the embedded fallback key is used so PDF rendering
+ /// still works in local/dev setups without Key Vault access.
+ ///
+ public static void SetLicense(string? licenseKey = null) =>
+ Spire.License.LicenseProvider.SetLicenseKey(ResolveLicenseKey(licenseKey));
+
+ ///
+ /// Chooses the effective Spire license key: the managed-secret value when present,
+ /// otherwise the embedded fallback. Pure/side-effect-free for testability.
+ ///
+ internal static string ResolveLicenseKey(string? licenseKey) =>
+ string.IsNullOrWhiteSpace(licenseKey) ? EmbeddedLicenseKey : licenseKey;
+
+ private const string EmbeddedLicenseKey =
"I+ztXu/77JVCXwEAwVQwRISgL4qlo1lOxO6csGdd02iJsOnMzEkqjhRx6oJ5rw5fgaF5wUf83LWMWwLE8PNc" +
"/ZGUZIa8mTx9ovjM9fK2+xLk/VC3s555Qhd5+PLfgxIEsp4r6lw03P7YPvD6pvM745VQg0dd8thRoznmkWrkUf" +
"/2/MiUZyUyVrH+qyEZgkniqpuDdqoaUNx1RfsK6TyiKKB7nsiqDy9xrduuYCMgOg1wii3aU+anA/pHUYh/jMO0" +
@@ -41,7 +56,7 @@ public static class FuchsPdf
"uUg5LlJmPPXkTKHQJ/CM6EQkqIS4Foz7pBaaYRBgEz/zDujxbYUGN6LaJiANung4Zyl6k5arhHdCalRDe29avN1o" +
"vxe/5tUHQQDxq+yQ1cNChPJTFHR1bKKu0T7SW7p19qH5850rXcjtzK4+6zGYXq8HItH6UNiev27o9VUoKTv+XZiD" +
"27YE33vdwQHh5Kdc8CMMo+uaTI11uLBirUH63Na2oBkCGJjJzQk8Gc5NQs7+2DptJ/rNlOhwb/czZLB6OjH+vNCy" +
- "HZBCGPd17rIW16JQzgWv+OBI9DbD7pXYzDyF++IrBiRKBPNKCTwg3trm89J4zWeGW80bFtD0QnIcArA==");
+ "HZBCGPd17rIW16JQzgWv+OBI9DbD7pXYzDyF++IrBiRKBPNKCTwg3trm89J4zWeGW80bFtD0QnIcArA==";
// ── Colors ────────────────────────────────────────────────────────────────
private static readonly Color FuchsGray = Color.FromRgb(128, 128, 128);
@@ -193,21 +208,36 @@ public static class FuchsPdf
DefineStyles_Letter(doc, Array.Empty