Author SHA1 Message Date
GizzlerandCursor 0a449727ad Fix invalid CertificateCheckUrl column errors on existing databases.
Auto-migrate SonicContainer schema on startup, rename legacy typo columns, and use fallback SQL when the column is not yet present.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 13:24:20 +02:00
GizzlerandCursor 76e280d809 Simplify actions to one deploy button and per-row restart menu.
Keep only the yellow Austauschen + Neustart button; move manual restart into a three-dot row menu and probe TLS status after restart.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 13:21:35 +02:00
GizzlerandCursor 58b7826162 Add live TLS certificate probing and improve restart error handling.
Configure CertificateCheckUrl per container for curl-like TLS checks, classify Sonic permission errors, and extend setup wizard for container management.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 13:16:21 +02:00
GizzlerandCursor d20e0df618 Simplify UI labels and dialogs: drop clutter, show Sonic as connected/not connected.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 08:19:15 +02:00
GizzlerandCursor f98daec656 Show target certificate validity after PFX exchange.
Reuse the source PFX password for path probes and fall back to the loaded certificate expiry for successfully exchanged targets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 08:12:45 +02:00
GizzlerandCursor 89a694ba21 Add edit and delete for systems, containers, and paths in settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 08:12:15 +02:00
GizzlerandCursor aac71ec2cd Allow soft-deleting certificate paths from settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:55:29 +02:00
GizzlerandCursor b1e1789fd1 Run container restart after certificate copy so exchange takes effect.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:45:43 +02:00
GizzlerandCursor a5557b44fa Fix MfApi restart invoke by passing empty param arrays instead of null.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:41:23 +02:00
GizzlerandCursor 38fa04b540 Copy bundled Runtime\bin JRE to output so MfApi finds java.exe.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:39:34 +02:00
Mike Gisler 3c100877be runtime push 2026-07-29 09:35:53 +02:00
GizzlerandCursor dcceb7b719 Fix Java 8 resolution for MfApi restart, refresh targets after settings, and copy errors to clipboard.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:34:04 +02:00
GizzlerandCursor f584fd68fe Improve certificate path probing with demo UNC target and remove Sonic container scan.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:14:25 +02:00
GizzlerandCursor f7e9bd43e2 Add Sonic container scan with manual multi-select after system setup.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 09:03:01 +02:00
GizzlerandCursor 09b31a4ac9 Fix settings add buttons and verify catalog inserts against SQL.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 08:58:16 +02:00
GizzlerandCursor ed6777ffed Expand settings UI to manage systems, containers, and certificate paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 08:53:13 +02:00
GizzlerandCursor 121a428755 Include Always Encrypted PFX for local AE certificate install.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 08:30:23 +02:00
GizzlerandCursor e45ced8b73 Add complete seed, Always Encrypted cert package, and TEST system DE-Test.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 08:30:12 +02:00
GizzlerandCursor dcfcb5289d Simplify DB to six tables and load credentials via Always Encrypted SonicCredential.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 08:23:55 +02:00
Mike Gisler 4d19873776 big changes 2026-07-28 08:03:12 +02:00
GizzlerandCursor c73f53d740 Slim MfApi stack to restart-only path.
Drop ping/list discovery, WinRM scripts, and heavy Java probing so only KnownContainers + MBean restart remain.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 12:32:05 +02:00
GizzlerandCursor ade63d0e43 Remove offline sample targets; load containers from appsettings KnownContainers.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 12:20:26 +02:00
GizzlerandCursor d1b95bcb9d Keep original UI; drop unused deploy/XApi/WinRM/TLS/SQL paths.
Form1 design unchanged; only MfApi restart and certificate recognition remain active.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 11:09:46 +02:00
GizzlerandCursor 7a9cdb6ecf Force new MfApi tool build and MBean stop restart path.
Reject stale SonicMfContainerTool.class, print ToolVersion, prefer tool on classpath.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 10:22:39 +02:00
Gizzler ada6b6ca83 Fallback MfApi restart via MBean stop when IAgentProxy is unbounded.
Remote JMS clients cannot call IAgentProxy.restart; use stop/restart invoke like SMC.
2026-07-24 10:19:29 +02:00
GizzlerandCursor a89a7ad4b6 Fix MfApi ClassNotFound by passing Java classpath via ArgumentList.
Use ct-ZADBService and explicit MfClientLibPath so SMC restart finds client JARs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 10:16:01 +02:00
1777 changed files with 380007 additions and 5575 deletions
+3
View File
@@ -28,6 +28,9 @@ x86/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
# Bundled JRE for MfApi
!ZA.CoreService.ESBCertificateManager/Runtime/bin/
!ZA.CoreService.ESBCertificateManager/Runtime/bin/**
[Oo]bj/
[Oo]ut/
[Ll]og/
@@ -7,28 +7,102 @@ public static class AppSettingsLoader
{
public static AppSettings Load()
{
// Zuerst Output-Verzeichnis, dann aktuelles Arbeitsverzeichnis (z.B. VS Debug).
string basePath = Directory.Exists(AppContext.BaseDirectory)
? AppContext.BaseDirectory
: Directory.GetCurrentDirectory();
string settingsPath = Path.Combine(basePath, "appsettings.json");
if (!File.Exists(settingsPath))
{
string cwdPath = Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json");
string cwdPath = Path.Combine(
Directory.GetCurrentDirectory(),
"appsettings.json");
if (File.Exists(cwdPath))
{
basePath = Directory.GetCurrentDirectory();
}
else
{
throw new FileNotFoundException(
"Die Datei appsettings.json wurde nicht gefunden.",
settingsPath);
}
}
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddJsonFile(
"appsettings.json",
optional: false,
reloadOnChange: false)
.Build();
AppSettings settings = new();
configuration.Bind(settings);
Validate(settings);
return settings;
}
private static void Validate(
AppSettings settings)
{
if (string.IsNullOrWhiteSpace(settings.EnvironmentCode))
{
throw new InvalidOperationException(
"EnvironmentCode fehlt in appsettings.json.");
}
settings.EnvironmentCode =
settings.EnvironmentCode
.Trim()
.ToUpperInvariant();
string[] allowedEnvironmentCodes =
[
"DEV",
"TEST",
"PROD"
];
if (!allowedEnvironmentCodes.Contains(
settings.EnvironmentCode,
StringComparer.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"EnvironmentCode '{settings.EnvironmentCode}' ist ungültig. " +
"Erlaubt sind DEV, TEST und PROD.");
}
if (string.IsNullOrWhiteSpace(
settings.Database.ConnectionString))
{
throw new InvalidOperationException(
"Database:ConnectionString fehlt in appsettings.json.");
}
if (string.IsNullOrWhiteSpace(
settings.Runtime.JavaExecutablePath))
{
throw new InvalidOperationException(
"Runtime:JavaExecutablePath fehlt in appsettings.json.");
}
if (string.IsNullOrWhiteSpace(
settings.Runtime.SonicClientLibraryPath))
{
throw new InvalidOperationException(
"Runtime:SonicClientLibraryPath fehlt in appsettings.json.");
}
if (string.IsNullOrWhiteSpace(
settings.AlwaysEncrypted.CertificateThumbprint))
{
throw new InvalidOperationException(
"AlwaysEncrypted:CertificateThumbprint fehlt.");
}
}
}
@@ -1,9 +0,0 @@
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Data;
public interface ITargetRepository
{
Task<IReadOnlyList<DeploymentTarget>> GetActiveTargetsAsync(CancellationToken cancellationToken = default);
string SourceDescription { get; }
}
@@ -1,46 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Data;
public sealed class JsonTargetRepository : ITargetRepository
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
Converters = { new JsonStringEnumConverter() }
};
private readonly string _filePath;
public JsonTargetRepository(string filePath)
{
_filePath = filePath;
}
public string SourceDescription => $"Offline-Sample ({Path.GetFileName(_filePath)})";
public async Task<IReadOnlyList<DeploymentTarget>> GetActiveTargetsAsync(
CancellationToken cancellationToken = default)
{
if (!File.Exists(_filePath))
{
throw new FileNotFoundException(
$"Sample-Zieldatei wurde nicht gefunden: {_filePath}",
_filePath);
}
await using FileStream stream = File.OpenRead(_filePath);
List<DeploymentTarget>? targets = await JsonSerializer.DeserializeAsync<List<DeploymentTarget>>(
stream,
JsonOptions,
cancellationToken);
return (targets ?? [])
.Where(t => t.IsActive)
.OrderBy(t => t.SortOrder)
.ThenBy(t => t.Name)
.ToList();
}
}
@@ -1,167 +0,0 @@
using Microsoft.Data.SqlClient;
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Data;
/// <summary>
/// Schreibt Deployment-Lauf-Ergebnisse in die SQL-Datenbank.
/// Tabellen: dbo.DeploymentRuns (GUID-PK) + dbo.DeploymentTargetResults.
/// Ist kein ConnectionString konfiguriert, werden alle Operationen still übersprungen.
/// </summary>
public sealed class SqlRunLogger
{
private readonly string? _connectionString;
public SqlRunLogger(string? connectionString)
{
_connectionString = string.IsNullOrWhiteSpace(connectionString)
? null
: connectionString;
}
public bool IsEnabled => _connectionString is not null;
/// <summary>
/// Legt einen neuen Lauf-Datensatz an.
/// </summary>
public async Task BeginRunAsync(
Guid runId,
DateTimeOffset startedAt,
string certificateFilePath,
string certificateFingerprint,
CancellationToken cancellationToken = default)
{
if (_connectionString is null) return;
const string sql = """
INSERT INTO dbo.DeploymentRuns
(Id, StartedAtUtc, SourceFile, SourceFingerprint, StartedBy, MachineName, OverallStatus)
VALUES
(@Id, @StartedAtUtc, @SourceFile, @SourceFingerprint, @StartedBy, @MachineName, N'Running');
""";
await using SqlConnection conn = new(_connectionString);
await conn.OpenAsync(cancellationToken);
await using SqlCommand cmd = new(sql, conn);
cmd.Parameters.AddWithValue("@Id", runId);
cmd.Parameters.AddWithValue("@StartedAtUtc", startedAt.UtcDateTime);
cmd.Parameters.AddWithValue("@SourceFile", Path.GetFileName(certificateFilePath));
cmd.Parameters.AddWithValue("@SourceFingerprint", certificateFingerprint);
cmd.Parameters.AddWithValue("@StartedBy", Environment.UserName);
cmd.Parameters.AddWithValue("@MachineName", Environment.MachineName);
await cmd.ExecuteNonQueryAsync(cancellationToken);
}
/// <summary>
/// Aktualisiert den Lauf-Datensatz mit Endzeitpunkt und Gesamtstatus.
/// </summary>
public async Task CompleteRunAsync(
Guid runId,
DateTimeOffset finishedAt,
bool overallSuccess,
int successCount,
int totalCount,
CancellationToken cancellationToken = default)
{
if (_connectionString is null) return;
string status = overallSuccess
? "Success"
: (successCount > 0 ? "PartialFailure" : "Failure");
const string sql = """
UPDATE dbo.DeploymentRuns
SET FinishedAtUtc = @FinishedAtUtc,
OverallStatus = @OverallStatus
WHERE Id = @Id;
""";
await using SqlConnection conn = new(_connectionString);
await conn.OpenAsync(cancellationToken);
await using SqlCommand cmd = new(sql, conn);
cmd.Parameters.AddWithValue("@Id", runId);
cmd.Parameters.AddWithValue("@FinishedAtUtc", finishedAt.UtcDateTime);
cmd.Parameters.AddWithValue("@OverallStatus", status);
await cmd.ExecuteNonQueryAsync(cancellationToken);
}
/// <summary>
/// Schreibt das Ergebnis eines einzelnen Deployment-Ziels.
/// </summary>
public async Task WriteTargetResultAsync(
Guid runId,
TargetStepResult step,
CancellationToken cancellationToken = default)
{
if (_connectionString is null) return;
string status = step.Success ? "Success" : "Failure";
const string sql = """
INSERT INTO dbo.DeploymentTargetResults
(DeploymentRunId, TargetId, TargetName,
StartedAtUtc, FinishedAtUtc,
CopySucceeded, RestartSucceeded, TlsSucceeded,
ObservedFingerprint, Status, ErrorMessage)
VALUES
(@RunId, @TargetId, @TargetName,
@StartedAtUtc, @FinishedAtUtc,
@CopySucceeded, @RestartSucceeded, @TlsSucceeded,
@ObservedFingerprint, @Status, @ErrorMessage);
""";
await using SqlConnection conn = new(_connectionString);
await conn.OpenAsync(cancellationToken);
await using SqlCommand cmd = new(sql, conn);
cmd.Parameters.AddWithValue("@RunId", runId);
cmd.Parameters.AddWithValue("@TargetId", step.TargetId);
cmd.Parameters.AddWithValue("@TargetName", step.TargetName);
cmd.Parameters.AddWithValue("@StartedAtUtc", step.StartedAt.UtcDateTime);
cmd.Parameters.AddWithValue("@FinishedAtUtc", step.FinishedAt.UtcDateTime);
cmd.Parameters.AddWithValue("@CopySucceeded", step.CopySucceeded);
cmd.Parameters.AddWithValue("@RestartSucceeded", step.RestartSucceeded);
cmd.Parameters.AddWithValue("@TlsSucceeded", step.TlsSucceeded);
cmd.Parameters.AddWithValue("@ObservedFingerprint", (object?)step.ObservedFingerprint ?? DBNull.Value);
cmd.Parameters.AddWithValue("@Status", status);
cmd.Parameters.AddWithValue("@ErrorMessage", step.Success ? DBNull.Value : (object?)(step.Detail ?? step.StatusText));
await cmd.ExecuteNonQueryAsync(cancellationToken);
}
/// <summary>
/// Persistiert ein vollständiges <see cref="DeploymentRunResult"/> in einem einzigen Aufruf.
/// </summary>
public async Task PersistRunResultAsync(
DeploymentRunResult runResult,
CancellationToken cancellationToken = default)
{
if (_connectionString is null) return;
await BeginRunAsync(
runResult.RunId,
runResult.StartedAt,
runResult.CertificateFilePath,
runResult.CertificateFingerprint,
cancellationToken);
foreach (TargetStepResult step in runResult.TargetResults)
{
await WriteTargetResultAsync(runResult.RunId, step, cancellationToken);
}
int successCount = runResult.TargetResults.Count(r => r.Success);
await CompleteRunAsync(
runResult.RunId,
runResult.FinishedAt,
runResult.OverallSuccess,
successCount,
runResult.TargetResults.Count,
cancellationToken);
}
}
@@ -1,111 +0,0 @@
using Microsoft.Data.SqlClient;
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Data;
public sealed class SqlTargetRepository : ITargetRepository
{
private readonly string _connectionString;
public SqlTargetRepository(string connectionString)
{
_connectionString = connectionString;
}
public string SourceDescription => "SQL Server (dbo.DeploymentTargets)";
public async Task<IReadOnlyList<DeploymentTarget>> GetActiveTargetsAsync(
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(_connectionString))
{
throw new InvalidOperationException("Es ist keine SQL-Verbindungszeichenfolge konfiguriert.");
}
const string sql = """
SELECT
Id,
Name,
Environment,
IsActive,
CertificateTargetPath,
CertificateFileName,
ContainerName,
RestartType,
RestartCommand,
RestartArguments,
RestartTimeoutSeconds,
SonicConnectionName,
XapiSourcePath,
TlsHost,
TlsPort,
TlsServerName,
ExpectedFingerprint,
SortOrder
FROM dbo.DeploymentTargets
WHERE IsActive = 1
ORDER BY SortOrder, Name;
""";
List<DeploymentTarget> targets = [];
await using SqlConnection connection = new(_connectionString);
await connection.OpenAsync(cancellationToken);
await using SqlCommand command = new(sql, connection);
await using SqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
int id = reader.GetOrdinal("Id");
int name = reader.GetOrdinal("Name");
int environment = reader.GetOrdinal("Environment");
int isActive = reader.GetOrdinal("IsActive");
int certificateTargetPath = reader.GetOrdinal("CertificateTargetPath");
int certificateFileName = reader.GetOrdinal("CertificateFileName");
int containerName = reader.GetOrdinal("ContainerName");
int restartType = reader.GetOrdinal("RestartType");
int restartCommand = reader.GetOrdinal("RestartCommand");
int restartArguments = reader.GetOrdinal("RestartArguments");
int restartTimeoutSeconds = reader.GetOrdinal("RestartTimeoutSeconds");
int sonicConnectionName = reader.GetOrdinal("SonicConnectionName");
int xapiSourcePath = reader.GetOrdinal("XapiSourcePath");
int tlsHost = reader.GetOrdinal("TlsHost");
int tlsPort = reader.GetOrdinal("TlsPort");
int tlsServerName = reader.GetOrdinal("TlsServerName");
int expectedFingerprint = reader.GetOrdinal("ExpectedFingerprint");
int sortOrder = reader.GetOrdinal("SortOrder");
while (await reader.ReadAsync(cancellationToken))
{
RestartType parsedRestartType = Enum.TryParse(
reader.GetString(restartType),
ignoreCase: true,
out RestartType parsed)
? parsed
: RestartType.None;
targets.Add(new DeploymentTarget
{
Id = reader.GetInt32(id),
Name = reader.GetString(name),
Environment = reader.GetString(environment),
IsActive = reader.GetBoolean(isActive),
TargetDirectory = reader.GetString(certificateTargetPath),
CertificateFileName = reader.GetString(certificateFileName),
ContainerName = reader.GetString(containerName),
RestartType = parsedRestartType,
RestartCommand = reader.IsDBNull(restartCommand) ? string.Empty : reader.GetString(restartCommand),
RestartArguments = reader.GetString(restartArguments),
RestartTimeoutSeconds = reader.GetInt32(restartTimeoutSeconds),
SonicConnectionName = reader.GetString(sonicConnectionName),
XapiSourcePath = reader.GetString(xapiSourcePath),
TlsHost = reader.GetString(tlsHost),
TlsPort = reader.IsDBNull(tlsPort) ? null : reader.GetInt32(tlsPort),
TlsServerName = reader.GetString(tlsServerName),
ExpectedFingerprint = reader.IsDBNull(expectedFingerprint) ? null : reader.GetString(expectedFingerprint),
SortOrder = reader.GetInt32(sortOrder)
});
}
return targets;
}
}
@@ -1,44 +0,0 @@
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Data;
public static class TargetRepositoryFactory
{
public static async Task<(ITargetRepository Repository, string LoadMessage)> CreateAsync(
AppSettings settings,
CancellationToken cancellationToken = default)
{
string samplePath = Path.GetFullPath(
Path.Combine(AppContext.BaseDirectory, settings.SampleTargetsPath));
if (settings.UseOfflineSampleData)
{
ITargetRepository jsonRepo = new JsonTargetRepository(samplePath);
_ = await jsonRepo.GetActiveTargetsAsync(cancellationToken);
return (jsonRepo, $"Ziele geladen aus {jsonRepo.SourceDescription}.");
}
if (string.IsNullOrWhiteSpace(settings.ConnectionString))
{
return OfflineSample(samplePath, "Keine SQL-Verbindung konfiguriert Offline-Sample wird verwendet.");
}
try
{
SqlTargetRepository sqlRepo = new(settings.ConnectionString);
_ = await sqlRepo.GetActiveTargetsAsync(cancellationToken);
return (sqlRepo, $"Ziele geladen aus {sqlRepo.SourceDescription}.");
}
catch (Exception ex)
{
return OfflineSample(
samplePath,
$"SQL nicht erreichbar ({ex.Message}). Offline-Sample wird verwendet.");
}
}
private static (ITargetRepository Repository, string LoadMessage) OfflineSample(
string samplePath,
string message)
=> (new JsonTargetRepository(samplePath), message);
}
@@ -1,42 +0,0 @@
[
{
"Id": 1,
"Name": "DE-Test Container",
"Environment": "TEST",
"IsActive": true,
"TargetDirectory": "DeploySandbox/target-esb",
"CertificateFileName": "esb-cert.cer",
"ContainerName": "DE-Test",
"RestartType": "SonicContainer",
"RestartCommand": "",
"RestartArguments": "",
"RestartTimeoutSeconds": 120,
"SonicConnectionName": "DE-Test",
"XapiSourcePath": "",
"TlsHost": "",
"TlsPort": null,
"TlsServerName": "",
"ExpectedFingerprint": null,
"SortOrder": 10
},
{
"Id": 2,
"Name": "Lokaler CMD-Test (Echo)",
"Environment": "DEV",
"IsActive": false,
"TargetDirectory": "DeploySandbox/target-cmd",
"CertificateFileName": "esb-cert.cer",
"ContainerName": "",
"RestartType": "Command",
"RestartCommand": "cmd.exe",
"RestartArguments": "/c echo Neustart-Simulation OK",
"RestartTimeoutSeconds": 15,
"SonicConnectionName": "",
"XapiSourcePath": "",
"TlsHost": "",
"TlsPort": null,
"TlsServerName": "",
"ExpectedFingerprint": null,
"SortOrder": 20
}
]
@@ -0,0 +1,36 @@
/*
DATABASE: [master]
OBJECTNAME: DropExisting
ACTION: DROP
IDX: 001
DATE: 2026-07-29
USER: CUR
COMMENT: Loescht die befuellte Alt-DB (Tippfehler-Name) und die Ziel-DB
vollstaendig inkl. aller Tabellen und Always-Encrypted-Keys
(CMK/CEK). Danach: 01_CreateDatabase -> 02_CreateSchema -> 03_Seed.
Voraussetzung Client: Always-Encrypted-Zertifikat mit Thumbprint
aus appsettings (CurrentUser\My) muss vorhanden bleiben.
ACHTUNG: destruktiv, keine Datenmigration.
*/
USE [master];
GO
/* Alte DB mit Tippfehler im Namen */
IF DB_ID(N'EsbZertifkatManager') IS NOT NULL
BEGIN
ALTER DATABASE [EsbZertifkatManager]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [EsbZertifkatManager];
END;
GO
/* Ziel-DB (falls bereits angelegt / befuellt) */
IF DB_ID(N'EsbZertifikatManager') IS NOT NULL
BEGIN
ALTER DATABASE [EsbZertifikatManager]
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [EsbZertifikatManager];
END;
GO
@@ -0,0 +1,31 @@
/*
DATABASE: [EsbZertifikatManager]
OBJECTNAME: Database
ACTION: CREATE
IDX: 002
DATE: 2026-07-29
USER: CUR
COMMENT: Legt die leere Ziel-DB an. Vorher 00_DropExisting.sql ausfuehren
(loescht Alt- und Ziel-DB inkl. Always Encrypted).
Reihenfolge: 00_DropExisting -> 01_CreateDatabase ->
02_CreateSchema -> 03_Seed.
*/
USE [master];
GO
IF DB_ID(N'EsbZertifikatManager') IS NOT NULL
BEGIN
THROW 50001,
N'Datenbank [EsbZertifikatManager] existiert noch. Zuerst 00_DropExisting.sql ausfuehren.',
1;
END;
GO
CREATE DATABASE [EsbZertifikatManager];
GO
ALTER DATABASE [EsbZertifikatManager] SET RECOVERY SIMPLE;
GO
ALTER DATABASE [EsbZertifikatManager] SET READ_WRITE;
GO
@@ -0,0 +1,607 @@
/*
DATABASE: [EsbZertifikatManager]
OBJECTNAME: Schema
ACTION: CREATE
IDX: 002
DATE: 2026-07-29
USER: CUR
COMMENT: 6 Kern-Tabellen + Always Encrypted (CMK/CEK).
Vorher 00_DropExisting + 01_CreateDatabase ausfuehren.
CMK-KEY_PATH = CurrentUser/My/<Thumbprint aus appsettings>.
CEK-ENCRYPTED_VALUE ist an dieses Zertifikat gebunden.
Bei neuem Cert: CEK in SSMS neu erzeugen und ENCRYPTED_VALUE ersetzen.
Connection String muss Column Encryption Setting=Enabled enthalten.
*/
USE [EsbZertifikatManager];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
/* -------------------------------------------------------------------------- */
/* Reste bereinigen: Kind-Tabellen -> AE-Tabellen -> CEK -> CMK */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[CertificateBackup]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[CertificateBackup];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentRunStep]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentRunStep];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentRunContainer]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentRunContainer];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentRun]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentRun];
END;
GO
IF OBJECT_ID(N'[dbo].[DeploymentAction]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[DeploymentAction];
END;
GO
IF OBJECT_ID(N'[dbo].[TlsEndpoint]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[TlsEndpoint];
END;
GO
IF OBJECT_ID(N'[dbo].[CertificateTarget]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[CertificateTarget];
END;
GO
IF OBJECT_ID(N'[dbo].[CertificateArtifact]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[CertificateArtifact];
END;
GO
IF OBJECT_ID(N'[dbo].[AppUser]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[AppUser];
END;
GO
IF OBJECT_ID(N'[dbo].[SonicCredential]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[SonicCredential];
END;
GO
IF OBJECT_ID(N'[dbo].[SonicContainer]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[SonicContainer];
END;
GO
/* SonicConnection kann AE-Spalten haben — vor CEK-Drop entfernen */
IF OBJECT_ID(N'[dbo].[SonicConnection]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[SonicConnection];
END;
GO
IF OBJECT_ID(N'[dbo].[EsbService]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[EsbService];
END;
GO
IF OBJECT_ID(N'[dbo].[Country]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[Country];
END;
GO
IF OBJECT_ID(N'[dbo].[Environment]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[Environment];
END;
GO
IF OBJECT_ID(N'[dbo].[Company]', N'U') IS NOT NULL
BEGIN
DROP TABLE [dbo].[Company];
END;
GO
IF EXISTS
(
SELECT 1
FROM [sys].[column_encryption_keys] AS cek
WHERE cek.[name] = N'CEK_ESB_ZertifikatManager'
)
BEGIN
DROP COLUMN ENCRYPTION KEY [CEK_ESB_ZertifikatManager];
END;
GO
IF EXISTS
(
SELECT 1
FROM [sys].[column_master_keys] AS cmk
WHERE cmk.[name] = N'CMK_ESB_ZertifikatManager'
)
BEGIN
DROP COLUMN MASTER KEY [CMK_ESB_ZertifikatManager];
END;
GO
/* -------------------------------------------------------------------------- */
/* Always Encrypted neu anlegen */
/* -------------------------------------------------------------------------- */
CREATE COLUMN MASTER KEY [CMK_ESB_ZertifikatManager]
WITH
(
KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE',
KEY_PATH = N'CurrentUser/My/95E906CCC9C5F227969877CF3BD46D2AD7168853'
);
GO
CREATE COLUMN ENCRYPTION KEY [CEK_ESB_ZertifikatManager]
WITH VALUES
(
COLUMN_MASTER_KEY = [CMK_ESB_ZertifikatManager],
ALGORITHM = 'RSA_OAEP',
ENCRYPTED_VALUE = 0x017000630075007200720065006E00740075007300650072002F006D0079002F003900350065003900300036006300630063003900630035006600320032003700390036003900380037003700630066003300620064003400360064003200610064003700310036003800380035003300000063875F8F9E6CA28F5C6AB80295C9526F00C6708A9E3D8EEF172189C88A445F21D08CA11BCA77B3460C8F109A0C30B3CF9E3E7007BC4EACE9563591014C6FDE01F15E72F6992C7862F1C22C7E69D00418B26E700F95112E870A17C4510891AE687B522F71A58E70DFAE211F6B233628FB17E87F2837726E4801CB45A2357CE57194239950B19930CE5C8B7AF9A0CFF86AFA52DF97D392C77DE694ECA336A0DBCB6E5157AF8713636D90D121BDA1CBC4909C3A1102BE9FE561321FED761A672D2E534A5C94391704C79D007804DE628961BB2A4744D88E975C8287459F2D7D42F643078CEFF8842DFE4C826CF4B47BE2A59DC65D495F39BE45AA583E2784DE039F
);
GO
/* -------------------------------------------------------------------------- */
/* Company */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[Company]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Company]
(
[CompanyId] [int] IDENTITY(1, 1) NOT NULL,
[CompanyCode] [nvarchar](30) NOT NULL,
[CompanyName] [nvarchar](200) NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_Company] PRIMARY KEY CLUSTERED ([CompanyId] ASC),
CONSTRAINT [uc_Company_CompanyCode] UNIQUE NONCLUSTERED ([CompanyCode] ASC)
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Company_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [dc_Company_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Company_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [dc_Company_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Company_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Company]
ADD CONSTRAINT [dc_Company_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
/* -------------------------------------------------------------------------- */
/* Environment */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[Environment]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[Environment]
(
[EnvironmentId] [int] IDENTITY(1, 1) NOT NULL,
[EnvironmentCode] [nvarchar](30) NOT NULL,
[EnvironmentName] [nvarchar](100) NOT NULL,
[SortOrder] [int] NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_Environment] PRIMARY KEY CLUSTERED ([EnvironmentId] ASC),
CONSTRAINT [uc_Environment_EnvironmentCode] UNIQUE NONCLUSTERED ([EnvironmentCode] ASC),
CONSTRAINT [uc_Environment_SortOrder] UNIQUE NONCLUSTERED ([SortOrder] ASC)
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Environment_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Environment]
ADD CONSTRAINT [dc_Environment_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Environment_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Environment]
ADD CONSTRAINT [dc_Environment_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_Environment_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[Environment]
ADD CONSTRAINT [dc_Environment_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
/* -------------------------------------------------------------------------- */
/* SonicConnection */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[SonicConnection]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[SonicConnection]
(
[SonicConnectionId] [int] IDENTITY(1, 1) NOT NULL,
[EnvironmentId] [int] NOT NULL,
[ConnectionName] [nvarchar](150) NOT NULL,
[ManagementHost] [nvarchar](255) NOT NULL,
[ManagementPort] [int] NOT NULL,
[DomainName] [nvarchar](150) NOT NULL,
[ConnectionProtocol] [nvarchar](20) NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_SonicConnection] PRIMARY KEY CLUSTERED ([SonicConnectionId] ASC),
CONSTRAINT [uc_SonicConnection_ConnectionName] UNIQUE NONCLUSTERED ([ConnectionName] ASC),
CONSTRAINT [fk_SonicConnection_EnvironmentId]
FOREIGN KEY ([EnvironmentId])
REFERENCES [dbo].[Environment] ([EnvironmentId]),
CONSTRAINT [cc_SonicConnection_ManagementPort]
CHECK ([ManagementPort] >= (1) AND [ManagementPort] <= (65535))
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_ConnectionProtocol]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_ConnectionProtocol]
DEFAULT (N'tcp') FOR [ConnectionProtocol];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicConnection_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicConnection]
ADD CONSTRAINT [dc_SonicConnection_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[SonicConnection]')
AND i.[name] = N'idx_SonicConnection_EnvironmentActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_SonicConnection_EnvironmentActive]
ON [dbo].[SonicConnection]
(
[EnvironmentId] ASC,
[IsActive] ASC
);
END;
GO
/* -------------------------------------------------------------------------- */
/* SonicCredential (Always Encrypted) */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[SonicCredential]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[SonicCredential]
(
[SonicCredentialId] [int] IDENTITY(1, 1) NOT NULL,
[SonicConnectionId] [int] NOT NULL,
[CredentialName] [nvarchar](150) NOT NULL,
[CredentialUserName] [nvarchar](256)
COLLATE Latin1_General_BIN2
ENCRYPTED WITH
(
COLUMN_ENCRYPTION_KEY = [CEK_ESB_ZertifikatManager],
ENCRYPTION_TYPE = Randomized,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'
) NULL,
[CredentialSecret] [nvarchar](512)
COLLATE Latin1_General_BIN2
ENCRYPTED WITH
(
COLUMN_ENCRYPTION_KEY = [CEK_ESB_ZertifikatManager],
ENCRYPTION_TYPE = Randomized,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'
) NULL,
[IsDefault] [bit] NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_SonicCredential] PRIMARY KEY CLUSTERED ([SonicCredentialId] ASC),
CONSTRAINT [uc_SonicCredential_CredentialName]
UNIQUE NONCLUSTERED ([SonicConnectionId] ASC, [CredentialName] ASC),
CONSTRAINT [fk_SonicCredential_SonicConnectionId]
FOREIGN KEY ([SonicConnectionId])
REFERENCES [dbo].[SonicConnection] ([SonicConnectionId])
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_IsDefault]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_IsDefault]
DEFAULT ((0)) FOR [IsDefault];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicCredential_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicCredential]
ADD CONSTRAINT [dc_SonicCredential_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
/* -------------------------------------------------------------------------- */
/* SonicContainer */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[SonicContainer]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[SonicContainer]
(
[SonicContainerId] [int] IDENTITY(1, 1) NOT NULL,
[CompanyId] [int] NOT NULL,
[SonicConnectionId] [int] NOT NULL,
[ContainerName] [nvarchar](250) NOT NULL,
[ContainerDisplayName] [nvarchar](250) NULL,
[CertificateCheckUrl] [nvarchar](500) NULL,
[RestartTimeoutSeconds] [int] NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_SonicContainer] PRIMARY KEY CLUSTERED ([SonicContainerId] ASC),
CONSTRAINT [uc_SonicContainer_CompanyContainer]
UNIQUE NONCLUSTERED ([CompanyId] ASC, [ContainerName] ASC),
CONSTRAINT [fk_SonicContainer_CompanyId]
FOREIGN KEY ([CompanyId])
REFERENCES [dbo].[Company] ([CompanyId]),
CONSTRAINT [fk_SonicContainer_SonicConnectionId]
FOREIGN KEY ([SonicConnectionId])
REFERENCES [dbo].[SonicConnection] ([SonicConnectionId]),
CONSTRAINT [cc_SonicContainer_RestartTimeoutSeconds]
CHECK ([RestartTimeoutSeconds] >= (10) AND [RestartTimeoutSeconds] <= (3600))
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_RestartTimeoutSeconds]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_RestartTimeoutSeconds]
DEFAULT ((180)) FOR [RestartTimeoutSeconds];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_SonicContainer_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD CONSTRAINT [dc_SonicContainer_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[SonicContainer]')
AND i.[name] = N'idx_SonicContainer_ConnectionActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_SonicContainer_ConnectionActive]
ON [dbo].[SonicContainer]
(
[SonicConnectionId] ASC,
[IsActive] ASC
);
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[SonicContainer]')
AND i.[name] = N'idx_SonicContainer_CompanyActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_SonicContainer_CompanyActive]
ON [dbo].[SonicContainer]
(
[CompanyId] ASC,
[IsActive] ASC
);
END;
GO
/* -------------------------------------------------------------------------- */
/* CertificateTarget */
/* -------------------------------------------------------------------------- */
IF OBJECT_ID(N'[dbo].[CertificateTarget]', N'U') IS NULL
BEGIN
CREATE TABLE [dbo].[CertificateTarget]
(
[CertificateTargetId] [int] IDENTITY(1, 1) NOT NULL,
[SonicContainerId] [int] NOT NULL,
[TargetDirectory] [nvarchar](500) NOT NULL,
[TargetFileName] [nvarchar](260) NOT NULL,
[BackupEnabled] [bit] NOT NULL,
[BackupDirectoryName] [nvarchar](100) NOT NULL,
[IsActive] [bit] NOT NULL,
[CreationDateTime] [datetime2](3) NOT NULL,
[CreatedBy] [nvarchar](128) NOT NULL,
[ModifiedDateTime] [datetime2](3) NULL,
[ModifiedBy] [nvarchar](128) NULL,
CONSTRAINT [pk_CertificateTarget] PRIMARY KEY CLUSTERED ([CertificateTargetId] ASC),
CONSTRAINT [uc_CertificateTarget_Destination]
UNIQUE NONCLUSTERED
(
[SonicContainerId] ASC,
[TargetDirectory] ASC,
[TargetFileName] ASC
),
CONSTRAINT [fk_CertificateTarget_SonicContainerId]
FOREIGN KEY ([SonicContainerId])
REFERENCES [dbo].[SonicContainer] ([SonicContainerId])
);
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_BackupEnabled]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_BackupEnabled]
DEFAULT ((1)) FOR [BackupEnabled];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_BackupDirectoryName]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_BackupDirectoryName]
DEFAULT (N'Backup') FOR [BackupDirectoryName];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_IsActive]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_IsActive]
DEFAULT ((1)) FOR [IsActive];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_CreationDateTime]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_CreationDateTime]
DEFAULT (SYSUTCDATETIME()) FOR [CreationDateTime];
END;
GO
IF OBJECT_ID(N'[dbo].[dc_CertificateTarget_CreatedBy]', N'D') IS NULL
BEGIN
ALTER TABLE [dbo].[CertificateTarget]
ADD CONSTRAINT [dc_CertificateTarget_CreatedBy]
DEFAULT (SUSER_SNAME()) FOR [CreatedBy];
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [sys].[indexes] AS i
WHERE i.[object_id] = OBJECT_ID(N'[dbo].[CertificateTarget]')
AND i.[name] = N'idx_CertificateTarget_ContainerActive'
)
BEGIN
CREATE NONCLUSTERED INDEX [idx_CertificateTarget_ContainerActive]
ON [dbo].[CertificateTarget]
(
[SonicContainerId] ASC,
[IsActive] ASC
);
END;
GO
@@ -0,0 +1,351 @@
/*
DATABASE: [EsbZertifikatManager]
OBJECTNAME: Seed
ACTION: INSERT
IDX: 002
DATE: 2026-07-29
USER: CUR
COMMENT: KOMPLETTE Seed-Datei (alles in einem Lauf):
- Company: DE, PL, AT
- Environment: TEST2, TEST, PROD
- SonicConnection: DE-Test (TEST)
- SonicContainer: ct-ZADBService
- CertificateTarget: Platzhalter-Pfad
Keine Credentials (Always Encrypted / Setup-Wizard).
Voraussetzung: 00_DropExisting, 01_CreateDatabase, 02_CreateSchema,
AlwaysEncrypted-Zertifikat importiert (Install-Certificate.ps1).
*/
USE [EsbZertifikatManager];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
/* -------------------------------------------------------------------------- */
/* Company */
/* -------------------------------------------------------------------------- */
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Company] AS company
WHERE company.[CompanyCode] = N'DE'
)
BEGIN
INSERT INTO [dbo].[Company]
(
[CompanyCode],
[CompanyName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'DE', N'ZIEHL-ABEGG Deutschland', 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Company] AS company
WHERE company.[CompanyCode] = N'PL'
)
BEGIN
INSERT INTO [dbo].[Company]
(
[CompanyCode],
[CompanyName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'PL', N'ZIEHL-ABEGG Polen', 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Company] AS company
WHERE company.[CompanyCode] = N'AT'
)
BEGIN
INSERT INTO [dbo].[Company]
(
[CompanyCode],
[CompanyName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'AT', N'ZIEHL-ABEGG Oesterreich', 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
/* -------------------------------------------------------------------------- */
/* Environment */
/* -------------------------------------------------------------------------- */
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = N'TEST2'
)
BEGIN
INSERT INTO [dbo].[Environment]
(
[EnvironmentCode],
[EnvironmentName],
[SortOrder],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'TEST2', N'Test 2', 1, 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = N'TEST'
)
BEGIN
INSERT INTO [dbo].[Environment]
(
[EnvironmentCode],
[EnvironmentName],
[SortOrder],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'TEST', N'Test', 2, 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = N'PROD'
)
BEGIN
INSERT INTO [dbo].[Environment]
(
[EnvironmentCode],
[EnvironmentName],
[SortOrder],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(N'PROD', N'Production', 3, 1, SYSUTCDATETIME(), SUSER_SNAME());
END;
GO
/* -------------------------------------------------------------------------- */
/* TEST-System: DE-Test + ct-ZADBService + CertificateTarget */
/* -------------------------------------------------------------------------- */
DECLARE @EnvironmentIdTest [int];
DECLARE @CompanyIdDe [int];
DECLARE @SonicConnectionIdDeTest [int];
DECLARE @SonicContainerId [int];
SELECT @EnvironmentIdTest = environment.[EnvironmentId]
FROM [dbo].[Environment] AS environment
WHERE environment.[EnvironmentCode] = N'TEST';
SELECT @CompanyIdDe = company.[CompanyId]
FROM [dbo].[Company] AS company
WHERE company.[CompanyCode] = N'DE';
IF @EnvironmentIdTest IS NULL
OR @CompanyIdDe IS NULL
BEGIN
THROW 50002,
N'Seed unvollstaendig: Environment TEST oder Company DE fehlt.',
1;
END;
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[SonicConnection] AS connection
WHERE connection.[ConnectionName] = N'DE-Test'
)
BEGIN
INSERT INTO [dbo].[SonicConnection]
(
[EnvironmentId],
[ConnectionName],
[ManagementHost],
[ManagementPort],
[DomainName],
[ConnectionProtocol],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(
@EnvironmentIdTest,
N'DE-Test',
N'dekun-painwbdet',
13070,
N'proalpha-test',
N'tcp',
1,
SYSUTCDATETIME(),
SUSER_SNAME()
);
END;
SELECT @SonicConnectionIdDeTest = connection.[SonicConnectionId]
FROM [dbo].[SonicConnection] AS connection
WHERE connection.[ConnectionName] = N'DE-Test';
IF @SonicConnectionIdDeTest IS NULL
BEGIN
THROW 50003,
N'Seed fehlgeschlagen: SonicConnection DE-Test nicht gefunden.',
1;
END;
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[SonicContainer] AS container
WHERE container.[SonicConnectionId] = @SonicConnectionIdDeTest
AND container.[ContainerName] = N'ct-ZADBService'
)
BEGIN
INSERT INTO [dbo].[SonicContainer]
(
[CompanyId],
[SonicConnectionId],
[ContainerName],
[ContainerDisplayName],
[RestartTimeoutSeconds],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(
@CompanyIdDe,
@SonicConnectionIdDeTest,
N'ct-ZADBService',
N'ZADBService',
180,
1,
SYSUTCDATETIME(),
SUSER_SNAME()
);
END;
SELECT @SonicContainerId = container.[SonicContainerId]
FROM [dbo].[SonicContainer] AS container
WHERE container.[SonicConnectionId] = @SonicConnectionIdDeTest
AND container.[ContainerName] = N'ct-ZADBService';
IF @SonicContainerId IS NULL
BEGIN
THROW 50004,
N'Seed fehlgeschlagen: SonicContainer ct-ZADBService nicht gefunden.',
1;
END;
/* Demo-Pfad für Zertifikat-Austausch (UNC server.p12) immer sicherstellen */
DECLARE @DemoDirectory [nvarchar](500) =
N'\\dekun-painwbdet\e$\proalpha-inwb\test\Containers\proalpha-test.ct-ZADBService\certs';
DECLARE @DemoFileName [nvarchar](260) = N'server.p12';
UPDATE [dbo].[CertificateTarget]
SET
[TargetDirectory] = @DemoDirectory,
[TargetFileName] = @DemoFileName,
[BackupEnabled] = 1,
[BackupDirectoryName] = N'Backup',
[IsActive] = 1,
[ModifiedDateTime] = SYSUTCDATETIME(),
[ModifiedBy] = SUSER_SNAME()
WHERE [SonicContainerId] = @SonicContainerId
AND
(
[TargetFileName] IN (N'server.crt', N'server.p12')
OR [TargetDirectory] LIKE N'C:\ESB\Certificates%'
);
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[CertificateTarget] AS target
WHERE target.[SonicContainerId] = @SonicContainerId
AND target.[TargetDirectory] = @DemoDirectory
AND target.[TargetFileName] = @DemoFileName
AND target.[IsActive] = 1
)
BEGIN
INSERT INTO [dbo].[CertificateTarget]
(
[SonicContainerId],
[TargetDirectory],
[TargetFileName],
[BackupEnabled],
[BackupDirectoryName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(
@SonicContainerId,
@DemoDirectory,
@DemoFileName,
1,
N'Backup',
1,
SYSUTCDATETIME(),
SUSER_SNAME()
);
END;
GO
/* Kontrolle */
SELECT
company.[CompanyCode],
environment.[EnvironmentCode],
connection.[ConnectionName],
connection.[ManagementHost],
connection.[ManagementPort],
connection.[DomainName],
container.[ContainerName],
target.[TargetDirectory],
target.[TargetFileName]
FROM [dbo].[SonicConnection] AS connection
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] = connection.[EnvironmentId]
INNER JOIN [dbo].[SonicContainer] AS container
ON container.[SonicConnectionId] = connection.[SonicConnectionId]
INNER JOIN [dbo].[Company] AS company
ON company.[CompanyId] = container.[CompanyId]
LEFT JOIN [dbo].[CertificateTarget] AS target
ON target.[SonicContainerId] = container.[SonicContainerId]
ORDER BY
environment.[SortOrder],
company.[CompanyCode],
connection.[ConnectionName];
GO
@@ -0,0 +1,106 @@
/*
COMMENT: Stellt den Demo-Zertifikatspfad für DE-Test / ct-ZADBService sicher.
Kann allein gegen eine bestehende EsbZertifikatManager-DB laufen
(ohne Drop/Create). EnvironmentCode muss TEST sein (appsettings).
*/
USE [EsbZertifikatManager];
GO
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @SonicContainerId [int];
DECLARE @TargetDirectory [nvarchar](500) =
N'\\dekun-painwbdet\e$\proalpha-inwb\test\Containers\proalpha-test.ct-ZADBService\certs';
DECLARE @TargetFileName [nvarchar](260) = N'server.p12';
SELECT @SonicContainerId = container.[SonicContainerId]
FROM [dbo].[SonicContainer] AS container
INNER JOIN [dbo].[SonicConnection] AS connection
ON connection.[SonicConnectionId] = container.[SonicConnectionId]
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] = connection.[EnvironmentId]
WHERE environment.[EnvironmentCode] = N'TEST'
AND connection.[ConnectionName] = N'DE-Test'
AND container.[ContainerName] = N'ct-ZADBService';
IF @SonicContainerId IS NULL
BEGIN
THROW 50010,
N'Demo-Pfad: Container ct-ZADBService / DE-Test / TEST nicht gefunden. Zuerst 03_Seed.sql ausführen.',
1;
END;
/* Alte Platzhalter am gleichen Container auf Demo-Pfad umbiegen */
UPDATE [dbo].[CertificateTarget]
SET
[TargetDirectory] = @TargetDirectory,
[TargetFileName] = @TargetFileName,
[BackupEnabled] = 1,
[BackupDirectoryName] = N'Backup',
[IsActive] = 1,
[ModifiedDateTime] = SYSUTCDATETIME(),
[ModifiedBy] = SUSER_SNAME()
WHERE [SonicContainerId] = @SonicContainerId
AND
(
[TargetFileName] IN (N'server.crt', N'server.p12')
OR [TargetDirectory] LIKE N'C:\ESB\Certificates%'
);
IF NOT EXISTS
(
SELECT 1
FROM [dbo].[CertificateTarget] AS target
WHERE target.[SonicContainerId] = @SonicContainerId
AND target.[TargetDirectory] = @TargetDirectory
AND target.[TargetFileName] = @TargetFileName
AND target.[IsActive] = 1
)
BEGIN
INSERT INTO [dbo].[CertificateTarget]
(
[SonicContainerId],
[TargetDirectory],
[TargetFileName],
[BackupEnabled],
[BackupDirectoryName],
[IsActive],
[CreationDateTime],
[CreatedBy]
)
VALUES
(
@SonicContainerId,
@TargetDirectory,
@TargetFileName,
1,
N'Backup',
1,
SYSUTCDATETIME(),
SUSER_SNAME()
);
END;
SELECT
target.[CertificateTargetId],
company.[CompanyCode],
environment.[EnvironmentCode],
connection.[ConnectionName],
container.[ContainerName],
target.[TargetDirectory],
target.[TargetFileName],
target.[IsActive]
FROM [dbo].[CertificateTarget] AS target
INNER JOIN [dbo].[SonicContainer] AS container
ON container.[SonicContainerId] = target.[SonicContainerId]
INNER JOIN [dbo].[SonicConnection] AS connection
ON connection.[SonicConnectionId] = container.[SonicConnectionId]
INNER JOIN [dbo].[Environment] AS environment
ON environment.[EnvironmentId] = connection.[EnvironmentId]
INNER JOIN [dbo].[Company] AS company
ON company.[CompanyId] = container.[CompanyId]
WHERE container.[SonicContainerId] = @SonicContainerId;
PRINT N'Demo-Pfad ist aktiv: \\dekun-painwbdet\e$\...\certs\server.p12';
GO
@@ -0,0 +1,49 @@
/*
DATABASE: [EsbZertifikatManager]
OBJECTNAME: SonicContainer.CertificateCheckUrl
ACTION: ALTER
IDX: 005
DATE: 2026-07-31
USER: CUR
COMMENT: URL/Host:Port fuer TLS-Zertifikatspruefung (curl-aehnlich) am Container.
*/
USE [EsbZertifikatManager];
GO
IF COL_LENGTH(N'dbo.SonicContainer', N'CertificateCheckUrl') IS NULL
BEGIN
IF COL_LENGTH(N'dbo.SonicContainer', N'vertifiaktechecurl') IS NOT NULL
BEGIN
EXEC sp_rename
N'dbo.SonicContainer.vertifiaktechecurl',
N'CertificateCheckUrl',
N'COLUMN';
END
ELSE IF COL_LENGTH(N'dbo.SonicContainer', N'VerifyCertificateCheckCurl') IS NOT NULL
BEGIN
EXEC sp_rename
N'dbo.SonicContainer.VerifyCertificateCheckCurl',
N'CertificateCheckUrl',
N'COLUMN';
END
ELSE IF COL_LENGTH(N'dbo.SonicContainer', N'CertificateCheckCurl') IS NOT NULL
BEGIN
EXEC sp_rename
N'dbo.SonicContainer.CertificateCheckCurl',
N'CertificateCheckUrl',
N'COLUMN';
END
ELSE IF COL_LENGTH(N'dbo.SonicContainer', N'ZertifikatCheckCurl') IS NOT NULL
BEGIN
EXEC sp_rename
N'dbo.SonicContainer.ZertifikatCheckCurl',
N'CertificateCheckUrl',
N'COLUMN';
END
ELSE
BEGIN
ALTER TABLE [dbo].[SonicContainer]
ADD [CertificateCheckUrl] [nvarchar](500) NULL;
END
END;
GO
@@ -0,0 +1,41 @@
<#
.SYNOPSIS
Importiert das Always-Encrypted-Zertifikat in CurrentUser\My.
.NOTES
PFX-Passwort (nur Dev): ESB-AE-Dev-Only!
Thumbprint muss mit appsettings AlwaysEncrypted:CertificateThumbprint uebereinstimmen.
#>
$ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$pfxPath = Join-Path $scriptDir "ESB_ZertifikatManager_AE.pfx"
$thumbPath = Join-Path $scriptDir "thumbprint.txt"
if (-not (Test-Path $pfxPath)) {
throw "PFX nicht gefunden: $pfxPath"
}
$expectedThumb = (Get-Content -Path $thumbPath -Raw).Trim()
$password = ConvertTo-SecureString -String "ESB-AE-Dev-Only!" -Force -AsPlainText
$imported = Import-PfxCertificate `
-FilePath $pfxPath `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Password $password `
-Exportable
Write-Host "Zertifikat importiert:"
Write-Host " Subject : $($imported.Subject)"
Write-Host " Thumbprint : $($imported.Thumbprint)"
Write-Host " PrivateKey : $($imported.HasPrivateKey)"
if (-not [string]::Equals(
$imported.Thumbprint,
$expectedThumb,
[System.StringComparison]::OrdinalIgnoreCase)) {
Write-Warning "Thumbprint weicht von thumbprint.txt ab ($expectedThumb)."
}
Write-Host ""
Write-Host "appsettings.json AlwaysEncrypted:CertificateThumbprint ="
Write-Host $imported.Thumbprint
@@ -0,0 +1 @@
0x017000630075007200720065006E00740075007300650072002F006D0079002F003900350065003900300036006300630063003900630035006600320032003700390036003900380037003700630066003300620064003400360064003200610064003700310036003800380035003300000063875F8F9E6CA28F5C6AB80295C9526F00C6708A9E3D8EEF172189C88A445F21D08CA11BCA77B3460C8F109A0C30B3CF9E3E7007BC4EACE9563591014C6FDE01F15E72F6992C7862F1C22C7E69D00418B26E700F95112E870A17C4510891AE687B522F71A58E70DFAE211F6B233628FB17E87F2837726E4801CB45A2357CE57194239950B19930CE5C8B7AF9A0CFF86AFA52DF97D392C77DE694ECA336A0DBCB6E5157AF8713636D90D121BDA1CBC4909C3A1102BE9FE561321FED761A672D2E534A5C94391704C79D007804DE628961BB2A4744D88E975C8287459F2D7D42F643078CEFF8842DFE4C826CF4B47BE2A59DC65D495F39BE45AA583E2784DE039F
@@ -0,0 +1 @@
ESB-AE-Dev-Only!
@@ -0,0 +1 @@
95E906CCC9C5F227969877CF3BD46D2AD7168853
@@ -0,0 +1,67 @@
# Datenbank EsbZertifikatManager
Schlankes Schema (6 Tabellen) + Always Encrypted.
## Einmalige Reihenfolge
1. **Zertifikat importieren** (auf jedem Client-PC):
```powershell
cd Database\AlwaysEncrypted
.\Install-Certificate.ps1
```
- PFX: `ESB_ZertifikatManager_AE.pfx`
- Passwort: siehe `pfx-password.txt` (`ESB-AE-Dev-Only!`)
- Thumbprint: `95E906CCC9C5F227969877CF3BD46D2AD7168853`
2. In SSMS:
1. `00_DropExisting.sql`
2. `01_CreateDatabase.sql`
3. `02_CreateSchema.sql` (CMK/CEK + Tabellen)
4. `03_Seed.sql` (**komplett**: Companies, Environments, DE-Test, Container, Target)
3. App starten → Einstellungen → System **DE-Test** → Benutzerprofil anlegen → Übernehmen.
## Seed-Inhalt (`03_Seed.sql`)
| Objekt | Werte |
|---|---|
| Company | DE, PL, AT |
| Environment | TEST2, TEST, PROD |
| SonicConnection | DE-Test → `tcp://dekun-painwbdet:13070`, Domain `proalpha-test`, Env TEST |
| SonicContainer | ct-ZADBService (Company DE) |
| CertificateTarget | Demo-UNC `\\dekun-painwbdet\e$\...\certs\server.p12` |
| SonicCredential | **nicht** geseedet (Setup-Wizard / AE) |
## Demo-Pfad nachziehen (bestehende DB)
Wenn der alte Platzhalter noch in SQL liegt:
```text
04_EnsureDemoPath.sql
```
Zusätzlich: in `appsettings.json` ist `"Demo": { "Enabled": true, ... }`
die App zeigt den Demo-Pfad auch ohne SQL-Update in der Zielliste (Kopieren testen).
## TLS-Prüf-URL am Container (bestehende DB)
Für Live-Zertifikatsprüfung (curl-ähnlich) die Spalte nachziehen:
```text
05_AddCertificateCheckUrl.sql
```
Danach in Einstellungen → Container die Prüf-URL setzen, z. B. `https://server:8443`.
## Always Encrypted
| Objekt | Name |
|---|---|
| CMK | `CMK_ESB_ZertifikatManager``CurrentUser/My/95E906CCC9C5F227969877CF3BD46D2AD7168853` |
| CEK | `CEK_ESB_ZertifikatManager` |
| Spalten | `SonicCredential.CredentialUserName`, `CredentialSecret` |
Connection String braucht `Column Encryption Setting=Enabled`.
@@ -0,0 +1,437 @@
# Dateiübersicht nach Ordnerstruktur
Was **jede einzelne Datei** macht inkl. Sonic Management API.
Projektroot der App: `ZA.CoreService.ESBCertificateManager/`
---
## 0. Gesamtbild (wer ruft wen)
```text
Program.cs
└─ Form1.cs (UI)
├─ Zertifikat lesen → CertificateInfo
├─ Ziele ← SonicContainerDiscovery (KnownContainers / Live)
└─ Neustart
└─ DeploymentOrchestrator
└─ RestartExecutor
└─ SonicManagementClient
└─ SonicMfApiExecutor
└─ java + Tools/SonicMfContainerTool
└─ Sonic JARs (externe API)
└─ Domain Manager tcp://…:13070
```
Konfiguration: `appsettings.json``AppSettingsLoader``AppSettings` / `SonicConnection`.
---
## 1. Projektroot (`ZA.CoreService.ESBCertificateManager/`)
### `Program.cs`
Einstiegspunkt der WinForms-App.
`ApplicationConfiguration.Initialize()` + `Application.Run(new Form1())`.
Keine Business-Logik.
### `Form1.cs`
**Gesamte Benutzeroberfläche** (Dark-Design, Sidebar, Zertifikatskarte, Grid, Buttons).
Wichtige Aufgaben:
| Bereich | Was passiert |
|---------|----------------|
| Zertifikat | Dateidialog → `X509Certificate2` → Subject/Issuer/Fingerprint/Gültigkeit anzeigen |
| Ziele | Grid aus `KnownContainers`; Button „Container laden“ → Live-Discovery |
| Prüfung | `ValidateRestartOnly` (ContainerName + SonicConnectionName) |
| Neustart | Bestätigung → `DeploymentOrchestrator.RestartOnlyAsync` → Ergebnisdialog |
| Status | Statuszeile, Fortschritt im Grid, SonicHome/Java-Anzeige |
Deploy-Button ist vorhanden im Layout, aber **ausgeblendet/deaktiviert**.
### `Form1.Designer.cs`
WinForms-Designer-Stub. `InitializeComponent()` ist praktisch leer das Layout wird in `Form1.cs` per Code gebaut.
### `Form1.resx`
Ressourcen-Datei zum Formular (Standard WinForms). Kaum eigene Inhalte.
### `appsettings.json`
**Laufzeitkonfiguration** die „Online“-Quelle für Sonic:
- `LogDirectory` wohin Logs geschrieben werden
- `SonicConnections[]` Domain, URL, User/Pass, Java, Lib-Pfad, **KnownContainers**
Beispiel-Verbindung `DE-Test` → Domain `proalpha-test`, Container `ct-ZADBService`.
### `ZA.CoreService.ESBCertificateManager.csproj`
Projektdatei: .NET 8 WinForms, NuGet (Configuration), welche Dateien nach `bin` kopiert werden (`appsettings`, `Tools`, `Assets`, `Docs`).
### `ZA.CoreService.ESBCertificateManager.csproj.user`
Lokale Visual-Studio-Benutzereinstellungen (Debugger usw.). Nicht fachlich relevant.
---
## 2. Ordner `Configuration/`
### `AppSettingsLoader.cs`
Liest `appsettings.json` über `Microsoft.Extensions.Configuration` und bindet sie an `AppSettings`.
Sucht zuerst im Output-Verzeichnis (`AppContext.BaseDirectory`), sonst im Arbeitsverzeichnis.
---
## 3. Ordner `Models/` (Datenstrukturen)
### `AppSettings.cs`
Root-Settings-Objekt:
- `LogDirectory`
- `List<SonicConnection> SonicConnections`
### `SonicConnection.cs`
Eine Sonic-/SMC-Verbindung:
| Property | Bedeutung |
|----------|-----------|
| `Name` | Alias, z. B. `DE-Test` |
| `DomainName` | z. B. `proalpha-test` |
| `ConnectionUrl` | `tcp://host:port` wie SMC |
| `Username` / `Password` | SMC-Login |
| `SonicHome` / `JavaHome` / `JavaPath` | Pfade |
| `MfClientLibPath` | Ordner mit Client-JARs |
| `KnownContainers` | Containerliste für die UI |
| `TimeoutSeconds` / `PostRestartDelaySeconds` | Timeouts |
| `ManagementModeDisplay` | Anzeigetext „MfApi …“ |
Enthält noch ältere Hilfsfelder/Enums (WinRM etc.) aus der Historie der aktive Client nutzt nur den MfApi-Pfad.
### `DeploymentTarget.cs`
Ein **Ziel** im Grid (ein Container zum Neustarten):
- `Id`, `Name`, `Environment`, `IsActive`
- `ContainerName` z. B. `ct-ZADBService`
- `SonicConnectionName` Verweis auf `SonicConnection.Name`
- `RestartType` für Neustart typischerweise `SonicContainer`
- Weitere Felder (TargetDirectory, Tls*, Xapi*) können noch im Model stehen, werden für den aktuellen Neustart-Pfad nicht mehr aktiv genutzt
### `CertificateInfo.cs`
Ergebnis der lokalen Zertifikatsprüfung:
- Subject, Issuer
- FingerprintSha256
- ValidFrom / ValidUntil
- IsCurrentlyValid
### `DeploymentRunResult.cs`
Ergebnisstrukturen nach einem Lauf:
- `DeploymentRunResult` Gesamtlauf (Liste Zielergebnisse, Zeiten, OverallSuccess)
- `TargetStepResult` ein Ziel (Success, StatusText, Detail, …)
- `ValidationIssue` / `PreflightValidationResult` Vorabprüfung
- `TargetProgressUpdate` Fortschritt für die UI
---
## 4. Ordner `Services/` (Geschäftslogik)
### `DeploymentOrchestrator.cs`
Orchestriert **nur Neustart** (kein Deploy/TLS):
1. `ValidateRestartOnly` → Preflight
2. `RestartOnlyAsync` → für jedes Ziel `RestartExecutor`
3. Schreibt Log über `RunLogger`
4. Meldet Progress an die UI
### `RestartExecutor.cs`
Mappt ein `DeploymentTarget` auf die passende `SonicConnection` und ruft
`SonicManagementClient.RestartContainerAsync(containerName)` auf.
### `PreflightValidator.cs`
Prüft vor dem Neustart:
- mindestens ein Ziel ausgewählt
- `ContainerName` gesetzt
- `SonicConnectionName` gesetzt
### `SonicManagementClient.cs`
**Dünne Fassade** zur MfApi (kein WinRM/HTTP/XApi mehr):
| Methode | Zweck |
|---------|--------|
| `CheckConnectionAsync` | Ping / Soft-Fallback KnownContainers+TCP |
| `GetContainersAsync` | Liste von Containern (API oder KnownContainers) |
| `RestartContainerAsync` | Neustart + Wartezeit `PostRestartDelaySeconds` |
### `SonicMfApiExecutor.cs`
**Brücke C# → Java.** Das ist der technische Kern „unsere Seite der API“:
1. Java 8+ finden (`JavaPath` / Suche)
2. Classpath aus allen JARs unter `MfClientLibPath` / `SonicHome\lib` bauen
3. `Tools\SonicMfContainerTool.class` laden (Version prüfen)
4. Prozess starten: `java -cp … SonicMfContainerTool ping|list|restart …`
5. Passwort über Env `ESB_SONIC_PASSWORD`
6. Erfolg bei Neustart nur wenn Output `OK:RestartInvoked` enthält
Öffentliche Methoden:
- `TestConnectionAsync` → Tool-Befehl `ping`
- `ListContainersAsync``list`
- `RestartAsync``restart`
- `ResolveRuntimePaths` → für Statuszeile in der UI
### `SonicContainerDiscovery.cs`
Baut die Zielliste:
- `BuildTargetsFromConfig()` aus `KnownContainers` in appsettings (Start der App)
- `DiscoverAsync()` live per `SonicManagementClient` listen + mit KnownContainers mergen
### `RunLogger.cs`
Schreibt Textdateien unter `LogDirectory` (ein Log pro Lauf). Redaktiert ggf. secrets-ähnliche Strings.
### `PathResolver.cs`
Löst relative Pfade gegen `AppContext.BaseDirectory` auf (z. B. für Logs).
---
## 5. Ordner `Tools/` (Java + Sonic-API)
Hier liegt der **eigentliche Aufruf der Sonic Management Application API**.
### `SonicMfContainerTool.java`
Kleines Java-Hilfsprogramm (Reflection, kompilierbar ohne Sonic-JARs auf dem Compile-Classpath).
**Befehle:**
| Befehl | Bedeutung |
|--------|-----------|
| `ping` | Connect + Agenten zählen |
| `list` | Containernamen der Domain ausgeben |
| `restart` | Lifecycle auf einem Container |
**Connect (API-Login):**
```text
Hashtable: ConnectionURLs, DefaultUser, DefaultPassword
→ new JMSConnectorAddress(env)
→ new JMSConnectorClient().connect(address, timeout)
```
Klassen aus Sonic-JARs:
- `com.sonicsw.mf.jmx.client.JMSConnectorAddress`
- `com.sonicsw.mf.jmx.client.JMSConnectorClient`
**Neustart Ziel:**
```text
ObjectName = {Domain}.{Container}:ID=AGENT
Beispiel: proalpha-test.ct-ZADBService:ID=AGENT
```
**Neustart Wege (nacheinander):**
1. **MBean.invoke** `"stop"` / `"restart"` / `"shutdown"` ← bevorzugt remote
2. **IAgentProxy** über `MFProxyFactory.createAgentProxy` (oft *unbounded*-Fehler)
3. **DomainManager**-Operationen wie `restartContainer` / `stopContainer`
Erfolg:
```text
OK:RestartInvoked method=…
INFO:ToolVersion=…
```
### `SonicMfContainerTool.class` / `SonicMfContainerTool$Args.class`
Vorkompilierter Java-8-Bytecode (major 52), den die App zur Laufzeit nutzt (ohne `javac` auf dem Zielrechner).
### `SonicMfContainerTool.jar`
Optional verpackte Variante des Tools (Classpath-Alternative).
### `verify-mfapi-classpath.bat`
Hilfsskript: prüft manuell, ob `JMSConnectorAddress` mit den JARs unter `C:\DEV\MQ10.0\lib` ladbar ist.
---
## 6. Ordner `Docs/` (Dokumentation)
### `Komplette-Erklaerung.md`
Schritt-für-Schritt: Zertifikat, Preflight, C#-Kette, Java-API, Fehlerbilder, Präsentationsskript.
### `Neustart-und-Management-API.md`
Kompaktere API-/Neustart-Erklärung inkl. „Was ist unnötig in der großen Sonic-API?“.
### `Dateiuebersicht-nach-Ordner.md`
Diese Datei Katalog aller Projektdateien.
---
## 7. Ordner `Assets/`
### `logo.png`
UI-/Branding-Grafik.
### `TestCertificates/esb-test-cert.cer`
Beispiel-Zertifikat zum lokalen Test der Dateierkennung (kein Sonic-Deploy).
---
## 8. Ordner `Demo/`
### `Demo/Central/esb-cert.cer`
Weiteres Beispielzertifikat. Nicht Teil der Online-Neustart-Logik.
---
## 9. Sibling-Ordner `doc-10.0.10/` (neben der App, im Repo)
Produkt-Dokumentation Aurea CX Messenger / Sonic (nicht App-Code).
### `CXMessenger_2017_R3.htm`
Index der offiziellen Doku. Wichtiger Link:
- **Management Application API Reference** → `Docs2017/api/mgmt_api/`
Das ist die API-Familie, die SMC und unser Java-Tool nutzen.
### `Docs2017/api/analytics-offloader/…`
Javadoc-Stub für Analytics Offload **nicht** der Neustart-Pfad.
### PDFs / vollständige `mgmt_api`
Im Index verlinkt; können im Checkout fehlen. Für Reviews idealerweise nachziehen.
---
## 10. Die Sonic Management Application API detailliert
### 10.1 Was „API“ hier bedeutet
Es ist **keine REST-URL der WinForms-App**.
Es ist die **Java Management Application API** von Sonic/Aurea:
- Transport: JMS zum Domain Manager (`tcp://host:port`)
- Steuerung: JMX-ähnliche ObjectNames + Operationen
- Authentifizierung: SMC-User/Pass
Dieselbe Schicht wie der Restart in der Sonic Management Console.
### 10.2 Welche Sonic-Klassen wir wirklich brauchen
| Klasse | Aufgabe |
|--------|---------|
| `JMSConnectorAddress` | Verbindungsparameter kapseln |
| `JMSConnectorClient` | Verbinden / disconnect / invoke / queryNames |
| `MFProxyFactory` | `createAgentProxy` |
| `IAgentProxy` | typisierte Methoden stop/restart/… (remote oft eingeschränkt) |
| ObjectName `domain.container:ID=AGENT` | Ziel-Agent |
Benötigte JARs typischerweise unter `C:\DEV\MQ10.0\lib`:
- `mgmt_client.jar`
- `mfcontext.jar`
- `sonic_Client.jar`
(+ weitere Abhängigkeiten im selben Ordner)
### 10.3 Warum die Sonic-API „viele unnötige Funktionen“ hat
Die Produkt-API deckt ab:
- Messaging (Queues, Topics, Durable Subs)
- Broker-Admin
- ESB
- Metrics / Notifications
- Config / Directory
**Für unseren Use-Case reichen 3 Schritte:** Connect → Agent finden → Stop/Restart.
Der Rest der JARs/Javadoc ist Produktumfang, nicht App-Feature.
### 10.4 „Unbounded client connector“
Remote-Clients (SMC, unser Tool) sind oft **unbounded**.
Dann schlägt `IAgentProxy.restart()` fehl mit:
```text
Operation unsupported for unbounded client connector
```
Deshalb bevorzugt das Tool **MBean.invoke("stop")**.
Operativ entspricht das oft SMC-Restart: Stop + Auto-Relaunch durch Launch Daemon.
### 10.5 Sequenz eines Neustarts (API-Ebene)
```text
1. C#: RestartExecutor → SonicManagementClient → SonicMfApiExecutor
2. C#: startet java -cp Tools;lib\*.jar SonicMfContainerTool restart …
3. Java: JMSConnectorClient.connect(url, user, pass)
4. Java: ObjectName = proalpha-test.ct-ZADBService:ID=AGENT
5. Java: connector.invoke(on, "stop", …) // oder Fallback
6. Java: OK:RestartInvoked
7. C#: PostRestartDelaySeconds warten
8. UI: Erfolg/Fehler + Log
```
---
## 11. Zertifikatsprüfung welche Dateien
| Datei | Rolle |
|-------|--------|
| `Form1.cs` | Dateiauswahl, PFX-Passwort, Anzeige |
| `Models/CertificateInfo.cs` | Ergebnisobjekt |
| `Assets/...` / `Demo/...` | optionale Testdateien |
Ablauf:
1. Datei wählen (`.cer/.crt/.pem/.pfx`)
2. Als `X509Certificate2` laden
3. Subject/Issuer/Fingerprint/Gültigkeit berechnen
4. Anzeigen **lokal verifiziert**
Nicht enthalten: Kopieren auf ESB, TLS-Probe gegen Live-Host, CA-Kettenprüfung.
---
## 12. Was absichtlich fehlt (gelöscht / deaktiviert)
| Früher | Status |
|--------|--------|
| `Data/targets.sample.json` + JSON-Repository | entfernt Ziele aus KnownContainers |
| SQL-Repos / Schema | entfernt |
| WinRM / LocalCmd / stopcontainer.bat-Executor | entfernt |
| CertificateDeployer / TlsCertificateProbe | entfernt |
| XApi-Import | entfernt |
| Deploy-Button | UI ausgeblendet |
---
## 13. Schnell-Tabelle „Datei → Ein Satz“
| Datei | Ein Satz |
|-------|----------|
| `Program.cs` | Startet die App |
| `Form1.cs` | UI + Zertifikat + Neustart-Klick |
| `Form1.Designer.cs` | Leerer Designer-Stub |
| `Form1.resx` | Formular-Ressourcen |
| `appsettings.json` | Sonic-Verbindung + KnownContainers + Java/Libs |
| `*.csproj` | Build/Copy-Regeln |
| `Configuration/AppSettingsLoader.cs` | JSON → Settings-Objekt |
| `Models/AppSettings.cs` | Settings-Root |
| `Models/SonicConnection.cs` | Eine SMC-/Domain-Verbindung |
| `Models/DeploymentTarget.cs` | Ein Container-Ziel im Grid |
| `Models/CertificateInfo.cs` | Lokales Zertifikats-Ergebnis |
| `Models/DeploymentRunResult.cs` | Lauf-/Validierungs-DTOs |
| `Services/DeploymentOrchestrator.cs` | Neustart über alle Ziele |
| `Services/RestartExecutor.cs` | Ziel → SonicClient |
| `Services/PreflightValidator.cs` | Pflichtfelder prüfen |
| `Services/SonicManagementClient.cs` | Fassade ping/list/restart |
| `Services/SonicMfApiExecutor.cs` | Java-Prozess + Classpath |
| `Services/SonicContainerDiscovery.cs` | KnownContainers + Live-Liste |
| `Services/RunLogger.cs` | Datei-Logs |
| `Services/PathResolver.cs` | Relative Pfade |
| `Tools/SonicMfContainerTool.java` | Sonic Management API Aufrufe |
| `Tools/*.class` / `.jar` | Vorkompiliertes Tool |
| `Tools/verify-mfapi-classpath.bat` | Classpath-Selbsttest |
| `Docs/*.md` | Erklärungen |
| `Assets/*` | Logo / Testzertifikat |
| `Demo/*` | Demo-Zertifikat |
| `doc-10.0.10/*` | Offizielle Sonic/CX-Messenger-Doku |
---
*Ende der Dateiübersicht. Für Ablauf-Details siehe `Komplette-Erklaerung.md`.*
@@ -0,0 +1,530 @@
# Komplette Erklärung: ESB Certificate Manager
Was passiert **einzeln, von oben nach unten**.
Zertifikat prüfen · Ziele laden · Sonic-API · Container-Neustart.
Diese Datei ist zum Erklären (Präsentation, Kollegen, Übergabe).
Technischer Fokus: aktueller Stand mit **MfApi-Neustart** und **lokaler Zertifikatserkennung**.
---
# Teil A Was die Anwendung ist
## A1. Zweck in einem Satz
Die WinForms-App hilft dabei,
1. ein **Zertifikat als Datei zu erkennen und lokal zu prüfen**, und
2. einen **Sonic-/ESB-Container remote neu zu starten** so ähnlich wie der Restart-Button in der **Sonic Management Console (SMC)**.
## A2. Was sie bewusst *nicht* macht (aktuell)
| Nicht aktiv | Erklärung |
|-------------|-----------|
| Zertifikat auf den Server **kopieren** | Deploy-Button ist ausgeblendet |
| Nach dem Deploy **TLS gegen den Endpoint prüfen** | früher TLS-Probe entfernt/deaktiviert |
| **XApi** importieren | nicht mehr im Pfad |
| Neustart per **`stopcontainer.bat`** | oft in SMC-only-Installationen nicht vorhanden |
| Neustart per **WinRM / Windows-Login** | SMC-User ≠ Windows-User |
| Eigene **HTTP-REST-API** der App | die App *ist* kein Server; sie *ruft* Sonic auf |
## A3. Die wichtigsten Namen in eurer Umgebung
| Name | Typ | Beispiel |
|------|-----|----------|
| Verbindungs-Alias | Eintrag in `appsettings` | `DE-Test` |
| Domain | Sonic-Domain | `proalpha-test` |
| Container (kurz) | Laufzeit-Container | `ct-ZADBService` |
| ObjectName (JMX) | vollständige Agent-Adresse | `proalpha-test.ct-ZADBService:ID=AGENT` |
| ConnectionUrl | Domain-Manager wie in SMC | `tcp://dekun-painwbdet:13070` |
| Login | SMC-Credentials | `Administrator` / `Administrator` |
| Sonic-Libs | Client-JARs | `C:\DEV\MQ10.0\lib` |
| Java | Runtime für das Hilfstool | JRE 1.8+ |
**Merksatz:**
`DE-Test` = Verbindung. `ct-ZADBService` = Container. Nicht vertauschen.
---
# Teil B Programmstart (was zuerst passiert)
## B1. Start der App
1. `Program.cs` startet `Form1`.
2. `AppSettingsLoader` liest `appsettings.json` (aus dem Output-Ordner der App).
3. Es werden gebaut:
- `DeploymentOrchestrator` (Neustart-Orchestrierung)
- `SonicContainerDiscovery` (Container-Liste optional laden)
4. Die UI (Dark-Design) wird aufgebaut.
5. Beim ersten Anzeigen (`Shown`) werden Ziele geladen.
## B2. Ziele laden
1. Ziele kommen aus `appsettings.json``SonicConnections[].KnownContainers` (kein Offline-JSON).
2. Jede Zeile hat u. a.:
- Anzeigename (`DE-Test / ct-ZADBService`)
- Umgebung = Domain
- **ContainerName** (z. B. `ct-ZADBService`)
- **SonicConnectionName** (z. B. `DE-Test`)
- Status-Spalte
Optional: Button **„Container laden“** fragt Sonic live per API und merged mit `KnownContainers`.
---
# Teil C Zertifikat: Erkennen & Verifizieren (Schritt für Schritt)
Hier geht es **nur um die lokale Datei**. Es wird nichts auf den ESB geschrieben und kein TLS zum Server aufgebaut.
## C1. Benutzer wählt Datei
1. Klick auf Dateiauswahl in der UI.
2. `OpenFileDialog` filtert: `.cer`, `.crt`, `.pem`, `.pfx`.
3. Abbruch → nichts ändert sich.
4. Dateiendung wird geprüft (`IsSupportedCertificateFile`).
5. Unbekanntes Format → Warnung, Ende.
## C2. Datei lesen
1. Aufruf `TryReadSelectedCertificate(pfad)`.
2. Intern: `X509Certificate2` laden.
3. **PFX-Sonderfall:**
- Zuerst Versuch *ohne* Passwort.
- Bei `CryptographicException` → Passwort-Dialog.
- Falsches Passwort → Fehlermeldung, kein Zertifikat geladen.
4. Andere Fehler (Datei kaputt, kein X.509) → Fehlermeldung.
## C3. Was aus dem Zertifikat extrahiert wird
| Feld | Quelle | Anzeige |
|------|--------|---------|
| Subject | `GetNameInfo(SimpleName)` bzw. `Subject` | „Ausgestellt für“ |
| Issuer | Issuer-SimpleName bzw. `Issuer` | „Ausgestellt von“ |
| Gültig von / bis | `NotBefore` / `NotAfter` | Datum |
| Fingerprint | SHA-256 Hash, mit `:` formatiert | z. B. `AB:CD:…` |
| Gültigkeitsstatus | `now` zwischen NotBefore und NotAfter? | grün GÜLTIG / rot UNGÜLTIG |
## C4. Was „Verifizierung“ hier genau heißt
| Prüfung | Passiert? | Bedeutung |
|---------|-----------|-----------|
| Datei lesbar als X.509 | ja | Format/Inhalt ok |
| Zeitlich gültig | ja | nicht abgelaufen / noch nicht gültig |
| Fingerprint anzeigen | ja | manueller Abgleich möglich |
| Signaturkette gegen Windows-CA | **nein** | nicht implementiert |
| Datei auf ESB kopieren | **nein** | Deploy aus |
| TLS-Handshake zum Live-System + Fingerprint vergleichen | **nein** | TLS-Probe aus |
**Erklärsatz:**
> Wir verifizieren das Zertifikat **als Datei auf dem PC** (lesbar + Gültigkeitszeitraum + Fingerprint).
> Wir verifizieren **nicht**, dass genau dieses Zertifikat schon am laufenden ESB hängt.
> Der Neustart ist davon getrennt.
## C5. UI nach erfolgreichem Laden
1. Pfad steht im Textfeld.
2. Metadaten-Karte wird gefüllt.
3. Statuszeile: „Zertifikat ausgewählt: …“.
4. Sidebar-Schritt kann auf „Ziele“ springen.
5. Neustart braucht das Zertifikat **nicht zwingend** der Neustart hängt am Container, nicht an der Datei.
---
# Teil D Vor dem Neustart (Preflight)
## D1. Benutzer hakt Ziel(e) an
Im Grid: Checkbox „Selected“ setzen.
## D2. Button „ESB neu starten“
1. `GetSelectedTargets()` sammelt alle angehakten Zeilen.
2. `ValidateRestartOnly` prüft für jedes Ziel:
- `ContainerName` nicht leer
- `SonicConnectionName` nicht leer
3. Fehler → Dialog „Neustart blockiert“ mit Liste der Probleme.
4. OK → Bestätigungsdialog (Containername, Hinweis auf Management API).
5. Nein → Abbruch. Ja → Neustart startet.
---
# Teil E Neustart Gesamtablauf (C#-Seite)
Alles von hier läuft **ohne** Zertifikatskopieren.
```text
Form1.RunRestartOnlyAsync
→ DeploymentOrchestrator.RestartOnlyAsync
→ für jedes Ziel:
RestartExecutor.ExecuteAsync
→ SonicManagementClient.RestartContainerAsync
→ SonicMfApiExecutor.RestartAsync
→ java SonicMfContainerTool restart …
```
## E1. Orchestrator
Pro Ziel:
1. Status im Grid: „Neustart…“
2. `RestartExecutor.ExecuteAsync(target)`
3. Ergebnis + Detail in Logdatei unter `Logs\`
4. Status im Grid aktualisieren
5. Am Ende: Erfolgs-/Fehlerdialog mit Tool-Ausgabe
## E2. RestartExecutor
1. Liest `target.ContainerName` (z. B. `ct-ZADBService`).
2. Sucht in `appsettings` die `SonicConnection` mit `Name == SonicConnectionName` (`DE-Test`).
3. Fehlt die Verbindung → Fehler „Sonic-Verbindung nicht gefunden“.
4. Sonst: `new SonicManagementClient(connection)` und `RestartContainerAsync(containerName)`.
## E3. SonicManagementClient
Schlanke Fassade, nur MfApi:
1. Ruft `_mfApi.RestartAsync(containerName)` auf.
2. Bei Erfolg: wartet `PostRestartDelaySeconds` (z. B. 20 s), damit der Container Zeit zum Hochfahren hat.
3. Liefert Status-Text + komplette Java-Ausgabe zurück.
4. Bei Fehler: Fehlermeldung + Output für den Dialog.
---
# Teil F SonicMfApiExecutor: Java vorbereiten und starten
Das ist die Brücke von .NET zu Sonic.
## F1. Java finden
Reihenfolge grob:
1. `JavaPath` aus appsettings (bevorzugt)
2. `JavaHome`
3. Umgebungsvariablen / PATH / Registry / unter SonicHome
Bedingung: Runtime muss **Java 8+** können (Class-File major ≥ 52).
Zu alte Sonic-JREs (6/7) werden übersprungen.
## F2. Classpath bauen (Sonic-JARs)
1. `MfClientLibPath` (z. B. `C:\DEV\MQ10.0\lib`) oder Suche unter `SonicHome`.
2. Es müssen echte Client-JARs da sein, u. a.:
- `mgmt_client.jar`
- `mfcontext.jar`
- `sonic_Client.jar`
3. **Alle** `*.jar` im Lib-Ordner werden explizit in den Classpath geschrieben (kein fragiles `lib\*` mit kaputten Quotes).
4. Das Tool-Verzeichnis `Tools\` kommt **vorne** in den Classpath, damit die richtige `SonicMfContainerTool.class` geladen wird.
## F3. Java-Tool laden
1. Bevorzugt vorcompilierte `Tools\SonicMfContainerTool.class` (Java 8, major 52).
2. Alte/zu kleine Class-Dateien werden verworfen.
3. Temp-Cache alter Compilate wird gelöscht.
4. Falls nötig: aus `.java` mit `javac --release 8` neu bauen.
## F4. Prozess starten
Ungefähr:
```text
"C:\...\java.exe"
-cp "C:\...\Tools;C:\DEV\MQ10.0\lib\mgmt_client.jar;...;C:\DEV\MQ10.0\lib\mfcontext.jar;..."
SonicMfContainerTool
restart
--domain proalpha-test
--url tcp://dekun-painwbdet:13070
--user Administrator
--container ct-ZADBService
--timeout 120
```
Wichtig:
- Argumente über `ProcessStartInfo.ArgumentList` (keine doppelten Anführungszeichen).
- Passwort über Umgebung `ESB_SONIC_PASSWORD`, nicht in der sichtbaren Kommandozeile.
- stdout/stderr werden gelesen und an die UI zurückgegeben.
## F5. Erfolgsauswertung in C#
Neustart gilt nur als Erfolg, wenn die Ausgabe enthält:
```text
OK:RestartInvoked
```
Sonst: Fehlerdialog mit kompletter Tool-Ausgabe.
---
# Teil G Die Sonic Management Application API (Java-Tool)
Hier passiert der eigentliche „API-Call“ **nicht REST**, sondern **JMS/JMX Management Framework**.
Offizielle Produktwelt (Aurea CX Messenger / Sonic):
- Index: `doc-10.0.10/CXMessenger_2017_R3.htm`
- Eintrag: **Management Application API Reference** (`Docs2017/api/mgmt_api/`)
Das ist dieselbe API-Familie, die die SMC für Restart nutzt.
## G1. Warum „API“ hier kompliziert klingt
Die Sonic-Produkt-API enthält **sehr viele** Bereiche:
- Broker / Queues / Topics
- ESB-Services
- Metrics / Notifications
- Directory / Config
- Agent Lifecycle
**Für unseren Neustart brauchen wir nur einen schmalen Teil:**
1. Verbinden zum Domain Manager
2. Agent-ObjectName ansprechen
3. Lifecycle: stop/restart
Der Rest der JARs ist Produktumfang nicht „unsere App-API mit 100 Features“.
## G2. Schritt 1 Tool startet
Ausgabe:
```text
INFO:ToolVersion=2026-07-24c-mbean-stop
```
Wenn diese Zeile fehlt → oft noch eine **alte** `.class` im Einsatz.
## G3. Schritt 2 Argumente parsen
Pflicht:
- Befehl: `ping` | `list` | `restart`
- `--domain`
- `--url`
- `--user`
- bei `restart`: `--container`
- Passwort: `--password` oder `ESB_SONIC_PASSWORD`
## G4. Schritt 3 Connect (der eigentliche API-Login)
Java (vereinfacht, per Reflection):
```text
Hashtable env:
ConnectionURLs = tcp://dekun-painwbdet:13070
DefaultUser = Administrator
DefaultPassword = ***
JMSConnectorAddress address = new JMSConnectorAddress(env)
JMSConnectorClient connector = new JMSConnectorClient()
connector.connect(address, timeout)
```
Klassen kommen aus den Sonic-JARs:
- `com.sonicsw.mf.jmx.client.JMSConnectorAddress`
- `com.sonicsw.mf.jmx.client.JMSConnectorClient`
Ausgabe bei Erfolg:
```text
INFO:Connected url=tcp://dekun-painwbdet:13070 user=Administrator
```
Das entspricht dem SMC-Login gegen denselben Domain Manager.
## G5. Schritt 4 Ziel-Agent bestimmen
ObjectName:
```text
proalpha-test.ct-ZADBService:ID=AGENT
```
Muster:
```text
{DomainName}.{ContainerName}:ID=AGENT
```
Optional: per `queryNames` nach vorhandenen Agenten suchen; wenn das scheitert, Preferred-Name verwenden.
## G6. Schritt 5 Lifecycle (Neustart) drei Wege nacheinander
### Weg 1 MBean invoke (bevorzugt remote)
```text
connector.invoke(objectName, "stop", [], [])
// oder "restart", "shutdown"
```
Warum zuerst `stop`?
- Remote-Clients sind oft **„unbounded“**.
- `IAgentProxy.restart()` wirft dann häufig:
`Operation unsupported for unbounded client connector`
- SMC-Restart wirkt für den Operator oft wie: **Stop + automatischer Relaunch** durch Launch Daemon / Host Manager.
Erfolg:
```text
OK:RestartInvoked method=MBean.stop container=ct-ZADBService domain=proalpha-test
```
### Weg 2 Typisierter Proxy
```text
IAgentProxy agent = MFProxyFactory.createAgentProxy(connector, objectName)
agent.stop() / agent.restart() / agent.shutdown()
```
Schön dokumentiert, remote oft eingeschränkt (unbounded).
### Weg 3 DomainManager / Manager-MBeans
Suche nach Operationen wie:
- `restartContainer(containerName)`
- `stopContainer(containerName)`
Fallback, falls Agent-direkt nicht klappt.
## G7. Schritt 6 Disconnect
`connector.disconnect()` im `finally`.
## G8. Befehle des Tools im Überblick
| Befehl | Zweck |
|--------|--------|
| `ping` | Verbindungstest + Agenten zählen |
| `list` | Containernamen der Domain ausgeben |
| `restart` | Lifecycle auf einem Container |
---
# Teil H Komplette Kette als nummerierte Liste
So kannst du es jemandem erzählen:
### Zertifikat
1. App starten
2. Zertifikatsdatei wählen
3. Datei als X.509 lesen (PFX ggf. mit Passwort)
4. Subject, Issuer, Fingerprint, Gültigkeit anzeigen
5. Lokal „verifiziert“ = lesbar + im Gültigkeitszeitraum
### Neustart
6. Ziel im Grid anhaken (`ct-ZADBService` / Verbindung `DE-Test`)
7. „ESB neu starten“ → Preflight → Bestätigung
8. C# findet Sonic-Verbindung in appsettings
9. C# findet Java 8+ und baut Classpath aus `C:\DEV\MQ10.0\lib`
10. C# startet `SonicMfContainerTool restart …`
11. Java verbindet per Management API an `tcp://…:13070`
12. Java adressiert `proalpha-test.ct-ZADBService:ID=AGENT`
13. Java ruft Stop/Restart per MBean (Fallback Proxy/Manager)
14. Java schreibt `OK:RestartInvoked`
15. C# wartet `PostRestartDelaySeconds`
16. UI zeigt Erfolg/Fehler + Logausgabe
17. Optional in SMC prüfen: Uptime/Status des Containers
---
# Teil I Konfiguration im Detail
## I1. `appsettings.json` (Sonic-Block)
| Feld | Rolle |
|------|--------|
| `Name` | Alias, von Zielen referenziert (`DE-Test`) |
| `DomainName` | Sonic-Domain |
| `ConnectionUrl` | Domain-Manager-URL (wie SMC) |
| `Username` / `Password` | SMC-Login |
| `SonicHome` | Installationsroot |
| `MfClientLibPath` | Ordner mit Client-JARs |
| `JavaPath` / `JavaHome` | Java 8+ |
| `KnownContainers` | Fallback-Liste, z. B. `ct-ZADBService` |
| `TimeoutSeconds` | Timeout Java/API |
| `PostRestartDelaySeconds` | Wartezeit nach erfolgreichem Invoke |
Felder wie `WinRm*`, `Container*Path`, `ManagementHttpPort` können noch in der JSON stehen, werden vom schlanken MfApi-Client **nicht mehr genutzt**.
## I2. Container-Liste
Kein separates Sample-JSON. Container stehen in:
```json
"KnownContainers": [ "ct-ZADBService" ]
```
Live-Erweiterung über UI-Button „Container laden“.
---
# Teil J Dateien und Verantwortlichkeiten
| Datei | Verantwortung |
|-------|----------------|
| `Form1.cs` | UI, Zertifikat lesen, Neustart-Button, Dialoge |
| `DeploymentOrchestrator.cs` | Neustart über alle gewählten Ziele |
| `PreflightValidator.cs` | Pflichtfelder prüfen |
| `RestartExecutor.cs` | Ziel → SonicConnection |
| `SonicManagementClient.cs` | Fassade MfApi |
| `SonicMfApiExecutor.cs` | Java/Classpath/Prozess |
| `Tools/SonicMfContainerTool.java` | Connect + Lifecycle über Sonic-API |
| `appsettings.json` | Laufzeitkonfiguration inkl. KnownContainers |
| `Logs\` | Laufprotokolle |
---
# Teil K Erfolgs- und Fehlerbilder
## K1. Gesunde Ausgabe (Auszug)
```text
INFO:ToolVersion=2026-07-24c-mbean-stop
INFO:Connected url=tcp://dekun-painwbdet:13070 user=Administrator
INFO:PreferredObjectName=proalpha-test.ct-ZADBService:ID=AGENT
INFO:Trying MBean.invoke(stop) on …
OK:RestartInvoked method=MBean.stop container=ct-ZADBService domain=proalpha-test
```
## K2. Häufige Fehler
| Meldung | Bedeutung | Typische Lösung |
|---------|-----------|-----------------|
| `ClassNotFoundException: JMSConnectorAddress` | JARs fehlen / Classpath kaputt | `MfClientLibPath`, neuer Build |
| `UnsupportedClassVersionError` | Java-Version mismatch | Tool als Java 8 + Runtime Java 8+ |
| `Operation unsupported for unbounded…` | Proxy-Restart remote gesperrt | MBean-stop-Pfad / aktuelles Tool |
| Kein `ToolVersion=` | Alte Class-Datei | Neu bauen, richtigen bin-Ordner starten |
| Container `DE-Test` | Alias statt Kurzname | `ct-ZADBService` |
| `stopcontainer.bat` fehlt | SMC-only | normal MfApi nutzen |
---
# Teil L Wie man es jemandem erklärt (Kurzskript)
1. **UI:** Zertifikat laden → wir sehen Subject/Fingerprint/Gültigkeit. Das ist lokale Dateiprüfung.
2. **Neustart:** Wir starten denselben Management-Weg wie die SMC, nicht per Batch-Datei und nicht per FTP.
3. **Technik:** C# orchestriert; ein kleines Java-Tool + Sonic-Client-JARs sprechen mit dem Domain Manager.
4. **API:** Sonic Management Application API (JMS/JMX). Sehr große Produkt-API, wir nutzen nur Connect + Agent-Stop/Restart.
5. **Haken:** Remote heißt „unbounded“ deshalb oft `stop` per MBean statt `IAgentProxy.restart()`.
6. **Ergebnis:** Container `ct-ZADBService` in Domain `proalpha-test` wird neu gestartet; danach in der SMC Status/Uptime prüfen.
---
# Teil M Abgrenzung: Was ist „unsere API“ vs. Sonic-API?
| Ebene | Beschreibung |
|-------|--------------|
| **Unsere App** | WinForms + C#-Services. Kein öffentlicher REST-Endpoint. |
| **Unser Java-Tool** | Dünner Adapter (`ping`/`list`/`restart`) mit Fallbacks. |
| **Sonic Management Application API** | Produkt-API in den JARs; offiziell dokumentiert; SMC nutzt dieselbe Familie. |
| **Unnötig für den Use-Case** | MQ Messaging API, ESB API, Metrics, XApi, WinRM, Bat-Scripts, TLS-Probe solange nur „Datei prüfen + Neustart“ gefragt ist. |
---
*Ende der kompletten Erklärung. Bei Abweichungen gilt der Code im Projekt (Branch mit MfApi-Neustart).*
@@ -0,0 +1,507 @@
# MfApi-Restart vollständige Erklärung
Stand: Tool-Version `2026-07-24d-restart-only`
Zweck dieses Dokuments: erklären, **wie die Sonic Management Application API (MfApi) in dieser App genutzt wird**, Schicht für Schicht von Button-Klick bis MBean-Aufruf.
---
## 1. Was ist „die API“ hier?
Es gibt **keine eigene REST-API** der WinForms-App.
Gemeint ist die **Sonic / Aurea MF Management Application API** (wie in der Sonic Management Console, SMC):
| Begriff | Bedeutung |
|---------|-----------|
| **MfApi** | Management Framework API über JMS/JMX-Client |
| **Domain Manager** | zentrale Verwaltung der Sonic-Domain |
| **Container / Agent** | laufende ESB-Instanz, z.B. `ct-ZADBService` |
| **SMC** | Sonic Management Console (UI mit denselben Credentials/URL) |
| **MBean** | verwaltbares Objekt im Domain Manager (`ObjectName`) |
Die App startet lokal einen **Java-Prozess**, der die offiziellen Sonic-Client-JARs lädt und denselben Neustart-Intent ausführt wie **Restart** in der SMC.
### Warum Java und nicht reines .NET?
Die Sonic-Client-Bibliotheken (`mgmt_client.jar`, `mfcontext.jar`, `sonic_Client.jar`, …) sind **Java**.
C# orchestriert nur: Konfiguration lesen → `java.exe` starten → stdout auswerten.
---
## 2. Was die App bewusst *nicht* mehr macht
Nach dem Slimming ist die API-Schicht **nur Restart**:
| Entfernt | Früher |
|----------|--------|
| `ping` | Verbindungstest über Agent-Query |
| `list` | Live-Containerliste aus dem Domain Manager |
| IAgentProxy-Fallback | oft „Operation unsupported for unbounded client connector“ |
| DomainManager-Scan | `restartContainer` / Manager-MBeans |
| WinRM / `stopcontainer.bat` | lokaler Script-Pfad |
| HTTP-REST-Pfade | ungenutzt |
Container kommen ausschließlich aus **`appsettings.json``KnownContainers`**.
---
## 3. Architektur-Überblick
```text
┌─────────────────────────────────────────────────────────────────┐
│ WinForms UI (Form1) │
│ Button „ESB neu starten“ │
└────────────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ DeploymentOrchestrator │
│ Preflight → Schleife über Ziele → Logging / Progress │
└────────────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ RestartExecutor │
│ Ziel → SonicConnection (Name-Match) │
└────────────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ SonicManagementClient │
│ Restart + PostRestartDelay │
└────────────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ SonicMfApiExecutor (.NET) │
│ Java finden, Classpath bauen, Process starten, Output prüfen │
└────────────────────────────┬────────────────────────────────────┘
│ Process: java -cp … SonicMfContainerTool restart …
┌─────────────────────────────────────────────────────────────────┐
│ SonicMfContainerTool (Java) │
│ connect → MBean.invoke(stop|restart) → OK:RestartInvoked │
└────────────────────────────┬────────────────────────────────────┘
│ tcp://…:13070 (JMS Management)
┌─────────────────────────────────────────────────────────────────┐
│ Sonic Domain Manager / Broker │
│ ObjectName: proalpha-test.ct-ZADBService:ID=AGENT │
│ Launch Daemon startet Container nach stop ggf. neu │
└─────────────────────────────────────────────────────────────────┘
```
---
## 4. Konfiguration (`appsettings.json`)
Beispiel (aktuell):
```json
{
"LogDirectory": "Logs",
"SonicConnections": [
{
"Name": "DE-Test",
"DomainName": "proalpha-test",
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
"Username": "Administrator",
"Password": "Administrator",
"SonicHome": "C:\\DEV\\MQ10.0",
"JavaHome": "C:\\Program Files (x86)\\Java\\jre1.8.0_501",
"JavaPath": "C:\\Program Files (x86)\\Java\\jre1.8.0_501\\bin\\java.exe",
"MfClientLibPath": "C:\\DEV\\MQ10.0\\lib",
"KnownContainers": [ "ct-ZADBService" ],
"TimeoutSeconds": 120,
"PostRestartDelaySeconds": 20
}
]
}
```
### Felder erklärt
| Feld | Rolle |
|------|--------|
| **Name** | Alias der Verbindung. `DeploymentTarget.SonicConnectionName` muss dazu passen (hier `DE-Test`). |
| **DomainName** | Sonic-Domain (`proalpha-test`). Teil des JMX-`ObjectName`. |
| **ConnectionUrl** | Dieselbe TCP-URL wie in der SMC (`tcp://host:port`). |
| **Username / Password** | SMC-Login (nicht Windows/WinRM). Passwort geht als Env `ESB_SONIC_PASSWORD` an Java. |
| **SonicHome** | Installationsroot; Fallback für `lib\*.jar`, wenn `MfClientLibPath` leer/unbrauchbar. |
| **JavaPath** | Bevorzugter Pfad zu `java.exe` (**Java 8+**, Class major ≥ 52). |
| **JavaHome** | Alternativ `JavaHome\bin\java.exe`. |
| **MfClientLibPath** | Ordner mit Client-JARs (`mgmt_client.jar`, `mfcontext.jar`, …). |
| **KnownContainers** | Liste der Container-**Kurznamen** für die Grid-Ziele. |
| **TimeoutSeconds** | Timeout für Java-Prozess und Tool-Connect (5600 s, Clamp in Code). |
| **PostRestartDelaySeconds** | Wartezeit **nach** erfolgreichem `OK:RestartInvoked` (0120 s), bevor UI „fertig“ meldet. |
### Wichtige Namensunterscheidung
| Name | Beispiel | Ist |
|------|----------|-----|
| Verbindungs-Alias | `DE-Test` | Eintrag in `SonicConnections[].Name` |
| Domain | `proalpha-test` | Sonic-Domain |
| Container | `ct-ZADBService` | Agent / Container in SMC |
Häufiger Fehler: Alias `DE-Test` als Containername verwenden. Der ObjectName wäre dann falsch.
Korrekt:
```text
proalpha-test.ct-ZADBService:ID=AGENT
```
---
## 5. Ziele (DeploymentTarget)
Beim Start baut `SonicContainerDiscovery.BuildTargetsFromConfig()` aus jeder Connection und jedem `KnownContainers`-Eintrag ein Ziel:
| Property | Quelle / Wert |
|----------|----------------|
| `Name` | `"DE-Test / ct-ZADBService"` |
| `Environment` | `DomainName` |
| `ContainerName` | `ct-ZADBService` |
| `SonicConnectionName` | `DE-Test` |
| `RestartType` | `SonicContainer` |
Es gibt **keinen Live-Query** mehr („Container laden“ wurde entfernt).
---
## 6. Aufrufkette jede Methode
### 6.1 UI `Form1`
| Schritt | Methode / Ereignis | Aufgabe |
|---------|-------------------|---------|
| 1 | `btnRestartOnly.Click` | Startet `RunRestartOnlyAsync()` |
| 2 | `GetSelectedTargets()` | Angehakte Grid-Zeilen |
| 3 | `_orchestrator.ValidateRestartOnly(...)` | Vorabprüfung |
| 4 | MessageBox-Bestätigung | Nutzer bestätigt Neustart |
| 5 | `_orchestrator.RestartOnlyAsync(...)` | eigentlicher Lauf |
| 6 | Grid / Statuszeile | Ergebnis anzeigen (kein Erfolgs-Popup) |
### 6.2 `PreflightValidator.ValidateRestartOnly`
Prüft grob:
- mindestens ein Ziel gewählt
- `ContainerName` gesetzt
- `SonicConnectionName` gesetzt
Fehler → Abbruch **vor** Java.
### 6.3 `DeploymentOrchestrator`
| Methode | Aufgabe |
|---------|---------|
| `ValidateRestartOnly` | reicht an Preflight durch |
| `RestartOnlyAsync` | Log öffnen, alle gewählten Ziele nacheinander |
| `RestartTargetOnlyAsync` | Progress „Neustart…“ → Executor → `TargetStepResult` |
Kein Deploy, kein TLS, kein SQL.
### 6.4 `RestartExecutor.ExecuteAsync`
1. `ContainerName` prüfen
2. `SonicConnection` per `Name == target.SonicConnectionName` finden
3. `new SonicManagementClient(connection)`
4. `RestartContainerAsync(target.ContainerName)`
### 6.5 `SonicManagementClient.RestartContainerAsync`
1. `_mfApi.RestartAsync(containerName)`
2. Bei Erfolg: `Task.Delay(PostRestartDelaySeconds)`
3. Status/Detail-String für UI/Log zurückgeben
Die Verzögerung gibt dem Launch Daemon Zeit, den Container wieder hochzufahren. Sie ist **kein** aktiver SMC-Status-Poll.
### 6.6 `SonicMfApiExecutor` die C#-Brücke
#### `PrepareTool()`
1. **Java** über `ResolveJavaExe()`:
- `JavaPath`
- `JavaHome\bin\java.exe`
- `JAVA_HOME` / `JRE_HOME`
- `java.exe` aus PATH
2. **Classpath** über `ResolveSonicClasspath()`:
- alle `*.jar` aus `MfClientLibPath`, sonst `SonicHome\lib` / `SonicHome`
- bevorzugte Reihenfolge: `mgmt_client.jar`, `mfcontext.jar`, `sonic_Client.jar`, `sonic_Crypto.jar`, `mf_common.jar`, dann Rest
3. **Tool-Verzeichnis** mit `SonicMfContainerTool.class` (`Tools\` neben der EXE)
Ergebnis-Classpath grob:
```text
<App>\Tools;<MfClientLibPath>\mgmt_client.jar;...;<weitere jars>
```
#### `RestartAsync(containerName)`
Startet:
```text
java -cp "<Tools>;<jars>" SonicMfContainerTool restart
--domain proalpha-test
--url tcp://dekun-painwbdet:13070
--user Administrator
--container ct-ZADBService
--timeout 120
```
- Argumente über `ProcessStartInfo.ArgumentList` (keine manuellen Quotes → sonst Classpath-Bugs)
- Passwort: Umgebungsvariable `ESB_SONIC_PASSWORD`
- stdout/stderr lesen, auf Timeout killen
#### Erfolgskriterium in C#
Alles muss gelten:
1. `ExitCode == 0`
2. Ausgabe enthält `OK:RestartInvoked`
3. Ausgabe enthält **kein** `ERROR:`
Sonst Fehlertext (+ Classpath-Hinweis bei `ClassNotFoundException` / `JMSConnectorAddress`).
---
## 7. Java-Tool der eigentliche API-Call
Datei: `Tools/SonicMfContainerTool.java`
Bytecode: `Tools/SonicMfContainerTool.class` (Java 8 / major 52)
Version-Stamp: `TOOL_VERSION = "2026-07-24d-restart-only"` (erste INFO-Zeile)
### 7.1 `main`
1. `INFO:ToolVersion=…` ausgeben
2. Args parsen nur Befehl `restart` erlaubt
3. Pflicht: `--domain`, `--url`, `--user`, `--container`
4. Passwort: `--password` oder Env `ESB_SONIC_PASSWORD`
5. `connect(...)`
6. `restart(...)`
7. `disconnect` im `finally`
### 7.2 `connect` Einloggen wie SMC
```text
Hashtable env:
ConnectionURLs = tcp://…
DefaultUser = Administrator
DefaultPassword = …
JMSConnectorAddress(env)
JMSConnectorClient()
optional: setTimeout / setRequestTimeout
client.connect(address [, timeout])
```
Klassen (aus den JARs, per Reflection):
- `com.sonicsw.mf.jmx.client.JMSConnectorAddress`
- `com.sonicsw.mf.jmx.client.JMSConnectorClient`
Reflection statt direkter Compile-Abhängigkeit: das Tool kompiliert ohne Sonic-JARs auf dem Build-Rechner; zur Laufzeit müssen die JARs im Classpath liegen.
### 7.3 `restart` ObjectName und Lifecycle
1. Kurzname bilden (`proalpha-test.ct-ZADBService``ct-ZADBService`)
2. ObjectName:
```text
<DomainName>.<ContainerKurzname>:ID=AGENT
→ proalpha-test.ct-ZADBService:ID=AGENT
```
3. Operationen der Reihe nach:
| Versuch | Operation | Bedeutung |
|---------|-----------|-----------|
| 1 | `stop` | Agent stoppen (SMC macht oft faktisch Stop; Daemon startet neu) |
| 2 | `restart` | falls vorhanden und erlaubt |
4. Aufruf:
```text
connector.invoke(objectName, op, new Object[0], new String[0])
```
Das ist der JMX/`MBeanServerConnection.invoke`-Weg über den Sonic-Management-Connector.
### 7.4 Side-Effect bei `stop`
Wenn `stop` eine Exception wirft, deren Message auf Verbindungsabbruch hindeutet (`disconnect`, `closed`, `not connected`, `connection lost`), wertet das Tool das als **Erfolg**:
```text
OK:RestartInvoked method=MBean.stop(side-effect) …
```
Grund: der Agent geht runter und reißt oft die Management-Session das ist erwartbar, kein „echter“ Fehlschlag.
### 7.5 Erfolgszeile
```text
OK:RestartInvoked method=MBean.stop container=ct-ZADBService domain=proalpha-test tool=2026-07-24d-restart-only
```
C# sucht genau nach `OK:RestartInvoked`.
### 7.6 Ausgabe-Konventionen
| Präfix | Bedeutung |
|---------|-----------|
| `INFO:` | Fortschritt / ObjectName / Trying … |
| `WARN:` | fehlgeschlagener Versuch, Weiterversuch |
| `ERROR:` | fatal, ExitCode ≠ 0 |
| `OK:RestartInvoked` | Erfolg |
---
## 8. Sequenz (zeitlich)
```mermaid
sequenceDiagram
participant UI as Form1
participant Orch as DeploymentOrchestrator
participant RE as RestartExecutor
participant MC as SonicManagementClient
participant EX as SonicMfApiExecutor
participant JV as SonicMfContainerTool
participant DM as Sonic Domain Manager
UI->>Orch: RestartOnlyAsync(selected)
Orch->>RE: ExecuteAsync(target)
RE->>MC: RestartContainerAsync(ct-ZADBService)
MC->>EX: RestartAsync(...)
EX->>EX: PrepareTool (java + jars + class)
EX->>JV: Process start restart …
JV->>DM: JMSConnectorClient.connect
JV->>DM: invoke(…:ID=AGENT, "stop")
DM-->>JV: ok / disconnect side-effect
JV-->>EX: OK:RestartInvoked
EX-->>MC: Success=true
MC->>MC: Delay PostRestartDelaySeconds
MC-->>UI: Status + Detail
```
---
## 9. Dateien und Verantwortlichkeiten
| Datei | Rolle |
|-------|--------|
| `Form1.cs` | UI, Auswahl, Bestätigung, Status |
| `Services/DeploymentOrchestrator.cs` | Lauf orchestrieren, Log |
| `Services/PreflightValidator.cs` | Vorabprüfung |
| `Services/RestartExecutor.cs` | Ziel → Connection |
| `Services/SonicManagementClient.cs` | Restart + Delay |
| `Services/SonicMfApiExecutor.cs` | Java-Prozess, Classpath, Erfolgsauswertung |
| `Services/SonicContainerDiscovery.cs` | KnownContainers → Grid-Ziele |
| `Models/SonicConnection.cs` | Konfigurationsmodell |
| `Models/DeploymentTarget.cs` | Zielzeile |
| `Tools/SonicMfContainerTool.java` | API-Client (Quellcode) |
| `Tools/SonicMfContainerTool.class` | ausgeliefert / Java-8-Bytecode |
| `appsettings.json` | Verbindungen + Containerliste |
---
## 10. Laufzeit-Voraussetzungen
1. **Java 8+** erreichbar (`JavaPath` empfohlen)
2. **`MfClientLibPath`** zeigt auf Ordner mit Sonic-Client-JARs
3. **`Tools\SonicMfContainerTool.class`** liegt neben der gebauten App
4. Netzwerk zu `ConnectionUrl` (Port z.B. 13070)
5. Gültige SMC-Credentials
6. Containername stimmt mit SMC überein (`ct-ZADBService`)
7. Domain Manager / Launch Daemon müssen Container nach Stop wieder starten können
### Tool neu kompilieren
```text
javac --release 8 -encoding UTF-8 Tools\SonicMfContainerTool.java
```
(oder `-source 1.8 -target 1.8`)
Danach App neu bauen, damit `.class` nach `bin\…\Tools\` kopiert wird (csproj Content-Copy).
---
## 11. Typische Fehler und Ursache
| Symptom | Ursache | Maßnahme |
|---------|---------|----------|
| `ClassNotFoundException: JMSConnectorAddress` | Classpath ohne MF-JARs / falsche Quotes | `MfClientLibPath` prüfen; `ArgumentList` nicht manuell quoten |
| `UnsupportedClassVersionError` | Runtime zu alt oder `.class` zu neu | Java 8+ Runtime; Tool mit `--release 8` bauen |
| `Java nicht gefunden` | `JavaPath` falsch | appsettings korrigieren |
| `Tools\SonicMfContainerTool.class nicht gefunden` | Build/Copy fehlt | Projekt neu bauen |
| `Neustart fehlgeschlagen` + Attempts | MBean `stop`/`restart` abgelehnt | ObjectName/Rechte/Containerstatus in SMC prüfen |
| Timeout | Domain Manager hängt / Netz | `TimeoutSeconds`, Firewall, Broker |
| Falscher Container | Alias statt Kurzname | `KnownContainers`: `ct-ZADBService` |
| Kein `ToolVersion=` in Output | alte/falsche `.class` | Tools neu kompilieren, Output-Ordner prüfen |
---
## 12. Verhältnis zur SMC
| SMC | Diese App |
|-----|-----------|
| Login mit User/Pass auf Domain-URL | dieselben Werte in appsettings |
| Container im Baum wählen | `KnownContainers` + Grid |
| Rechtsklick → Restart | `MBean.invoke("stop"|"restart")` auf `…:ID=AGENT` |
| UI zeigt Online/Offline | App wartet nur `PostRestartDelaySeconds`, pollt Status nicht |
Operativ oft: **Stop am Agent-MBean**; der **Launch Daemon** bringt den Container wieder hoch analog zu vielen SMC-Restart-Verhalten bei remote „unbounded“ Clients.
Früher getestet: `IAgentProxy.restart()` schlägt remote häufig mit
`Operation unsupported for unbounded client connector` fehl deshalb der direkte MBean-Weg.
---
## 13. Sicherheitshinweise
- Passwort steht in `appsettings.json` (Klartext) und kurzzeitig in der Prozess-Umgebung `ESB_SONIC_PASSWORD`.
- Nicht in öffentliche Repos committen bzw. Secrets auslagern.
- Logs (`LogDirectory`) können Tool-Output enthalten ggf. sensible Zeilen beachten.
---
## 14. Manueller Test (ohne UI)
Mit denselben Werten wie in appsettings:
```text
set ESB_SONIC_PASSWORD=Administrator
java -cp "Tools;C:\DEV\MQ10.0\lib\*" SonicMfContainerTool restart ^
--domain proalpha-test ^
--url tcp://dekun-painwbdet:13070 ^
--user Administrator ^
--container ct-ZADBService ^
--timeout 120
```
Erwartet u.a.:
```text
INFO:ToolVersion=2026-07-24d-restart-only
INFO:ObjectName=proalpha-test.ct-ZADBService:ID=AGENT
INFO:Trying MBean.invoke(stop)
OK:RestartInvoked method=MBean.stop container=ct-ZADBService domain=proalpha-test tool=2026-07-24d-restart-only
```
(Unter Windows Classpath ggf. mit `;` und expliziten JAR-Namen statt `*`, je nach Shell.)
---
## 15. Kurzfassung
1. UI wählt Ziel aus `KnownContainers`.
2. C# findet die passenden Sonic-Verbindungsdaten.
3. C# startet Java mit MF-Client-JARs + `SonicMfContainerTool`.
4. Java verbindet sich wie die SMC an den Domain Manager.
5. Java ruft auf dem Agent-MBean `stop` (sonst `restart`) auf.
6. Bei `OK:RestartInvoked` wartet C# noch `PostRestartDelaySeconds` und meldet Erfolg.
**Der einzige echte Sonic-API-Call der App ist:**
`JMSConnectorClient.invoke(<domain>.<container>:ID=AGENT, "stop"|"restart", …)`.
@@ -0,0 +1,371 @@
# ESB Certificate Manager Neustart, Management-API & Zertifikatsprüfung
Ausführliche Erklärung für Präsentation, Übergabe und Troubleshooting.
Stand: Branch `fix/mfapi-classpath-ct-zadbservice` (MfApi-Neustart, Zertifikat erkennen).
---
## 1. Was die App heute macht (und was nicht)
### Aktiv
| Funktion | Kurzbeschreibung |
|----------|------------------|
| **Zertifikat erkennen** | Datei wählen (`.cer` / `.crt` / `.pem` / `.pfx`), Metadaten anzeigen, Gültigkeit prüfen |
| **Container laden** | Optional: Liste aus Sonic Domain Manager + Fallback `KnownContainers` |
| **ESB neu starten** | Container-Neustart über Sonic **Management Application API** (wie Restart in der SMC) |
### Bewusst nicht (mehr) aktiv
| Entfernt / deaktiviert | Warum |
|------------------------|--------|
| Zertifikat auf Zielpfad **kopieren** (Deploy) | UI-Button ausgeblendet; war eigener Deploy-Pfad |
| **TLS-Probe** nach Deploy | Fingerprint gegen laufenden Endpoint nicht Teil des aktuellen Scope |
| **XApi-Import** | Extra-Skript/WinRM-Pfad, oft nicht vorhanden |
| **WinRM** / `stopcontainer.bat` | SMC-only-Installationen haben die Server-Scripts oft nicht; Logins ≠ Windows |
| **HTTP-REST** Management | Kein stabiler Endpoint in dieser Umgebung nachweisbar |
| **SQL- / Offline-Sample-Ziele** | Container kommen aus `KnownContainers` / Live-Discovery |
Kurz: **Datei prüfen + Neustart**. Alles andere war Ballast für den aktuellen Use-Case.
---
## 2. Begriffe (wichtig für die Erklärung)
| Begriff | Bedeutung in diesem Projekt |
|---------|-----------------------------|
| **SMC** | Sonic Management Console die GUI, mit der Operatoren Container starten/stoppen/neustarten |
| **Domain** | Logische Sonic-Domain, hier: `proalpha-test` |
| **Container** | Laufzeit-Container / Agent, hier: `ct-ZADBService` |
| **Verbindungs-Alias** | Name in `appsettings` (`DE-Test`) **nicht** der Containername |
| **ObjectName** | JMX-Name: `proalpha-test.ct-ZADBService:ID=AGENT` |
| **Domain Manager / Broker-URL** | `tcp://dekun-painwbdet:13070` dieselbe URL wie beim SMC-Login |
| **MfApi** | Unser Modusname für „Management Application API“ über Java-Client-JARs |
| **unbounded client connector** | Remote-JMS-Client (wie die SMC von außen). Manche Proxy-Methoden (z.B. `IAgentProxy.restart()`) sind dafür gesperrt |
| **Launch Daemon** | Prozess auf dem Host, der einen gestoppten Container oft automatisch wieder startet (SMC „Restart“ ≈ Stop + Auto-Relaunch) |
**Merksatz für Präsentationen:**
> `DE-Test` ist die Verbindung. `ct-ZADBService` ist der Container. Die Domain heißt `proalpha-test`.
---
## 3. Architektur Überblick
```text
┌─────────────────────────────────────────────────────────────┐
│ WinForms UI (Form1) │
│ - Zertifikat wählen / anzeigen │
│ - Ziele anhaken │
│ - Button „ESB neu starten“ │
└───────────────────────────┬─────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ DeploymentOrchestrator.RestartOnlyAsync │
│ → PreflightValidator (ContainerName + SonicConnection) │
│ → RestartExecutor │
└───────────────────────────┬─────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SonicManagementClient (dünne Fassade, nur MfApi) │
│ → SonicMfApiExecutor │
└───────────────────────────┬─────────────────────────────────┘
│ startet Prozess
┌─────────────────────────────────────────────────────────────┐
│ java.exe (Java 8+) │
│ Classpath: Tools\ + C:\DEV\MQ10.0\lib\*.jar │
│ Klasse: SonicMfContainerTool │
│ Befehl: restart --domain … --url … --user … --container …│
└───────────────────────────┬─────────────────────────────────┘
│ JMS / JMX (Management Framework)
┌─────────────────────────────────────────────────────────────┐
│ Sonic Domain Manager │
│ tcp://dekun-painwbdet:13070 │
│ Agent: proalpha-test.ct-ZADBService:ID=AGENT │
└─────────────────────────────────────────────────────────────┘
```
Es gibt **keine eigene REST-API der WinForms-App**.
Die „API“ ist die **Sonic Management Application API** (Java, JARs unter `MQ10.0\lib`), dieselbe Schicht, die auch die SMC nutzt.
Offizielle Doku-Verweise (Index im Repo):
- `doc-10.0.10/CXMessenger_2017_R3.htm`**Management Application API Reference** (`Docs2017/api/mgmt_api/`)
- Bücher: MQ/ESB Configuration and Management Guide
(Die PDF-/HTML-API-Ordner können im Checkout fehlen der Index verlinkt sie trotzdem.)
---
## 4. Zertifikat Erkennen & „Verifizieren“
### 4.1 Was wir heute tun
Die App **liest und prüft die Zertifikatsdatei lokal**. Sie spielt das Zertifikat **nicht** auf den ESB und prüft **nicht** per TLS-Handshake gegen einen Server (dieser Pfad ist deaktiviert).
Ablauf in der UI (`Form1`):
1. Benutzer wählt Datei (`.cer`, `.crt`, `.pem`, `.pfx`).
2. Datei wird mit `X509Certificate2` geladen.
3. Bei `.pfx` ggf. Passwort-Dialog.
4. Anzeige:
- Subject
- Issuer
- Gültig bis
- SHA-256-Fingerprint (formatiert mit `:`)
- Status **GÜLTIG** / **UNGÜLTIG / ABGELAUFEN** (Vergleich mit `DateTimeOffset.Now` und `NotBefore`/`NotAfter`)
Das ist **Dateiverifikation / Metadaten-Check**, kein Deploy und kein Remote-TLS-Verify.
### 4.2 Was „Zertifikat verifizieren“ bedeuten kann (für die Erklärung)
| Stufe | Bedeutung | Status in dieser App |
|-------|-----------|----------------------|
| **A. Datei lesen** | Datei ist ein gültiges X.509, Parsing ok | ✅ aktiv |
| **B. Zeitliche Gültigkeit** | `now` zwischen NotBefore und NotAfter | ✅ aktiv |
| **C. Fingerprint anzeigen** | SHA-256 zum manuellen Abgleich | ✅ aktiv |
| **D. Auf Ziel kopieren** | Datei nach `TargetDirectory` legen | ❌ deaktiviert |
| **E. TLS-Probe** | TCP/TLS zum Host:Port, Remote-Zertifikat lesen, Fingerprint vergleichen | ❌ entfernt / nicht aktiv |
| **F. Trust-Chain / CA** | Kette gegen Windows-Truststore prüfen | ❌ nicht implementiert |
Wenn jemand fragt „Wird das Zertifikat verifiziert?“:
> Wir verifizieren die **Datei lokal** (lesbar, Gültigkeitszeitraum, Fingerprint).
> Ein Abgleich gegen den **laufenden ESB-Endpoint per TLS** ist aktuell nicht Teil der App.
> Der Neustart betrifft den **Container**, nicht automatisch die Zertifikatsinstallation.
### 4.3 Typische Demo-Sätze
- „Hier lade ich das Zertifikat Subject/Issuer/Fingerprint erscheinen sofort.“
- „Rot = abgelaufen oder außerhalb des Gültigkeitsfensters.“
- „Den Fingerprint kann man mit dem erwarteten Wert aus der Doku/SMC abgleichen.“
- „Kopieren auf den Server und TLS-Nachprüfung wären der nächste Ausbauschritt bewusst getrennt vom Neustart.“
---
## 5. Die Sonic Management Application API warum sie kompliziert wirkt
### 5.1 Was Sonic bereitstellt (Produkt-API, nicht unsere Erfindung)
Sonic/Aurea CX Messenger hat mehrere API-Welten:
| API | Zweck | Nutzen wir für Neustart? |
|-----|--------|---------------------------|
| **Management Application API** (`mgmt_api`) | Domain Manager, Agenten, Restart/Stop wie SMC | ✅ ja |
| **CX Messenger MQ API** | Messaging (Queues, Producer/Consumer) | ❌ nein |
| **ESB API** | ESB-Services/Prozesse | ❌ nein |
| **Metrics / Notifications** | Monitoring | ❌ nein |
| **Administrative Programming Guide** | erweiterte Admin-Szenarien | nur indirekt als Konzept |
Für den Container-Neustart ist laut Produkt-Doku-Index die **Management Application API** der richtige Einstieg analog zum SMC-Button „Restart“.
### 5.2 Kernklassen (die wirklich gebraucht werden)
| Klasse / Konzept | Rolle |
|------------------|--------|
| `JMSConnectorAddress` | Verbindungsparameter (URL, User, Pass) |
| `JMSConnectorClient` | JMS/JMX-Verbindung zum Domain Manager |
| `ObjectName` | Ziel-Agent, z.B. `domain.container:ID=AGENT` |
| `MFProxyFactory.createAgentProxy` | Typisierter Proxy (`IAgentProxy`) |
| `IAgentProxy` / MBean-Operationen | `stop` / `restart` / `shutdown` … |
| Client-JARs unter `lib` | u.a. `mgmt_client.jar`, `mfcontext.jar`, `sonic_Client.jar` |
### 5.3 Warum es trotzdem „viele unnötige Funktionen“ zu geben scheint
**Ja die Sonic-API ist enorm breit.** Sie deckt Broker-Verwaltung, Queues, Durable Subscribers, Metrics, Config-Beans, Directory Service, Host Manager usw. ab.
**Für unseren Use-Case brauchen wir nur einen schmalen Schnitt:**
1. Verbinden
2. Agent-ObjectName finden
3. Lifecycle: Stop/Restart
Alles andere in den JARs und in der `mgmt_api`-Javadoc ist **Produktumfang**, nicht „unsere App-API“.
Zusätzlich wirkt **unser Java-Tool** (`SonicMfContainerTool`) kompliziert, weil es **Fallbacks** hat:
| Schritt | Was | Warum |
|---------|-----|--------|
| 1 | `MBean.invoke("stop"|"restart"|"shutdown")` | Remote/„unbounded“ oft der Weg, den SMC faktisch nutzt |
| 2 | `IAgentProxy.stop/restart/shutdown` | „Schöner“ typisierter Pfad scheitert oft mit *unbounded* |
| 3 | DomainManager/`restartContainer` | Weitere Fallback-Variante |
Das ist **Absicherung**, kein Feature-Wahn. Man könnte später auf den einen Pfad reduzieren, der in eurer Umgebung stabil ist (vermutlich MBean `stop`).
### 5.4 „Unbounded client connector“ Kernpunkt für Erklärungen
Remote-Clients (SMC, unser Tool) verbinden sich als **unbounded** JMS-Connector.
Manche Methoden am typisierten Proxy prüfen das und werfen:
```text
javax.management.JMRuntimeException:
Operation unsupported for unbounded client connector.
```
Deshalb:
- `IAgentProxy.restart()` allein ist **kein** zuverlässiger Remote-Weg.
- Stattdessen: JMX `invoke` auf dem Agent-MBean, typischerweise **`stop`**.
- Wenn der Container unter einem Launch Daemon / Host Manager läuft, kommt er wieder hoch → Operator-Wirkung „Restart“.
---
## 6. Neustart End-to-End-Ablauf
### 6.1 Konfiguration (`appsettings.json`)
Beispiel (relevante Felder):
```json
{
"Name": "DE-Test",
"DomainName": "proalpha-test",
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
"Username": "Administrator",
"Password": "Administrator",
"SonicHome": "C:\\DEV\\MQ10.0",
"JavaPath": "C:\\Program Files (x86)\\Java\\jre1.8.0_501\\bin\\java.exe",
"MfClientLibPath": "C:\\DEV\\MQ10.0\\lib",
"KnownContainers": [ "ct-ZADBService" ],
"TimeoutSeconds": 120,
"PostRestartDelaySeconds": 20
}
```
Container in `appsettings` unter der Verbindung `DE-Test`:
- `KnownContainers`: `[ "ct-ZADBService" ]`
### 6.2 UI → C#
1. Benutzer hakt Ziel an → **ESB neu starten**.
2. `PreflightValidator`: `ContainerName` + `SonicConnectionName` müssen gesetzt sein.
3. `RestartExecutor` sucht die `SonicConnection` und ruft `SonicManagementClient.RestartContainerAsync` auf.
4. `SonicMfApiExecutor`:
- Java 8+ finden
- Classpath aus allen JARs unter `MfClientLibPath` / `SonicHome\lib` bauen
- `Tools\SonicMfContainerTool.class` (Java-8-Bytecode) laden
- Prozess starten mit sauberer Argumentübergabe (`ArgumentList`, keine doppelten Quotes)
Ungefährer Aufruf:
```text
java -cp "<Tools>;<jar1>;<jar2>;..." SonicMfContainerTool restart
--domain proalpha-test
--url tcp://dekun-painwbdet:13070
--user Administrator
--container ct-ZADBService
--timeout 120
```
Passwort: Umgebungsvariable `ESB_SONIC_PASSWORD` (nicht auf der Kommandozeile).
### 6.3 Java-Tool (`SonicMfContainerTool`)
1. Loggt `INFO:ToolVersion=…` (Build-Kontrolle).
2. `connect` über `JMSConnectorAddress` + `JMSConnectorClient`.
3. ObjectName: bevorzugt `proalpha-test.ct-ZADBService:ID=AGENT`.
4. Lifecycle-Versuche (siehe Abschnitt 5.3).
5. Erfolg: Zeile `OK:RestartInvoked method=…`
6. C# wertet `OK:RestartInvoked` als Erfolg und wartet `PostRestartDelaySeconds`.
### 6.4 Erfolgsdefinition
| Signal | Bedeutung |
|--------|-----------|
| `INFO:Connected …` | Login/URL ok |
| `INFO:ToolVersion=…` | Richtige Tool-Version läuft |
| `OK:RestartInvoked …` | Lifecycle-Aufruf durchgekommen |
| SMC zeigt Offline → Online / Uptime reset | Operative Bestätigung |
---
## 7. Braucht die API „viele unnötige Funktionen“?
### Kurze Antwort
**Die Sonic-Produkt-API ja unser benötigter Schnitt nein.**
### Aufschlüsselung
| Schicht | Viele Funktionen? | Für uns nötig? |
|---------|-------------------|----------------|
| Sonic `mgmt_api` + Client-JARs | Ja, sehr breit | Nur Connect + Agent-Lifecycle |
| Unser C#-Client früher (WinRM, HTTP, XApi, Bat) | Ja, historisch gewachsen | Nein weitgehend entfernt |
| `SonicMfContainerTool` Fallbacks (MBean / IAgentProxy / DomainManager) | Mehrere Wege | Nützlich bis ein Weg überall stabil ist; danach ausdünnbar |
| Zertifikat TLS-Probe / Deploy | Extra-Komplexität | Aktuell nicht nötig |
### Was man später noch verschlanken könnte
1. Im Java-Tool nur noch den einen erfolgreichen Weg behalten (z.B. nur `MBean.invoke("stop")`).
2. In `appsettings` tote Felder entfernen (`WinRm*`, `Container*Path`, `ManagementHttpPort`, …) stehen teils noch in JSON, werden vom schlanken Client nicht mehr genutzt.
3. `SonicMfApiExecutor` Java-Discovery vereinfachen, sobald `JavaPath` überall fest gesetzt ist.
4. Optional: Zertifikat-Deploy + TLS-Probe als **eigenes** Modul wieder einführen klar getrennt vom Neustart.
---
## 8. Voraussetzungen auf dem Rechner (Checkliste)
| Voraussetzung | Beispiel / Hinweis |
|---------------|--------------------|
| SMC-Client / MQ-Libs | `C:\DEV\MQ10.0\lib` mit `mgmt_client.jar`, `mfcontext.jar`, `sonic_Client.jar` |
| Java 8+ | z.B. `jre1.8.0_501\bin\java.exe` (nicht zu alte Sonic-JRE 6/7) |
| Netzwerk | Port `13070` zum Domain Manager erreichbar |
| Credentials | Dieselben wie SMC-Login |
| Containername | Exakt wie in SMC (`ct-ZADBService`), nicht der Alias `DE-Test` |
| App-Build | Frische `Tools\SonicMfContainerTool.class` (Ausgabe muss `ToolVersion` zeigen) |
---
## 9. Typische Fehler & Interpretation
| Fehler / Symptom | Ursache | Maßnahme |
|------------------|---------|----------|
| `ClassNotFoundException: JMSConnectorAddress` | Classpath/JARs fehlen oder Quotes kaputt | `MfClientLibPath`, neuen Build, ArgumentList |
| `UnsupportedClassVersionError` | Tool mit zu neuem Java gebaut / zu alte Runtime | Tool major 52 + Java 8+ Runtime |
| `Operation unsupported for unbounded…` | `IAgentProxy.restart` remote gesperrt | MBean `stop` / neuer Tool-Build mit Fallbacks |
| Kein `INFO:ToolVersion=` | Alte `.class` im Output | Neu bauen, Debug/Release-Ordner prüfen |
| Container `DE-Test` | Alias statt Kurzname | `ct-ZADBService` setzen |
| `stopcontainer.bat` fehlt | SMC-only | Erwartet MfApi nutzen, nicht Bat |
---
## 10. Dateien im Code (Orientierung)
| Datei | Rolle |
|-------|--------|
| `Form1.cs` | UI: Zertifikat + Ziele + Neustart (Design) |
| `Services/DeploymentOrchestrator.cs` | Nur `RestartOnlyAsync` |
| `Services/RestartExecutor.cs` | Mappt Ziel → Sonic-Verbindung |
| `Services/SonicManagementClient.cs` | Fassade MfApi |
| `Services/SonicMfApiExecutor.cs` | Java starten, Classpath, Tool |
| `Tools/SonicMfContainerTool.java` | Eigentliche Management-API-Aufrufe |
| `appsettings.json` | Domain, URL, Login, Java, Lib-Pfad, KnownContainers |
---
## 11. Präsentations-Skript (kurz)
1. **Problem:** Zertifikat prüfen und ESB-Container neu starten ohne manuelle SMC-Klicks jedes Mal.
2. **Zertifikat:** Datei laden → Subject/Issuer/Fingerprint/Gültigkeit. Lokal verifiziert, noch kein Deploy.
3. **Neustart:** Dieselbe Management-API wie die SMC, nicht FTP, nicht `stopcontainer.bat`.
4. **Technik:** C# orchestriert, Java + Sonic-Client-JARs sprechen mit dem Domain Manager.
5. **Komplexität:** Die Sonic-API ist riesig; wir nutzen nur Connect + Agent-Stop/Restart. Fallbacks wegen „unbounded connector“.
6. **Ergebnis:** Container `ct-ZADBService` in Domain `proalpha-test` wird remote neu gestartet.
---
## 12. Offene Punkte / nächste sinnvolle Ausbauten
1. Zertifikat **nach** Erkennung auf den Zielhost legen (Deploy) bewusst getrennt.
2. Optional **TLS-Probe** (Remote-Fingerprint) nach Deploy.
3. Java-Tool auf den einen erfolgreichen Lifecycle-Pfad reduzieren.
4. `appsettings` von Altlast-Feldern (WinRM/HTTP) bereinigen.
5. Fehlende offizielle `mgmt_api`-HTML/PDF ins Repo legen, damit man in Reviews direkt die Klassen verlinken kann.
---
*Dokument erzeugt für Übergabe/Erklärung. Bei Abweichungen gilt der Code auf Branch `fix/mfapi-classpath-ct-zadbservice`.*
+1
View File
@@ -29,6 +29,7 @@
ClientSize = new Size(800, 450);
Name = "Form1";
Text = "Form1";
Load += Form1_Load;
ResumeLayout(false);
}
File diff suppressed because it is too large Load Diff
@@ -2,16 +2,66 @@ namespace ZA.CoreService.ESBCertificateManager.Models;
public sealed class AppSettings
{
public string ConnectionString { get; set; } = string.Empty;
public bool UseOfflineSampleData { get; set; } = true;
public string SampleTargetsPath { get; set; } = "Data/targets.sample.json";
public string EnvironmentCode { get; set; } = "TEST";
public string LogDirectory { get; set; } = "Logs";
public int TlsTimeoutSeconds { get; set; } = 8;
public int TlsRetryCount { get; set; } = 2;
public DatabaseSettings Database { get; set; } = new();
public RuntimeSettings Runtime { get; set; } = new();
public AlwaysEncryptedSettings AlwaysEncrypted { get; set; } = new();
/// <summary>
/// Verbindungskonfigurationen für Progress Sonic ESB Management Instanzen.
/// Jeder Eintrag entspricht einer Sonic-Domain (z.B. einer Umgebung oder Tochtergesellschaft).
/// Wenn aktiv, erscheint der Demo-UNC-Pfad auch ohne SQL-Update in der Zielliste.
/// </summary>
public List<SonicConnection> SonicConnections { get; set; } = [];
public DemoSettings Demo { get; set; } = new();
}
public sealed class DemoSettings
{
public bool Enabled { get; set; } = true;
public string TargetDirectory { get; set; } =
DemoCertificatePath.Directory;
public string TargetFileName { get; set; } =
DemoCertificatePath.FileName;
public string ContainerName { get; set; } =
"ct-ZADBService";
public string DisplayName { get; set; } =
"DE / Demo ZADBService";
public string SonicConnectionName { get; set; } =
"DE-Test";
}
public sealed class DatabaseSettings
{
public string ConnectionString { get; set; } = string.Empty;
}
public sealed class RuntimeSettings
{
public string JavaExecutablePath { get; set; } =
@"Runtime\bin\java.exe";
public string SonicClientLibraryPath { get; set; } =
@"Runtime\SonicClient\lib";
public bool SetupCompleted { get; set; }
}
public sealed class AlwaysEncryptedSettings
{
public string CertificateThumbprint { get; set; } =
string.Empty;
public string StoreName { get; set; } =
"My";
public string StoreLocation { get; set; } =
"CurrentUser";
}
@@ -0,0 +1,122 @@
using ZA.CoreService.ESBCertificateManager.Services;
namespace ZA.CoreService.ESBCertificateManager.Models;
/// <summary>
/// Aus dbo.SonicConnection geladene Verbindungsdaten.
/// Zugangsdaten liegen ausschließlich in dbo.SonicCredential.
/// </summary>
public sealed class DatabaseSonicConnection
{
public int Id { get; init; }
public required string Name { get; init; }
public required string ManagementHost { get; init; }
public int ManagementPort { get; init; }
public string? ConnectionProtocol { get; init; }
public string? DomainName { get; init; }
public string? EnvironmentCode { get; init; }
public bool IsActive { get; init; }
public string BuildConnectionUrl()
{
if (string.IsNullOrWhiteSpace(ConnectionProtocol))
{
throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlt ConnectionProtocol.");
}
if (string.IsNullOrWhiteSpace(ManagementHost))
{
throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlt ManagementHost.");
}
if (ManagementPort is < 1 or > 65535)
{
throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlt ein gültiger ManagementPort.");
}
string protocol = ConnectionProtocol
.Trim()
.TrimEnd(':', '/');
string host = ManagementHost.Trim();
return $"{protocol}://{host}:{ManagementPort}";
}
public SonicConnection ToRuntimeConnection(
RuntimeSettings runtimeSettings,
SonicCredentialProfile credential)
{
ArgumentNullException.ThrowIfNull(credential);
if (string.IsNullOrWhiteSpace(DomainName))
{
throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlt DomainName.");
}
if (!credential.IsComplete)
{
throw new InvalidOperationException(
$"Bei der Sonic-Verbindung '{Name}' fehlen die Zugangsdaten. " +
"Bitte die Einstellungen öffnen und ein Benutzerprofil hinterlegen.");
}
string? javaExecutablePath =
JavaRuntimeLocator.Resolve(
runtimeSettings.JavaExecutablePath);
if (javaExecutablePath is null)
{
throw new InvalidOperationException(
"Java 8 wurde nicht gefunden. " +
"Bitte Runtime:JavaExecutablePath auf " +
@"jre1.8*\bin\java.exe setzen.");
}
string sonicClientLibraryPath =
PathResolver.ResolvePath(
runtimeSettings.SonicClientLibraryPath);
string javaBinDirectory =
Path.GetDirectoryName(javaExecutablePath)
?? string.Empty;
string javaHome =
Directory.GetParent(javaBinDirectory)?.FullName
?? string.Empty;
string sonicHome =
Directory.GetParent(sonicClientLibraryPath)?.FullName
?? string.Empty;
return new SonicConnection
{
Name = Name,
DomainName = DomainName,
ConnectionUrl = BuildConnectionUrl(),
Username = credential.UserName,
// Nur Laufzeitwert, bereits clientseitig entschlüsselt.
Password = credential.Secret,
JavaPath = javaExecutablePath,
JavaHome = javaHome,
MfClientLibPath = sonicClientLibraryPath,
SonicHome = sonicHome,
TimeoutSeconds = 120,
PostRestartDelaySeconds = 20
};
}
}
@@ -0,0 +1,15 @@
namespace ZA.CoreService.ESBCertificateManager.Models;
/// <summary>
/// Fester Demo-Pfad für den ersten Zertifikat-Austausch-Test (TEST / DE).
/// </summary>
public static class DemoCertificatePath
{
public const string Directory =
@"\\dekun-painwbdet\e$\proalpha-inwb\test\Containers\proalpha-test.ct-ZADBService\certs";
public const string FileName = "server.p12";
public static string FullPath =>
Path.Combine(Directory, FileName);
}
@@ -9,6 +9,16 @@ public sealed class DeploymentTarget
public required string TargetDirectory { get; init; }
public required string CertificateFileName { get; init; }
/// <summary>Vollständiger Dateipfad (UNC oder lokal) zur Zieldatei.</summary>
public string FullTargetPath =>
Path.Combine(TargetDirectory, CertificateFileName);
public bool BackupEnabled { get; init; } = true;
public string BackupDirectoryName { get; init; } = "Backup";
public int? BackupRetentionDays { get; init; }
/// <summary>Name des Sonic-ESB-Containers (z.B. "sonic-container-a").</summary>
public string ContainerName { get; init; } = string.Empty;
@@ -0,0 +1,19 @@
namespace ZA.CoreService.ESBCertificateManager.Models;
public sealed class LocalSetupSelection
{
public string EnvironmentCode { get; set; } =
string.Empty;
public int SonicConnectionId { get; set; }
public int SonicCredentialId { get; set; }
public string ConnectionName { get; set; } =
string.Empty;
public string CredentialName { get; set; } =
string.Empty;
public DateTimeOffset SavedAtUtc { get; set; }
}
@@ -0,0 +1,78 @@
namespace ZA.CoreService.ESBCertificateManager.Models;
public sealed class CompanyOption
{
public int CompanyId { get; init; }
public required string CompanyCode { get; init; }
public required string CompanyName { get; init; }
public override string ToString()
{
return $"{CompanyCode} — {CompanyName}";
}
}
public sealed class ContainerOption
{
public int SonicContainerId { get; init; }
public int SonicConnectionId { get; init; }
public int CompanyId { get; init; }
public required string ContainerName { get; init; }
public string? ContainerDisplayName { get; init; }
/// <summary>
/// Endpoint für TLS-Zertifikatsprüfung, z. B. https://host:8443 oder host:8443.
/// </summary>
public string? CertificateCheckUrl { get; init; }
public required string CompanyCode { get; init; }
public required string ConnectionName { get; init; }
public int RestartTimeoutSeconds { get; init; }
public override string ToString()
{
string display =
string.IsNullOrWhiteSpace(ContainerDisplayName)
? ContainerName
: ContainerDisplayName;
return $"{CompanyCode} / {display} ({ConnectionName})";
}
}
public sealed class CertificateTargetOption
{
public int CertificateTargetId { get; init; }
public int SonicContainerId { get; init; }
public required string TargetDirectory { get; init; }
public required string TargetFileName { get; init; }
public bool BackupEnabled { get; init; }
public required string BackupDirectoryName { get; init; }
public required string ContainerName { get; init; }
public required string ConnectionName { get; init; }
public required string CompanyCode { get; init; }
public string FullTargetPath =>
Path.Combine(TargetDirectory, TargetFileName);
public override string ToString()
{
return $"{CompanyCode} / {ContainerName}: {FullTargetPath}";
}
}
@@ -1,398 +1,48 @@
namespace ZA.CoreService.ESBCertificateManager.Models;
/// <summary>
/// Verbindungskonfiguration für eine Progress Sonic ESB Management Instanz.
/// Name = Verbindungs-Alias (hier oft gleich ContainerName, z.B. "DE-Test").
/// DomainName = Sonic-Domain (z.B. "proalpha-test").
/// Container wird in KnownContainers / DeploymentTarget.ContainerName geführt (z.B. "DE-Test").
/// Sonic-Verbindung für MfApi-Container-Restart (wie SMC).
/// </summary>
public sealed class SonicConnection
{
/// <summary>
/// Alias der Verbindung; referenziert von <see cref="DeploymentTarget.SonicConnectionName"/>.
/// </summary>
/// <summary>Alias; referenziert von DeploymentTarget.SonicConnectionName.</summary>
public required string Name { get; init; }
/// <summary>Sonic-Domain, z.B. "proalpha-test" (nicht der Containername).</summary>
/// <summary>Sonic-Domain, z.B. proalpha-test.</summary>
public required string DomainName { get; init; }
/// <summary>
/// Dieselbe Broker-/Management-URL wie in der Sonic Management Console (SMC),
/// z.B. "tcp://dekun-painwbdet:13070". Keine separate Domain-Console nötig.
/// </summary>
/// <summary>Broker-/Management-URL wie in der SMC, z.B. tcp://host:13070.</summary>
public required string ConnectionUrl { get; init; }
/// <summary>Login wie in der Sonic Management Console (nicht für WinRM).</summary>
public required string Username { get; init; }
/// <summary>Passwort wie in der Sonic Management Console (nicht für WinRM).</summary>
public required string Password { get; init; }
/// <summary>
/// Windows-Konto für WinRM (<c>Invoke-Command -Credential</c>).
/// Leer = aktueller Prozess-Benutzer ohne explizite Credentials.
/// Nicht mit <see cref="Username"/>/<see cref="Password"/> (Sonic SMC) verwechseln.
/// Nur zur Laufzeit aus Always Encrypted geladen.
/// Darf nicht geloggt oder dauerhaft gespeichert werden.
/// </summary>
public string WinRmUsername { get; init; } = string.Empty;
/// <summary>Windows-Passwort für WinRM; nur relevant wenn <see cref="WinRmUsername"/> gesetzt ist.</summary>
public string WinRmPassword { get; init; } = string.Empty;
public string Password { get; init; } = string.Empty;
/// <summary>
/// MfApi = Sonic MF Management Runtime API über ConnectionUrl + SMC-Logins (Standard);
/// WinRm / LocalCmd = optionaler Fallback über stopcontainer/startcontainer;
/// HttpApi = REST (falls vorhanden).
/// Verweis auf eine generische Windows-Anmeldeinformation.
/// Enthält ausdrücklich kein Kennwort.
/// </summary>
/// <summary>
/// Standard: MfApi = Management Application API (wie SMC):
/// JMSConnectorClient + MFProxyFactory.createAgentProxy + IAgentProxy.restart.
/// Laut CX-Messenger-Doku-Index: Docs2017/api/mgmt_api (nicht stopcontainer.bat).
/// LocalCmd nur wenn Server-Scripts existieren (bei reiner SMC oft nicht der Fall).
/// </summary>
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.MfApi;
public string CredentialReference { get; init; } = string.Empty;
/// <summary>
/// Effektiver Modus: bei WinRm und lokalem ConnectionUrl-Host wird LocalCmd erzwungen.
/// MfApi bleibt unverändert (nutzt Domain-Manager-Verbindung).
/// </summary>
public SonicManagementMode EffectiveManagementMode
=> ManagementMode == SonicManagementMode.WinRm && IsConnectionHostLocal(ConnectionUrl)
? SonicManagementMode.LocalCmd
: ManagementMode;
/// <summary>True wenn WinRm konfiguriert war, aber wegen lokalem Host auf LocalCmd umgestellt wurde.</summary>
public bool IsLocalCmdAutoForced
=> ManagementMode == SonicManagementMode.WinRm
&& EffectiveManagementMode == SonicManagementMode.LocalCmd;
/// <summary>Anzeigetext für UI (inkl. Auto-Erkennung).</summary>
public string ManagementModeDisplay
=> IsLocalCmdAutoForced
? "LocalCmd (Host lokal erkannt)"
: EffectiveManagementMode switch
{
SonicManagementMode.MfApi => "MfApi (SMC Management Application API)",
SonicManagementMode.LocalCmd => "LocalCmd (nur wenn stopcontainer.bat existiert)",
_ => EffectiveManagementMode.ToString()
};
/// <summary>
/// True wenn der Host aus ConnectionUrl dieser Maschine entspricht
/// (localhost / 127.0.0.1 / ::1 / Computername).
/// </summary>
public static bool IsConnectionHostLocal(string? connectionUrl)
{
if (string.IsNullOrWhiteSpace(connectionUrl))
{
return false;
}
string host;
try
{
host = new Uri(connectionUrl).Host;
}
catch
{
host = connectionUrl.Trim();
}
if (string.IsNullOrWhiteSpace(host))
{
return false;
}
if (host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
|| host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase)
|| host.Equals("::1", StringComparison.OrdinalIgnoreCase)
|| host.Equals("[::1]", StringComparison.OrdinalIgnoreCase))
{
return true;
}
string machine = Environment.MachineName;
if (host.Equals(machine, StringComparison.OrdinalIgnoreCase)
|| host.StartsWith(machine + ".", StringComparison.OrdinalIgnoreCase))
{
return true;
}
try
{
string dnsName = System.Net.Dns.GetHostName();
if (!string.IsNullOrWhiteSpace(dnsName)
&& (host.Equals(dnsName, StringComparison.OrdinalIgnoreCase)
|| host.StartsWith(dnsName + ".", StringComparison.OrdinalIgnoreCase)))
{
return true;
}
}
catch
{
// DNS optional
}
return false;
}
/// <summary>
/// Sonic-/MQ-/ESB-Installationsroot, z.B. "C:\Sonic\MQ10.0" (Client-JARs oft unter lib).
/// Bei ESB-Home: ggf. MfClientLibPath auf MQ\lib setzen.
/// </summary>
/// <summary>Installationsroot (optional; Libs oft unter lib).</summary>
public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0";
/// <summary>
/// Optional: JRE/JDK-Home (z.B. C:\Sonic\MQ10.0\jre) oder Pfad zu java.exe.
/// Leer = automatische Suche (JAVA_HOME/JRE_HOME, setenv.bat, rekursiv unter SonicHome, Registry, …).
/// </summary>
/// <summary>Optional: JRE/JDK-Home.</summary>
public string JavaHome { get; init; } = string.Empty;
/// <summary>
/// Optionaler direkter Pfad zu java.exe (überschreibt JavaHome wenn gesetzt).
/// </summary>
/// <summary>Optional: direkter Pfad zu java.exe.</summary>
public string JavaPath { get; init; } = string.Empty;
/// <summary>
/// Optionaler Pfad zu Sonic-Client-JARs für MfApi (mgmt_client.jar etc.).
/// Leer = SonicHome\lib, MQ_HOME\lib, ESB_HOME\lib bzw. nested MQ*/lib.
/// </summary>
/// <summary>Ordner mit mgmt_client.jar / mfcontext.jar / …</summary>
public string MfClientLibPath { get; init; } = string.Empty;
/// <summary>
/// Container-Namen (kurz), z.B. ["DE-Test"]. Nicht die Domain.
/// </summary>
/// <summary>Container-Kurznamen, z.B. ct-ZADBService.</summary>
public List<string> KnownContainers { get; init; } = [];
public int ManagementHttpPort { get; init; } = 8080;
public string ApiBasePath { get; init; } = "/api/v1";
public string ContainerListPath { get; init; } = string.Empty;
public string ContainerRestartPath { get; init; } = string.Empty;
public string ContainerStopPath { get; init; } = string.Empty;
public string ContainerStartPath { get; init; } = string.Empty;
public int WinRmPort { get; init; } = 5985;
public string WinRmRestartScript { get; init; } = string.Empty;
public string WinRmContainerListScript { get; init; } = string.Empty;
public string WinRmXapiImportScript { get; init; } = string.Empty;
public int TimeoutSeconds { get; init; } = 120;
public int PostRestartDelaySeconds { get; init; } = 20;
public string ResolveRestartScript()
=> string.IsNullOrWhiteSpace(WinRmRestartScript)
? DefaultRestartScript
: WinRmRestartScript;
public string ResolveContainerListScript()
=> string.IsNullOrWhiteSpace(WinRmContainerListScript)
? DefaultContainerListScript
: WinRmContainerListScript;
/// <summary>
/// Offizieller MF-Container-Neustart laut Aurea CX Messenger Doku:
/// SonicHome\bin\stopcontainer.bat Domain.Container
/// SonicHome\bin\startcontainer.bat Domain.Container
/// Danach Prozess-Verifikation (alte PIDs weg, neue PIDs da).
/// </summary>
public const string DefaultRestartScript =
"""
$ErrorActionPreference = 'Stop'
$sonicHome = '{sonicHome}'
$domain = '{domain}'
$container = '{container}'
$bin = Join-Path $sonicHome 'bin'
$stopBat = Join-Path $bin 'stopcontainer.bat'
$startBat = Join-Path $bin 'startcontainer.bat'
# Kurzname = Container (z.B. DE-Test), Full = Domain.Container (z.B. proalpha-test.DE-Test)
$shortName = if ($container -like '*.*') { ($container -split '\.', 2)[1] } else { $container }
$fullName = if ($container -like '*.*') { $container } else { "$domain.$container" }
$markers = @($shortName, $fullName, "$domain.$shortName") | Select-Object -Unique
function Get-ContainerPids {
$pids = @()
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object {
$cmd = $_.CommandLine
if (-not $cmd) { return $false }
if ($_.Name -notmatch 'java|javaw|sonic') { return $false }
foreach ($m in $markers) {
if ($cmd -like "*$m*") { return $true }
}
return $false
} |
ForEach-Object { $pids += [int]$_.ProcessId }
return @($pids | Select-Object -Unique)
}
function Wait-PidsGone([int[]]$pids, [int]$seconds) {
$deadline = (Get-Date).AddSeconds($seconds)
while ((Get-Date) -lt $deadline) {
$alive = @($pids | Where-Object { Get-Process -Id $_ -ErrorAction SilentlyContinue })
if ($alive.Count -eq 0) { return $true }
Start-Sleep -Seconds 2
}
return $false
}
function Wait-NewPids([int[]]$oldPids, [int]$seconds) {
$deadline = (Get-Date).AddSeconds($seconds)
while ((Get-Date) -lt $deadline) {
$now = @(Get-ContainerPids)
$fresh = @($now | Where-Object { $oldPids -notcontains $_ })
if ($fresh.Count -gt 0) { return ,$fresh }
Start-Sleep -Seconds 2
}
return ,@()
}
function Invoke-ContainerBat([string]$bat, [string]$name) {
if (-not (Test-Path -LiteralPath $bat)) {
throw "Sonic BAT fehlt: $bat (SonicHome pruefen)"
}
$arg = '/c "' + $bat + '" "' + $name + '"'
$p = Start-Process -FilePath 'cmd.exe' -ArgumentList $arg -Wait -PassThru -NoNewWindow
return [int]$p.ExitCode
}
Write-Output "INFO:Domain=$domain Container=$shortName Full=$fullName"
Write-Output "INFO:RestartVia=stopcontainer/startcontainer (MF Container)"
$before = @(Get-ContainerPids)
Write-Output "INFO:PIDsBefore=$($before -join ',')"
if ($before.Count -eq 0) {
Write-Output "WARN:No running Java/Sonic process for '$shortName' - starting anyway"
}
# 1) Offiziell: stopcontainer.bat Domain.Container (danach Kurzname als Fallback)
$stopOk = $false
foreach ($n in @($fullName, $shortName)) {
Write-Output "INFO:stopcontainer $n"
$code = Invoke-ContainerBat -bat $stopBat -name $n
Write-Output "INFO:stopcontainer ExitCode=$code Name=$n"
if ($code -eq 0) { $stopOk = $true; break }
}
if (-not $stopOk) {
Write-Output "WARN:stopcontainer non-zero; will force-stop remaining PIDs if any"
}
Start-Sleep -Seconds 3
$still = @(Get-ContainerPids)
# 2) Force-Stop falls BAT den Prozess nicht beendet (sonst kein echter Restart in SMC)
if ($still.Count -gt 0) {
Write-Output "INFO:Force-Stop PIDs=$($still -join ',')"
foreach ($procId in $still) {
Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue
}
}
if ($before.Count -gt 0) {
if (-not (Wait-PidsGone -pids $before -seconds 45)) {
throw "Container process still running after stop (PIDs=$($before -join ','))"
}
Write-Output "INFO:Old PIDs gone"
}
Start-Sleep -Seconds 3
# 3) Offiziell: startcontainer.bat Domain.Container
$started = $false
foreach ($n in @($fullName, $shortName)) {
Write-Output "INFO:startcontainer $n"
$code = Invoke-ContainerBat -bat $startBat -name $n
Write-Output "INFO:startcontainer ExitCode=$code Name=$n"
if ($code -eq 0) { $started = $true; break }
}
if (-not $started) {
throw "startcontainer failed for $fullName / $shortName"
}
$after = @(Wait-NewPids -oldPids $before -seconds 60)
if ($after.Count -eq 0) {
$any = @(Get-ContainerPids)
if ($any.Count -eq 0) {
throw "After start no process for container '$shortName'. Check SMC / SonicHome / ContainerName."
}
Write-Output "INFO:PIDsAfter=$($any -join ',')"
}
else {
Write-Output "INFO:PIDsAfter=$($after -join ',')"
}
Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName"
""";
public const string DefaultContainerListScript =
"""
$ErrorActionPreference = 'Continue'
$sonicHome = '{sonicHome}'
$domain = '{domain}'
$names = New-Object System.Collections.Generic.List[string]
function Add-Name([string]$n) {
if ([string]::IsNullOrWhiteSpace($n)) { return }
$n = $n.Trim()
if (-not $names.Contains($n)) { [void]$names.Add($n) }
}
if (-not (Test-Path -LiteralPath $sonicHome)) {
Write-Output "WARN:SonicHomeNichtGefunden:$sonicHome"
}
else {
Write-Output "INFO:SonicHomeOk:$sonicHome Domain=$domain"
Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue |
ForEach-Object {
if ($_.Name -like '*.cache') {
Add-Name ($_.Name -replace '\.cache$', '')
}
Get-ChildItem -LiteralPath $_.FullName -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -like '*.cache' } |
ForEach-Object { Add-Name ($_.Name -replace '\.cache$', '') }
}
Get-ChildItem -LiteralPath $sonicHome -Recurse -Filter 'container.xml' -File -ErrorAction SilentlyContinue |
Select-Object -First 40 |
ForEach-Object {
Add-Name (Split-Path $_.DirectoryName -Leaf)
}
}
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object { $_.CommandLine -match 'sonic|mf\.framework|container\.xml' } |
ForEach-Object {
if ($_.CommandLine -match '([A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)\.cache') {
Add-Name $Matches[1]
}
elseif ($_.CommandLine -match '\\([A-Za-z0-9_\-]+)\\container\.xml') {
Add-Name $Matches[1]
}
}
# Kurzform Domain.Container Container
foreach ($n in @($names.ToArray())) {
if ($n -like '*.*') {
$parts = $n -split '\.', 2
if ($parts.Count -eq 2) { Add-Name $parts[1] }
}
}
if ($names.Count -eq 0) {
Write-Output "WARN:KeineContainerGefunden Domain=$domain SonicHome=$sonicHome"
}
$names | Sort-Object -Unique
""";
}
public enum SonicManagementMode
{
HttpApi = 0,
WinRm = 1,
LocalCmd = 2,
/// <summary>Sonic MF Management API über Domain-Manager (ConnectionUrl + SMC-Credentials).</summary>
MfApi = 3
public string ManagementModeDisplay => "MfApi (SMC Restart)";
}
@@ -0,0 +1,31 @@
namespace ZA.CoreService.ESBCertificateManager.Models;
public sealed class SonicCredentialProfile
{
public int SonicCredentialId { get; init; }
public int SonicConnectionId { get; init; }
public required string CredentialName { get; init; }
public required string UserName { get; init; }
/// <summary>
/// Durch Microsoft.Data.SqlClient clientseitig entschlüsselt.
/// Darf niemals angezeigt oder protokolliert werden.
/// </summary>
public required string Secret { get; init; }
public bool IsDefault { get; init; }
public bool IsActive { get; init; }
public bool IsComplete =>
!string.IsNullOrWhiteSpace(UserName)
&& !string.IsNullOrEmpty(Secret);
public override string ToString()
{
return CredentialName;
}
}
@@ -0,0 +1,38 @@
namespace ZA.CoreService.ESBCertificateManager.Models;
public sealed class SonicSystemOption
{
public int SonicConnectionId { get; init; }
public required string ConnectionName { get; init; }
public required string EnvironmentCode { get; init; }
public required string DomainName { get; init; }
public required string ManagementHost { get; init; }
public int ManagementPort { get; init; }
public required string ConnectionProtocol { get; init; }
public required string ValidationContainerName { get; init; }
public string ConnectionUrl
{
get
{
string protocol = ConnectionProtocol
.Trim()
.TrimEnd(':', '/');
return
$"{protocol}://{ManagementHost.Trim()}:{ManagementPort}";
}
}
public override string ToString()
{
return ConnectionName;
}
}
@@ -1,13 +1,95 @@
namespace ZA.CoreService.ESBCertificateManager
{
using System;
using System.Windows.Forms;
using ZA.CoreService.ESBCertificateManager.Configuration;
using ZA.CoreService.ESBCertificateManager.Models;
using ZA.CoreService.ESBCertificateManager.Services;
using ZA.CoreService.ESBCertificateManager.Setup;
namespace ZA.CoreService.ESBCertificateManager;
internal static class Program
{
[STAThread]
static void Main()
private static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
try
{
AppSettings settings =
AppSettingsLoader.Load();
SonicSetupRepository repository =
new(
settings.Database.ConnectionString);
SetupCoordinator coordinator =
new(
settings,
repository);
SetupCheckResult setupResult =
coordinator
.CheckAsync()
.GetAwaiter()
.GetResult();
if (!setupResult.IsReady)
{
using SetupWizardForm setupForm =
new(
coordinator,
isSettingsMode: false);
DialogResult setupDialogResult =
setupForm.ShowDialog();
if (setupDialogResult != DialogResult.OK)
{
return;
}
SetupCheckResult finalCheck =
coordinator
.CheckAsync()
.GetAwaiter()
.GetResult();
if (!finalCheck.IsReady)
{
MessageBox.Show(
"Einrichtung unvollständig.",
"Einstellungen",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
return;
}
}
try
{
DatabaseSchemaPatcher
.EnsureCertificateCheckUrlColumnAsync(
settings.Database.ConnectionString)
.GetAwaiter()
.GetResult();
}
catch
{
// Ohne ALTER-Rechte weiter starten; Repositories nutzen Fallback-SQL.
}
Application.Run(
new Form1());
}
catch (Exception exception)
{
MessageBox.Show(
"Start fehlgeschlagen.",
"Fehler",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
}
@@ -0,0 +1,69 @@
Copyright © 1993, 2026, Oracle and/or its affiliates.
All rights reserved.
This software and related documentation are provided under a
license agreement containing restrictions on use and
disclosure and are protected by intellectual property laws.
Except as expressly permitted in your license agreement or
allowed by law, you may not use, copy, reproduce, translate,
broadcast, modify, license, transmit, distribute, exhibit,
perform, publish, or display any part, in any form, or by
any means. Reverse engineering, disassembly, or
decompilation of this software, unless required by law for
interoperability, is prohibited.
The information contained herein is subject to change
without notice and is not warranted to be error-free. If you
find any errors, please report them to us in writing.
If this is software or related documentation that is
delivered to the U.S. Government or anyone licensing it on
behalf of the U.S. Government, the following notice is
applicable:
U.S. GOVERNMENT END USERS: Oracle programs, including any
operating system, integrated software, any programs
installed on the hardware, and/or documentation, delivered
to U.S. Government end users are "commercial computer
software" pursuant to the applicable Federal Acquisition
Regulation and agency-specific supplemental regulations. As
such, use, duplication, disclosure, modification, and
adaptation of the programs, including any operating system,
integrated software, any programs installed on the hardware,
and/or documentation, shall be subject to license terms and
license restrictions applicable to the programs. No other
rights are granted to the U.S. Government.
This software or hardware is developed for general use in a
variety of information management applications. It is not
developed or intended for use in any inherently dangerous
applications, including applications that may create a risk
of personal injury. If you use this software or hardware in
dangerous applications, then you shall be responsible to
take all appropriate fail-safe, backup, redundancy, and
other measures to ensure its safe use. Oracle Corporation
and its affiliates disclaim any liability for any damages
caused by use of this software or hardware in dangerous
applications.
Oracle and Java are registered trademarks of Oracle and/or
its affiliates. Other names may be trademarks of their
respective owners.
Intel and Intel Xeon are trademarks or registered trademarks
of Intel Corporation. All SPARC trademarks are used under
license and are trademarks or registered trademarks of SPARC
International, Inc. AMD, Opteron, the AMD logo, and the AMD
Opteron logo are trademarks or registered trademarks of
Advanced Micro Devices. UNIX is a registered trademark of
The Open Group.
This software or hardware and documentation may provide
access to or information on content, products, and services
from third parties. Oracle Corporation and its affiliates
are not responsible for and expressly disclaim all
warranties of any kind with respect to third-party content,
products, and services. Oracle Corporation and its
affiliates will not be responsible for any loss, costs, or
damages incurred due to your access to or use of third-party
content, products, or services.
@@ -0,0 +1 @@
Please refer to https://java.com/otnlicense

Some files were not shown because too many files have changed in this diff Show More