using System.Diagnostics; using System.Text; using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; /// /// Neustart über Sonic-Server-Scripts: /// stopcontainer.bat / startcontainer.bat /// Diese liegen nur in einer vollen MQ/ESB-Server-Installation – nicht in einer /// reinen Sonic Management Console (SMC/Client). /// public sealed class SonicBinRestartExecutor { private readonly SonicConnection _connection; public SonicBinRestartExecutor(SonicConnection connection) { _connection = connection; } public (bool Ok, string? Error, string? Detail) Probe() { string sonicHome = _connection.SonicHome?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(sonicHome)) { return (false, "SonicHome ist leer – in appsettings setzen.", null); } if (!Directory.Exists(sonicHome)) { return (false, $"SonicHome existiert nicht: '{sonicHome}'", null); } (string? stop, string? start, string searchNote) = LocateContainerScripts(sonicHome); if (stop is null || start is null) { string binDir = Path.Combine(sonicHome, "bin"); string binListing = DescribeDirectory(binDir); bool looksLikeSmcOnly = Directory.Exists(Path.Combine(sonicHome, "lib")) && !File.Exists(Path.Combine(binDir, "stopcontainer.bat")); string why = looksLikeSmcOnly ? "Das sieht nach einer Sonic Management Console / Client-Installation aus " + "(lib vorhanden, aber keine Server-Scripts). " + "stopcontainer.bat gibt es nur auf dem Sonic-SERVER, nicht in der reinen SMC." : "Server-Scripts wurden unter SonicHome nicht gefunden."; return (false, why + $" Gesucht unter '{sonicHome}'.", $"{searchNote}\nInhalt von bin: {binListing}\n" + "Lösung A: SonicHome auf den Server-Installationspfad setzen (dort wo stopcontainer.bat liegt).\n" + "Lösung B: App lässt automatisch MfApi/SMC-Verbindung versuchen (ConnectionUrl + Login)."); } return (true, null, $"stop={stop}; start={start}"); } public async Task<(bool Success, string Status, string? Detail)> RestartAsync( string containerName, CancellationToken cancellationToken = default) { (bool probeOk, string? probeError, string? probeDetail) = Probe(); if (!probeOk) { return (false, "Neustart fehlgeschlagen (SonicBin)", $"{probeError}\n{probeDetail}"); } string sonicHome = _connection.SonicHome.Trim(); (string? stopBat, string? startBat, _) = LocateContainerScripts(sonicHome); string bin = Path.GetDirectoryName(stopBat!)!; string shortName = containerName.Contains('.') ? containerName[(containerName.IndexOf('.') + 1)..] : containerName; string fullName = containerName.Contains('.') ? containerName : $"{_connection.DomainName}.{containerName}"; StringBuilder log = new(); log.AppendLine($"SonicHome={sonicHome}"); log.AppendLine($"stop={stopBat}"); log.AppendLine($"start={startBat}"); log.AppendLine($"Container={fullName} (kurz={shortName})"); (bool stopOk, string stopOut, string stopErr, int stopCode) = await RunBatAsync(stopBat!, fullName, bin, cancellationToken); log.AppendLine($"STOP ExitCode={stopCode}"); if (stopOut.Length > 0) log.AppendLine("STOP out: " + Truncate(stopOut)); if (stopErr.Length > 0) log.AppendLine("STOP err: " + Truncate(stopErr)); await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); (bool startOk, string startOut, string startErr, int startCode) = await RunBatAsync(startBat!, fullName, bin, cancellationToken); log.AppendLine($"START ExitCode={startCode}"); if (startOut.Length > 0) log.AppendLine("START out: " + Truncate(startOut)); if (startErr.Length > 0) log.AppendLine("START err: " + Truncate(startErr)); if (startCode != 0 && !string.Equals(shortName, fullName, StringComparison.OrdinalIgnoreCase)) { log.AppendLine($"Retry START mit Kurzname '{shortName}'…"); (_, startOut, startErr, startCode) = await RunBatAsync(startBat!, shortName, bin, cancellationToken); log.AppendLine($"START(short) ExitCode={startCode}"); if (startOut.Length > 0) log.AppendLine("START out: " + Truncate(startOut)); if (startErr.Length > 0) log.AppendLine("START err: " + Truncate(startErr)); } await Task.Delay( TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)), cancellationToken); if (startCode != 0) { return (false, "Neustart fehlgeschlagen (SonicBin)", $"startcontainer ExitCode={startCode} (stop ExitCode={stopCode}).\n{log}"); } return (true, $"Container '{fullName}' neugestartet (SonicBin)", log.ToString()); } /// /// Sucht stop/startcontainer.bat unter SonicHome\bin und rekursiv (max. Tiefe 4). /// public static (string? StopBat, string? StartBat, string Note) LocateContainerScripts(string sonicHome) { List candidates = []; void AddDir(string? dir) { if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir) && !candidates.Contains(dir)) { candidates.Add(dir); } } AddDir(Path.Combine(sonicHome, "bin")); AddDir(Path.Combine(sonicHome, "MQ_HOME", "bin")); AddDir(Path.Combine(sonicHome, "MQ", "bin")); try { string? parent = Directory.GetParent(sonicHome)?.FullName; if (parent is not null) { foreach (string child in Directory.EnumerateDirectories(parent)) { AddDir(Path.Combine(child, "bin")); } } } catch { // ignore } // Rekursiv nach Dateinamen suchen try { foreach (string file in Directory.EnumerateFiles(sonicHome, "stopcontainer.bat", SearchOption.AllDirectories) .Take(20)) { AddDir(Path.GetDirectoryName(file)); } } catch { // ignore permission issues } foreach (string dir in candidates) { string stop = Path.Combine(dir, "stopcontainer.bat"); string start = Path.Combine(dir, "startcontainer.bat"); if (File.Exists(stop) && File.Exists(start)) { return (stop, start, $"Scripts gefunden in '{dir}'"); } } return (null, null, $"Keine Scripts in {candidates.Count} geprüften bin-Ordnern."); } private static string DescribeDirectory(string dir) { if (!Directory.Exists(dir)) { return "(Ordner existiert nicht)"; } try { string[] names = Directory.GetFileSystemEntries(dir) .Select(Path.GetFileName) .Where(n => n is not null) .Cast() .OrderBy(n => n) .Take(25) .ToArray(); return names.Length == 0 ? "(leer)" : string.Join(", ", names); } catch (Exception ex) { return $"(nicht lesbar: {ex.Message})"; } } private static async Task<(bool Started, string StdOut, string StdErr, int ExitCode)> RunBatAsync( string batPath, string argument, string workingDirectory, CancellationToken cancellationToken) { ProcessStartInfo psi = new() { FileName = "cmd.exe", Arguments = $"/c \"\"{batPath}\" \"{argument}\"\"", WorkingDirectory = workingDirectory, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; using Process process = new() { StartInfo = psi }; if (!process.Start()) { return (false, string.Empty, "cmd.exe konnte nicht gestartet werden.", -1); } Task stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken); Task stderrTask = process.StandardError.ReadToEndAsync(cancellationToken); await process.WaitForExitAsync(cancellationToken); return (true, (await stdoutTask).Trim(), (await stderrTask).Trim(), process.ExitCode); } private static string Truncate(string value, int max = 500) => value.Length <= max ? value : value[..max] + "…"; }