feat(macOS): add gateway password auth support and fix Swift 6.2 concurrency

- Add CLAWDIS_GATEWAY_PASSWORD to launchd plist environment
- Read password from gateway.remote.password config in client
- Fix Swift 6.2 sending parameter violations in config save functions
- Add password parameter to GatewayConnection.Config type
- GatewayChannel now sends password in connect auth params
- GatewayEndpointStore and GatewayLaunchAgentManager read password from config
- CLI gateway client reads password from remote config and env
This commit is contained in:
Jefferson Nunn
2026-01-01 21:34:46 -06:00
parent 9387ecf043
commit fe87d6d8be
12 changed files with 203 additions and 61 deletions

View File

@@ -280,28 +280,56 @@ struct TailscaleIntegrationSection: View {
return
}
let (success, errorMessage) = await TailscaleIntegrationSection.buildAndSaveTailscaleConfig(
tailscaleMode: self.tailscaleMode,
requireCredentialsForServe: self.requireCredentialsForServe,
password: trimmedPassword,
connectionMode: self.connectionMode,
isPaused: self.isPaused
)
if !success, let errorMessage {
self.statusMessage = errorMessage
return
}
if self.connectionMode == .local, !self.isPaused {
self.statusMessage = "Saved to ~/.clawdis/clawdis.json. Restarting gateway…"
} else {
self.statusMessage = "Saved to ~/.clawdis/clawdis.json. Restart the gateway to apply."
}
self.restartGatewayIfNeeded()
}
private nonisolated static func buildAndSaveTailscaleConfig(
tailscaleMode: GatewayTailscaleMode,
requireCredentialsForServe: Bool,
password: String,
connectionMode: AppState.ConnectionMode,
isPaused: Bool
) async -> (Bool, String?) {
var root = await ConfigStore.load()
var gateway = root["gateway"] as? [String: Any] ?? [:]
var tailscale = gateway["tailscale"] as? [String: Any] ?? [:]
tailscale["mode"] = self.tailscaleMode.rawValue
tailscale["mode"] = tailscaleMode.rawValue
gateway["tailscale"] = tailscale
if self.tailscaleMode != .off {
if tailscaleMode != .off {
gateway["bind"] = "loopback"
}
if self.tailscaleMode == .off {
if tailscaleMode == .off {
gateway.removeValue(forKey: "auth")
} else {
var auth = gateway["auth"] as? [String: Any] ?? [:]
if self.tailscaleMode == .serve, !self.requireCredentialsForServe {
if tailscaleMode == .serve, !requireCredentialsForServe {
auth["allowTailscale"] = true
auth.removeValue(forKey: "mode")
auth.removeValue(forKey: "password")
} else {
auth["allowTailscale"] = false
auth["mode"] = "password"
auth["password"] = trimmedPassword
auth["password"] = password
}
if auth.isEmpty {
@@ -319,17 +347,10 @@ struct TailscaleIntegrationSection: View {
do {
try await ConfigStore.save(root)
} catch {
self.statusMessage = error.localizedDescription
return
return (true, nil)
} catch let error {
return (false, error.localizedDescription)
}
if self.connectionMode == .local, !self.isPaused {
self.statusMessage = "Saved to ~/.clawdis/clawdis.json. Restarting gateway…"
} else {
self.statusMessage = "Saved to ~/.clawdis/clawdis.json. Restart the gateway to apply."
}
self.restartGatewayIfNeeded()
}
private func restartGatewayIfNeeded() {