using System.Net; using System.Net.Sockets; using ClaudeDo.Installer.Checks.Interfaces; using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Localization; namespace ClaudeDo.Installer.Checks; /// Both ports are configurable in the installer settings, so a conflict is only a warning. public sealed class PortCheck : IEnvironmentCheck { public const string CheckId = "ports"; private readonly IPortOwnerResolver _portOwnerResolver; public PortCheck(IPortOwnerResolver portOwnerResolver) => _portOwnerResolver = portOwnerResolver; public string Id => CheckId; public CheckSeverity Severity => CheckSeverity.Warning; public async Task RunAsync(InstallContext ctx, CancellationToken ct) { var loc = TrExtension.Localizer; var ports = new (string Label, int Port)[] { ("SignalR", ctx.SignalRPort), ("External MCP", ctx.ExternalMcpPort), }; var blocked = new List(); var ownWorker = new List(); foreach (var (label, port) in ports) { if (IsFree(port)) continue; var owner = await _portOwnerResolver.FindOwningProcessNameAsync(port, ct); if (owner is not null && owner.Contains("ClaudeDo.Worker", StringComparison.OrdinalIgnoreCase)) { ownWorker.Add($"{label} ({port})"); continue; } blocked.Add(owner is null ? loc?.Get("checks.ports.inUse", label, port) ?? $"{label} port {port} is in use." : loc?.Get("checks.ports.inUseBy", label, port, owner) ?? $"{label} port {port} is in use by '{owner}'."); } if (blocked.Count > 0) { return CheckResult.Fail(Id, Severity, "checks.ports.title", string.Join(" ", blocked), "checks.ports.hint"); } if (ownWorker.Count > 0) { var ownWorkerList = string.Join(", ", ownWorker); return CheckResult.Ok(Id, Severity, "checks.ports.title", loc?.Get("checks.ports.inUseByOwnWorker", ownWorkerList) ?? $"In use by the running ClaudeDo Worker: {ownWorkerList}."); } return CheckResult.Ok(Id, Severity, "checks.ports.title", loc?["checks.ports.available"] ?? "Ports are available."); } private static bool IsFree(int port) { try { using var listener = new TcpListener(IPAddress.Loopback, port); listener.Start(); return true; } catch (SocketException) { return false; } } }