feat: support configurable gateway port
This commit is contained in:
@@ -106,4 +106,20 @@ enum ClawdisConfigFile {
|
||||
return remote["password"] as? String
|
||||
}
|
||||
|
||||
static func gatewayPort() -> Int? {
|
||||
let root = self.loadDict()
|
||||
guard let gateway = root["gateway"] as? [String: Any] else { return nil }
|
||||
if let port = gateway["port"] as? Int, port > 0 { return port }
|
||||
if let number = gateway["port"] as? NSNumber, number.intValue > 0 {
|
||||
return number.intValue
|
||||
}
|
||||
if let raw = gateway["port"] as? String,
|
||||
let parsed = Int(raw.trimmingCharacters(in: .whitespacesAndNewlines)),
|
||||
parsed > 0
|
||||
{
|
||||
return parsed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ final class ControlChannel {
|
||||
"Reason: \(reason)"
|
||||
}
|
||||
|
||||
// Common misfire: we connected to localhost:18789 but the port is occupied
|
||||
// Common misfire: we connected to the configured localhost port but it is occupied
|
||||
// by some other process (e.g. a local dev gateway or a stuck SSH forward).
|
||||
// The gateway handshake returns something we can't parse, which currently
|
||||
// surfaces as "hello failed (unexpected response)". Give the user a pointer
|
||||
|
||||
@@ -306,7 +306,7 @@ struct DebugSettings: View {
|
||||
}
|
||||
|
||||
if self.portReports.isEmpty, !self.portCheckInFlight {
|
||||
Text("Check which process owns 18789 and suggest fixes.")
|
||||
Text("Check which process owns \(GatewayEnvironment.gatewayPort()) and suggest fixes.")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
@@ -946,7 +946,7 @@ extension DebugSettings {
|
||||
view.portCheckInFlight = true
|
||||
view.portReports = [
|
||||
DebugActions.PortReport(
|
||||
port: 18789,
|
||||
port: GatewayEnvironment.gatewayPort(),
|
||||
expected: "Gateway websocket (node/tsx)",
|
||||
status: .missing("Missing"),
|
||||
listeners: []),
|
||||
|
||||
@@ -72,6 +72,13 @@ enum GatewayEnvironment {
|
||||
}
|
||||
|
||||
static func gatewayPort() -> Int {
|
||||
if let raw = ProcessInfo.processInfo.environment["CLAWDIS_GATEWAY_PORT"] {
|
||||
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let parsed = Int(trimmed), parsed > 0 { return parsed }
|
||||
}
|
||||
if let configPort = ClawdisConfigFile.gatewayPort(), configPort > 0 {
|
||||
return configPort
|
||||
}
|
||||
let stored = UserDefaults.standard.integer(forKey: "gatewayPort")
|
||||
return stored > 0 ? stored : 18789
|
||||
}
|
||||
|
||||
@@ -188,7 +188,8 @@ final class HealthStore {
|
||||
if let error = self.lastError, !error.isEmpty {
|
||||
let lower = error.lowercased()
|
||||
if lower.contains("connection refused") {
|
||||
return "The gateway control port (127.0.0.1:18789) isn’t listening — restart Clawdis to bring it back."
|
||||
let port = GatewayEnvironment.gatewayPort()
|
||||
return "The gateway control port (127.0.0.1:\(port)) isn’t listening — restart Clawdis to bring it back."
|
||||
}
|
||||
if lower.contains("timeout") {
|
||||
return "Timed out waiting for the control server; the gateway may be crashed or still starting."
|
||||
|
||||
@@ -24,7 +24,7 @@ extension OnboardingView {
|
||||
discoveryModel: discovery)
|
||||
view.needsBootstrap = true
|
||||
view.localGatewayProbe = LocalGatewayProbe(
|
||||
port: 18789,
|
||||
port: GatewayEnvironment.gatewayPort(),
|
||||
pid: 123,
|
||||
command: "clawdis-gateway",
|
||||
expected: true)
|
||||
|
||||
@@ -42,7 +42,7 @@ actor PortGuardian {
|
||||
self.logger.info("port sweep skipped (mode=unconfigured)")
|
||||
return
|
||||
}
|
||||
let ports = [18789]
|
||||
let ports = [GatewayEnvironment.gatewayPort()]
|
||||
for port in ports {
|
||||
let listeners = await self.listeners(on: port)
|
||||
guard !listeners.isEmpty else { continue }
|
||||
@@ -148,7 +148,7 @@ actor PortGuardian {
|
||||
if mode == .unconfigured {
|
||||
return []
|
||||
}
|
||||
let ports = [18789]
|
||||
let ports = [GatewayEnvironment.gatewayPort()]
|
||||
var reports: [PortReport] = []
|
||||
|
||||
for port in ports {
|
||||
@@ -279,7 +279,8 @@ actor PortGuardian {
|
||||
return .init(port: port, expected: expectedDesc, status: .missing(text), listeners: [])
|
||||
}
|
||||
|
||||
let tunnelUnhealthy = mode == .remote && port == 18789 && tunnelHealthy == false
|
||||
let tunnelUnhealthy =
|
||||
mode == .remote && port == GatewayEnvironment.gatewayPort() && tunnelHealthy == false
|
||||
let reportListeners = listeners.map { listener in
|
||||
var expected = okPredicate(listener)
|
||||
if tunnelUnhealthy, expected { expected = false }
|
||||
@@ -347,7 +348,7 @@ actor PortGuardian {
|
||||
switch mode {
|
||||
case .remote:
|
||||
// Remote mode expects an SSH tunnel for the gateway WebSocket port.
|
||||
if port == 18789 { return cmd.contains("ssh") }
|
||||
if port == GatewayEnvironment.gatewayPort() { return cmd.contains("ssh") }
|
||||
return false
|
||||
case .local:
|
||||
return expectedCommands.contains { cmd.contains($0) }
|
||||
@@ -361,7 +362,7 @@ actor PortGuardian {
|
||||
mode: AppState.ConnectionMode,
|
||||
listeners: [Listener]) async -> Bool?
|
||||
{
|
||||
guard mode == .remote, port == 18789, !listeners.isEmpty else { return nil }
|
||||
guard mode == .remote, port == GatewayEnvironment.gatewayPort(), !listeners.isEmpty else { return nil }
|
||||
let hasSsh = listeners.contains { $0.command.lowercased().contains("ssh") }
|
||||
guard hasSsh else { return nil }
|
||||
return await self.probeGatewayHealth(port: port)
|
||||
|
||||
@@ -38,7 +38,7 @@ actor RemoteTunnelManager {
|
||||
}
|
||||
|
||||
/// Ensure an SSH tunnel is running for the gateway control port.
|
||||
/// Returns the local forwarded port (usually 18789).
|
||||
/// Returns the local forwarded port (usually the configured gateway port).
|
||||
func ensureControlTunnel() async throws -> UInt16 {
|
||||
let settings = CommandResolver.connectionSettings()
|
||||
guard settings.mode == .remote else {
|
||||
|
||||
Reference in New Issue
Block a user