diff --git a/apps/macos/Sources/Clawdis/GatewayEnvironment.swift b/apps/macos/Sources/Clawdis/GatewayEnvironment.swift index d552c3cdb..1f5d4d4f3 100644 --- a/apps/macos/Sources/Clawdis/GatewayEnvironment.swift +++ b/apps/macos/Sources/Clawdis/GatewayEnvironment.swift @@ -63,6 +63,7 @@ struct GatewayCommandResolution { enum GatewayEnvironment { private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "gateway.env") + private static let supportedBindModes: Set = ["loopback", "tailnet", "lan", "auto"] static func bundledGatewayExecutable() -> String? { guard let res = Bundle.main.resourceURL else { return nil } @@ -198,7 +199,8 @@ enum GatewayEnvironment { let port = self.gatewayPort() if let bundled { - let cmd = [bundled, "gateway-daemon", "--port", "\(port)", "--bind", "loopback"] + let bind = self.preferredGatewayBind() ?? "loopback" + let cmd = [bundled, "gateway-daemon", "--port", "\(port)", "--bind", bind] return GatewayCommandResolution(status: status, command: cmd) } if let gatewayBin { @@ -216,6 +218,27 @@ enum GatewayEnvironment { return GatewayCommandResolution(status: status, command: nil) } + private static func preferredGatewayBind() -> String? { + if let env = ProcessInfo.processInfo.environment["CLAWDIS_GATEWAY_BIND"] { + let trimmed = env.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + if self.supportedBindModes.contains(trimmed) { + return trimmed + } + } + + let root = ClawdisConfigFile.loadDict() + if let gateway = root["gateway"] as? [String: Any], + let bind = gateway["bind"] as? String + { + let trimmed = bind.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + if self.supportedBindModes.contains(trimmed) { + return trimmed + } + } + + return nil + } + static func installGlobal(version: Semver?, statusHandler: @escaping @Sendable (String) -> Void) async { let preferred = CommandResolver.preferredPaths().joined(separator: ":") let target = version?.description ?? "latest" diff --git a/apps/macos/Sources/Clawdis/GatewayLaunchAgentManager.swift b/apps/macos/Sources/Clawdis/GatewayLaunchAgentManager.swift index 050ea42da..4a222b3f2 100644 --- a/apps/macos/Sources/Clawdis/GatewayLaunchAgentManager.swift +++ b/apps/macos/Sources/Clawdis/GatewayLaunchAgentManager.swift @@ -1,6 +1,8 @@ import Foundation enum GatewayLaunchAgentManager { + private static let supportedBindModes: Set = ["loopback", "tailnet", "lan", "auto"] + private static var plistURL: URL { FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent("Library/LaunchAgents/\(gatewayLaunchdLabel).plist") @@ -52,6 +54,20 @@ enum GatewayLaunchAgentManager { let relayDir = self.relayDir(bundlePath: bundlePath) let preferredPath = ([relayDir] + CommandResolver.preferredPaths()) .joined(separator: ":") + let bind = self.preferredGatewayBind() ?? "loopback" + let token = self.preferredGatewayToken() + var envEntries = """ + PATH + \(preferredPath) + CLAWDIS_IMAGE_BACKEND + sips + """ + if let token { + envEntries += """ + CLAWDIS_GATEWAY_TOKEN + \(token) + """ + } let plist = """ @@ -66,7 +82,7 @@ enum GatewayLaunchAgentManager { --port \(port) --bind - loopback + \(bind) WorkingDirectory \(FileManager.default.homeDirectoryForCurrentUser.path) @@ -76,10 +92,7 @@ enum GatewayLaunchAgentManager { EnvironmentVariables - PATH - \(preferredPath) - CLAWDIS_IMAGE_BACKEND - sips + \(envEntries) StandardOutPath \(LogLocator.launchdGatewayLogPath) @@ -91,6 +104,33 @@ enum GatewayLaunchAgentManager { try? plist.write(to: self.plistURL, atomically: true, encoding: .utf8) } + private static func preferredGatewayBind() -> String? { + if let env = ProcessInfo.processInfo.environment["CLAWDIS_GATEWAY_BIND"] { + let trimmed = env.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + if self.supportedBindModes.contains(trimmed) { + return trimmed + } + } + + let root = ClawdisConfigFile.loadDict() + if let gateway = root["gateway"] as? [String: Any], + let bind = gateway["bind"] as? String + { + let trimmed = bind.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + if self.supportedBindModes.contains(trimmed) { + return trimmed + } + } + + return nil + } + + private static func preferredGatewayToken() -> String? { + let raw = ProcessInfo.processInfo.environment["CLAWDIS_GATEWAY_TOKEN"] ?? "" + let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty ? nil : trimmed + } + private struct LaunchctlResult { let status: Int32 let output: String