macOS: add remote gateway token field

This commit is contained in:
Conrad 2026-01-27 01:14:47 -08:00
parent 6372242da7
commit a7be243b7a
2 changed files with 52 additions and 0 deletions

View File

@ -213,6 +213,10 @@ final class AppState {
didSet { self.syncGatewayConfigIfNeeded() }
}
var remoteToken: String {
didSet { self.syncGatewayConfigIfNeeded() }
}
var remoteIdentity: String {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteIdentity, forKey: remoteIdentityKey) } }
}
@ -278,6 +282,7 @@ final class AppState {
let configRoot = MoltbotConfigFile.loadDict()
let configRemoteUrl = GatewayRemoteConfig.resolveUrlString(root: configRoot)
let configRemoteTransport = GatewayRemoteConfig.resolveTransport(root: configRoot)
let configRemoteToken = Self.resolveRemoteToken(root: configRoot)
let resolvedConnectionMode = ConnectionModeResolver.resolve(root: configRoot).mode
self.remoteTransport = configRemoteTransport
self.connectionMode = resolvedConnectionMode
@ -293,6 +298,7 @@ final class AppState {
self.remoteTarget = storedRemoteTarget
}
self.remoteUrl = configRemoteUrl ?? ""
self.remoteToken = configRemoteToken
self.remoteIdentity = UserDefaults.standard.string(forKey: remoteIdentityKey) ?? ""
self.remoteProjectRoot = UserDefaults.standard.string(forKey: remoteProjectRootKey) ?? ""
self.remoteCliPath = UserDefaults.standard.string(forKey: remoteCliPathKey) ?? ""
@ -352,6 +358,16 @@ final class AppState {
return trimmed
}
private static func resolveRemoteToken(root: [String: Any]) -> String {
guard let gateway = root["gateway"] as? [String: Any],
let remote = gateway["remote"] as? [String: Any],
let token = remote["token"] as? String
else {
return ""
}
return token.trimmingCharacters(in: .whitespacesAndNewlines)
}
private func startConfigWatcher() {
let configUrl = MoltbotConfigFile.url()
self.configWatcher = ConfigFileWatcher(url: configUrl) { [weak self] in
@ -375,6 +391,7 @@ final class AppState {
.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty ?? true)
let remoteTransport = GatewayRemoteConfig.resolveTransport(root: root)
let remoteToken = Self.resolveRemoteToken(root: root)
let desiredMode: ConnectionMode? = switch modeRaw {
case "local":
@ -402,6 +419,9 @@ final class AppState {
if remoteUrlText != self.remoteUrl {
self.remoteUrl = remoteUrlText
}
if remoteToken != self.remoteToken {
self.remoteToken = remoteToken
}
let targetMode = desiredMode ?? self.connectionMode
if targetMode == .remote,
@ -437,6 +457,7 @@ final class AppState {
let remoteIdentity = self.remoteIdentity
let remoteTransport = self.remoteTransport
let remoteUrl = self.remoteUrl
let remoteToken = self.remoteToken
let desiredMode: String? = switch connectionMode {
case .local:
"local"
@ -529,6 +550,17 @@ final class AppState {
}
}
let trimmedToken = remoteToken.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedToken.isEmpty {
if (remote["token"] as? String) != trimmedToken {
remote["token"] = trimmedToken
remoteChanged = true
}
} else if remote["token"] != nil {
remote.removeValue(forKey: "token")
remoteChanged = true
}
if remoteChanged {
gateway["remote"] = remote
changed = true
@ -684,6 +716,7 @@ extension AppState {
state.canvasEnabled = true
state.remoteTarget = "user@example.com"
state.remoteUrl = "wss://gateway.example.ts.net"
state.remoteToken = "example-token"
state.remoteIdentity = "~/.ssh/id_ed25519"
state.remoteProjectRoot = "~/Projects/moltbot"
state.remoteCliPath = ""

View File

@ -145,6 +145,8 @@ struct GeneralSettings: View {
self.remoteDirectRow
}
self.remoteTokenRow
GatewayDiscoveryInlineList(
discovery: self.gatewayDiscovery,
currentTarget: self.state.remoteTarget,
@ -305,6 +307,23 @@ struct GeneralSettings: View {
}
}
private var remoteTokenRow: some View {
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .center, spacing: 10) {
Text("Gateway token")
.font(.callout.weight(.semibold))
.frame(width: self.remoteLabelWidth, alignment: .leading)
SecureField("Shared gateway auth token", text: self.$state.remoteToken)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: .infinity)
}
Text("Must match gateway.auth.token (or gateway.remote.token) on the gateway host.")
.font(.caption)
.foregroundStyle(.secondary)
.padding(.leading, self.remoteLabelWidth + 10)
}
}
private var controlStatusLine: String {
switch ControlChannel.shared.state {
case .connected: "Connected"