fix(macos): parse SSH port correctly in remote connection test
- Fix "Test Remote" button failing for non-standard SSH ports (e.g., user@host:2222) The raw target string was passed directly to SSH, which interpreted host:2222 as the hostname. Now uses -p flag for the port, matching RemotePortTunnel.swift. - Fix SSH target field auto-repopulating after being cleared The config file watcher would reconstruct the SSH target from gateway.remote.url, undoing user edits. Removed auto-population from applyConfigOverrides(); initial population still happens in init() for first-time setup only. - Document gateway.remote.sshTarget and gateway.remote.sshIdentity config keys - Add non-standard SSH port example to docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a486940781
commit
1e15174a0a
@ -403,30 +403,10 @@ final class AppState {
|
||||
self.remoteUrl = remoteUrlText
|
||||
}
|
||||
|
||||
let targetMode = desiredMode ?? self.connectionMode
|
||||
if targetMode == .remote,
|
||||
remoteTransport != .direct,
|
||||
let host = AppState.remoteHost(from: remoteUrl)
|
||||
{
|
||||
self.updateRemoteTarget(host: host)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateRemoteTarget(host: String) {
|
||||
let trimmed = self.remoteTarget.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard let parsed = CommandResolver.parseSSHTarget(trimmed) else { return }
|
||||
let trimmedUser = parsed.user?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let user = (trimmedUser?.isEmpty ?? true) ? nil : trimmedUser
|
||||
let port = parsed.port
|
||||
let assembled: String
|
||||
if let user {
|
||||
assembled = port == 22 ? "\(user)@\(host)" : "\(user)@\(host):\(port)"
|
||||
} else {
|
||||
assembled = port == 22 ? host : "\(host):\(port)"
|
||||
}
|
||||
if assembled != self.remoteTarget {
|
||||
self.remoteTarget = assembled
|
||||
}
|
||||
// Note: We intentionally do NOT auto-populate remoteTarget here.
|
||||
// The SSH target is user-controlled via the UI; auto-populating it from
|
||||
// gateway.remote.url would override user edits (including clearing the field).
|
||||
// Initial population happens in init() for first-time setup only.
|
||||
}
|
||||
|
||||
private func syncGatewayConfigIfNeeded() {
|
||||
|
||||
@ -588,6 +588,11 @@ extension GeneralSettings {
|
||||
}
|
||||
|
||||
private static func sshCheckCommand(target: String, identity: String) -> [String] {
|
||||
guard let parsed = CommandResolver.parseSSHTarget(target) else {
|
||||
// Fallback: pass target as-is (will likely fail, but matches old behavior)
|
||||
return ["/usr/bin/ssh", target, "echo ok"]
|
||||
}
|
||||
|
||||
var args: [String] = [
|
||||
"/usr/bin/ssh",
|
||||
"-o", "BatchMode=yes",
|
||||
@ -595,10 +600,14 @@ extension GeneralSettings {
|
||||
"-o", "StrictHostKeyChecking=accept-new",
|
||||
"-o", "UpdateHostKeys=yes",
|
||||
]
|
||||
if parsed.port > 0 && parsed.port != 22 {
|
||||
args.append(contentsOf: ["-p", String(parsed.port)])
|
||||
}
|
||||
if !identity.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
args.append(contentsOf: ["-i", identity])
|
||||
}
|
||||
args.append(target)
|
||||
let userHost = parsed.user.map { "\($0)@\(parsed.host)" } ?? parsed.host
|
||||
args.append(userHost)
|
||||
args.append("echo ok")
|
||||
return args
|
||||
}
|
||||
|
||||
@ -2890,6 +2890,8 @@ Auth and Tailscale:
|
||||
Remote client defaults (CLI):
|
||||
- `gateway.remote.url` sets the default Gateway WebSocket URL for CLI calls when `gateway.mode = "remote"`.
|
||||
- `gateway.remote.transport` selects the macOS remote transport (`ssh` default, `direct` for ws/wss). When `direct`, `gateway.remote.url` must be `ws://` or `wss://`. `ws://host` defaults to port `18789`.
|
||||
- `gateway.remote.sshTarget` sets the SSH target for CLI probes and macOS app tunneling (`user@host` or `user@host:port`; port defaults to 22).
|
||||
- `gateway.remote.sshIdentity` sets the SSH identity file path for remote connections.
|
||||
- `gateway.remote.token` supplies the token for remote calls (leave unset for no auth).
|
||||
- `gateway.remote.password` supplies the password for remote calls (leave unset for no auth).
|
||||
|
||||
@ -2911,6 +2913,23 @@ macOS app behavior:
|
||||
}
|
||||
```
|
||||
|
||||
SSH tunnel example with non-standard SSH port (macOS app):
|
||||
|
||||
```json5
|
||||
{
|
||||
gateway: {
|
||||
mode: "remote",
|
||||
remote: {
|
||||
transport: "ssh",
|
||||
sshTarget: "admin@gateway.example.com:2222",
|
||||
sshIdentity: "~/.ssh/id_ed25519",
|
||||
url: "ws://127.0.0.1:18789",
|
||||
token: "your-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Direct transport example (macOS app):
|
||||
|
||||
```json5
|
||||
|
||||
@ -27,7 +27,8 @@ Remote mode supports two transports:
|
||||
1) Open *Settings → General*.
|
||||
2) Under **Clawdbot runs**, pick **Remote over SSH** and set:
|
||||
- **Transport**: **SSH tunnel** or **Direct (ws/wss)**.
|
||||
- **SSH target**: `user@host` (optional `:port`).
|
||||
- **SSH target**: `user@host` or `user@host:port` (port defaults to 22).
|
||||
- Example with non-standard SSH port: `admin@gateway.example.com:2222`
|
||||
- If the gateway is on the same LAN and advertises Bonjour, pick it from the discovered list to auto-fill this field.
|
||||
- **Gateway URL** (Direct only): `wss://gateway.example.ts.net` (or `ws://...` for local/LAN).
|
||||
- **Identity file** (advanced): path to your key.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user