53 lines
1.9 KiB
Swift
53 lines
1.9 KiB
Swift
import Foundation
|
|
|
|
/// Manages the SSH tunnel that forwards the remote gateway/control port to localhost.
|
|
actor RemoteTunnelManager {
|
|
static let shared = RemoteTunnelManager()
|
|
|
|
private var controlTunnel: RemotePortTunnel?
|
|
|
|
func controlTunnelPortIfRunning() async -> UInt16? {
|
|
if let tunnel = self.controlTunnel,
|
|
tunnel.process.isRunning,
|
|
let local = tunnel.localPort
|
|
{
|
|
return local
|
|
}
|
|
// If a previous Clawdis run already has an SSH listener on the expected port (common after restarts),
|
|
// reuse it instead of spawning new ssh processes that immediately fail with "Address already in use".
|
|
let desiredPort = UInt16(GatewayEnvironment.gatewayPort())
|
|
if let desc = await PortGuardian.shared.describe(port: Int(desiredPort)),
|
|
desc.command.lowercased().contains("ssh")
|
|
{
|
|
return desiredPort
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/// Ensure an SSH tunnel is running for the gateway control port.
|
|
/// Returns the local forwarded port (usually 18789).
|
|
func ensureControlTunnel() async throws -> UInt16 {
|
|
let settings = CommandResolver.connectionSettings()
|
|
guard settings.mode == .remote else {
|
|
throw NSError(
|
|
domain: "RemoteTunnel",
|
|
code: 1,
|
|
userInfo: [NSLocalizedDescriptionKey: "Remote mode is not enabled"])
|
|
}
|
|
|
|
if let local = await self.controlTunnelPortIfRunning() { return local }
|
|
|
|
let desiredPort = UInt16(GatewayEnvironment.gatewayPort())
|
|
let tunnel = try await RemotePortTunnel.create(
|
|
remotePort: GatewayEnvironment.gatewayPort(),
|
|
preferredLocalPort: desiredPort)
|
|
self.controlTunnel = tunnel
|
|
return tunnel.localPort ?? desiredPort
|
|
}
|
|
|
|
func stopAll() {
|
|
self.controlTunnel?.terminate()
|
|
self.controlTunnel = nil
|
|
}
|
|
}
|