fix(macos): honor discovered gateway ports
This commit is contained in:
committed by
Peter Steinberger
parent
eef90b47a3
commit
a5b29623b8
@@ -123,6 +123,35 @@ enum ClawdbotConfigFile {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func remoteGatewayPort() -> Int? {
|
||||||
|
let root = self.loadDict()
|
||||||
|
guard let gateway = root["gateway"] as? [String: Any],
|
||||||
|
let remote = gateway["remote"] as? [String: Any],
|
||||||
|
let raw = remote["url"] as? String
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
guard !trimmed.isEmpty, let url = URL(string: trimmed), let port = url.port, port > 0 else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setRemoteGatewayUrl(host: String, port: Int?) {
|
||||||
|
guard let port, port > 0 else { return }
|
||||||
|
let trimmedHost = host.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
guard !trimmedHost.isEmpty else { return }
|
||||||
|
self.updateGatewayDict { gateway in
|
||||||
|
var remote = gateway["remote"] as? [String: Any] ?? [:]
|
||||||
|
let existingUrl = (remote["url"] as? String)?
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||||
|
let scheme = URL(string: existingUrl)?.scheme ?? "ws"
|
||||||
|
remote["url"] = "\(scheme)://\(trimmedHost):\(port)"
|
||||||
|
gateway["remote"] = remote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func parseConfigData(_ data: Data) -> [String: Any]? {
|
private static func parseConfigData(_ data: Data) -> [String: Any]? {
|
||||||
if let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
if let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||||
return root
|
return root
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ final class GatewayDiscoveryModel {
|
|||||||
var lanHost: String?
|
var lanHost: String?
|
||||||
var tailnetDns: String?
|
var tailnetDns: String?
|
||||||
var sshPort: Int
|
var sshPort: Int
|
||||||
|
var gatewayPort: Int?
|
||||||
var cliPath: String?
|
var cliPath: String?
|
||||||
var stableID: String
|
var stableID: String
|
||||||
var debugID: String
|
var debugID: String
|
||||||
@@ -138,6 +139,7 @@ final class GatewayDiscoveryModel {
|
|||||||
lanHost: parsedTXT.lanHost,
|
lanHost: parsedTXT.lanHost,
|
||||||
tailnetDns: parsedTXT.tailnetDns,
|
tailnetDns: parsedTXT.tailnetDns,
|
||||||
sshPort: parsedTXT.sshPort,
|
sshPort: parsedTXT.sshPort,
|
||||||
|
gatewayPort: parsedTXT.gatewayPort,
|
||||||
cliPath: parsedTXT.cliPath,
|
cliPath: parsedTXT.cliPath,
|
||||||
stableID: stableID,
|
stableID: stableID,
|
||||||
debugID: BridgeEndpointID.prettyDescription(result.endpoint),
|
debugID: BridgeEndpointID.prettyDescription(result.endpoint),
|
||||||
@@ -207,11 +209,12 @@ final class GatewayDiscoveryModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func parseGatewayTXT(_ txt: [String: String])
|
static func parseGatewayTXT(_ txt: [String: String])
|
||||||
-> (lanHost: String?, tailnetDns: String?, sshPort: Int, cliPath: String?)
|
-> (lanHost: String?, tailnetDns: String?, sshPort: Int, gatewayPort: Int?, cliPath: String?)
|
||||||
{
|
{
|
||||||
var lanHost: String?
|
var lanHost: String?
|
||||||
var tailnetDns: String?
|
var tailnetDns: String?
|
||||||
var sshPort = 22
|
var sshPort = 22
|
||||||
|
var gatewayPort: Int?
|
||||||
var cliPath: String?
|
var cliPath: String?
|
||||||
|
|
||||||
if let value = txt["lanHost"] {
|
if let value = txt["lanHost"] {
|
||||||
@@ -228,12 +231,18 @@ final class GatewayDiscoveryModel {
|
|||||||
{
|
{
|
||||||
sshPort = parsed
|
sshPort = parsed
|
||||||
}
|
}
|
||||||
|
if let value = txt["gatewayPort"],
|
||||||
|
let parsed = Int(value.trimmingCharacters(in: .whitespacesAndNewlines)),
|
||||||
|
parsed > 0
|
||||||
|
{
|
||||||
|
gatewayPort = parsed
|
||||||
|
}
|
||||||
if let value = txt["cliPath"] {
|
if let value = txt["cliPath"] {
|
||||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
cliPath = trimmed.isEmpty ? nil : trimmed
|
cliPath = trimmed.isEmpty ? nil : trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
return (lanHost, tailnetDns, sshPort, cliPath)
|
return (lanHost, tailnetDns, sshPort, gatewayPort, cliPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func buildSSHTarget(user: String, host: String, port: Int) -> String {
|
static func buildSSHTarget(user: String, host: String, port: Int) -> String {
|
||||||
|
|||||||
@@ -694,6 +694,7 @@ extension GeneralSettings {
|
|||||||
host: host,
|
host: host,
|
||||||
port: gateway.sshPort)
|
port: gateway.sshPort)
|
||||||
self.state.remoteCliPath = gateway.cliPath ?? ""
|
self.state.remoteCliPath = gateway.cliPath ?? ""
|
||||||
|
ClawdbotConfigFile.setRemoteGatewayUrl(host: host, port: gateway.gatewayPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,19 @@ final class MacNodeModeCoordinator {
|
|||||||
return 18790
|
return 18790
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func remoteBridgePort() -> Int {
|
||||||
|
let fallback = Int(Self.loopbackBridgePort() ?? 18790)
|
||||||
|
let base = ClawdbotConfigFile.remoteGatewayPort() ?? GatewayEnvironment.gatewayPort()
|
||||||
|
guard base > 0 else { return fallback }
|
||||||
|
return Self.derivePort(base: base, offset: 1, fallback: fallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func derivePort(base: Int, offset: Int, fallback: Int) -> Int {
|
||||||
|
let derived = base + offset
|
||||||
|
guard derived > 0, derived <= Int(UInt16.max) else { return fallback }
|
||||||
|
return derived
|
||||||
|
}
|
||||||
|
|
||||||
static func probeEndpoint(_ endpoint: NWEndpoint, timeoutSeconds: Double) async -> Bool {
|
static func probeEndpoint(_ endpoint: NWEndpoint, timeoutSeconds: Double) async -> Bool {
|
||||||
let connection = NWConnection(to: endpoint, using: .tcp)
|
let connection = NWConnection(to: endpoint, using: .tcp)
|
||||||
let stream = Self.makeStateStream(for: connection)
|
let stream = Self.makeStateStream(for: connection)
|
||||||
@@ -269,7 +282,10 @@ final class MacNodeModeCoordinator {
|
|||||||
if mode == .remote {
|
if mode == .remote {
|
||||||
do {
|
do {
|
||||||
if self.tunnel == nil || self.tunnel?.process.isRunning == false {
|
if self.tunnel == nil || self.tunnel?.process.isRunning == false {
|
||||||
self.tunnel = try await RemotePortTunnel.create(remotePort: 18790)
|
let remotePort = Self.remoteBridgePort()
|
||||||
|
self.tunnel = try await RemotePortTunnel.create(
|
||||||
|
remotePort: remotePort,
|
||||||
|
allowRemoteUrlOverride: false)
|
||||||
}
|
}
|
||||||
if let localPort = self.tunnel?.localPort,
|
if let localPort = self.tunnel?.localPort,
|
||||||
let port = NWEndpoint.Port(rawValue: localPort)
|
let port = NWEndpoint.Port(rawValue: localPort)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ extension OnboardingView {
|
|||||||
user: user,
|
user: user,
|
||||||
host: host,
|
host: host,
|
||||||
port: gateway.sshPort)
|
port: gateway.sshPort)
|
||||||
|
ClawdbotConfigFile.setRemoteGatewayUrl(host: host, port: gateway.gatewayPort)
|
||||||
}
|
}
|
||||||
self.state.remoteCliPath = gateway.cliPath ?? ""
|
self.state.remoteCliPath = gateway.cliPath ?? ""
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ extension OnboardingView {
|
|||||||
lanHost: "bridge.local",
|
lanHost: "bridge.local",
|
||||||
tailnetDns: "bridge.ts.net",
|
tailnetDns: "bridge.ts.net",
|
||||||
sshPort: 2222,
|
sshPort: 2222,
|
||||||
|
gatewayPort: 18789,
|
||||||
cliPath: "/usr/local/bin/clawdbot",
|
cliPath: "/usr/local/bin/clawdbot",
|
||||||
stableID: "bridge-1",
|
stableID: "bridge-1",
|
||||||
debugID: "bridge-1",
|
debugID: "bridge-1",
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ final class RemotePortTunnel {
|
|||||||
Task { await PortGuardian.shared.removeRecord(pid: pid) }
|
Task { await PortGuardian.shared.removeRecord(pid: pid) }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func create(remotePort: Int, preferredLocalPort: UInt16? = nil) async throws -> RemotePortTunnel {
|
static func create(
|
||||||
|
remotePort: Int,
|
||||||
|
preferredLocalPort: UInt16? = nil,
|
||||||
|
allowRemoteUrlOverride: Bool = true
|
||||||
|
) async throws -> RemotePortTunnel {
|
||||||
let settings = CommandResolver.connectionSettings()
|
let settings = CommandResolver.connectionSettings()
|
||||||
guard settings.mode == .remote, let parsed = CommandResolver.parseSSHTarget(settings.target) else {
|
guard settings.mode == .remote, let parsed = CommandResolver.parseSSHTarget(settings.target) else {
|
||||||
throw NSError(
|
throw NSError(
|
||||||
@@ -49,7 +53,7 @@ final class RemotePortTunnel {
|
|||||||
|
|
||||||
let localPort = try await Self.findPort(preferred: preferredLocalPort)
|
let localPort = try await Self.findPort(preferred: preferredLocalPort)
|
||||||
let sshHost = parsed.host.trimmingCharacters(in: .whitespacesAndNewlines)
|
let sshHost = parsed.host.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let remotePortOverride = Self.resolveRemotePortOverride(for: sshHost)
|
let remotePortOverride = allowRemoteUrlOverride ? Self.resolveRemotePortOverride(for: sshHost) : nil
|
||||||
let resolvedRemotePort = remotePortOverride ?? remotePort
|
let resolvedRemotePort = remotePortOverride ?? remotePort
|
||||||
if let override = remotePortOverride {
|
if let override = remotePortOverride {
|
||||||
Self.logger.info(
|
Self.logger.info(
|
||||||
|
|||||||
Reference in New Issue
Block a user