feat: advertise cli path for remote ssh
This commit is contained in:
@@ -179,6 +179,10 @@ final class AppState {
|
|||||||
didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteProjectRoot, forKey: remoteProjectRootKey) } }
|
didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteProjectRoot, forKey: remoteProjectRootKey) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var remoteCliPath: String {
|
||||||
|
didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteCliPath, forKey: remoteCliPathKey) } }
|
||||||
|
}
|
||||||
|
|
||||||
private var earBoostTask: Task<Void, Never>?
|
private var earBoostTask: Task<Void, Never>?
|
||||||
|
|
||||||
init(preview: Bool = false) {
|
init(preview: Bool = false) {
|
||||||
@@ -235,6 +239,7 @@ final class AppState {
|
|||||||
self.remoteTarget = UserDefaults.standard.string(forKey: remoteTargetKey) ?? ""
|
self.remoteTarget = UserDefaults.standard.string(forKey: remoteTargetKey) ?? ""
|
||||||
self.remoteIdentity = UserDefaults.standard.string(forKey: remoteIdentityKey) ?? ""
|
self.remoteIdentity = UserDefaults.standard.string(forKey: remoteIdentityKey) ?? ""
|
||||||
self.remoteProjectRoot = UserDefaults.standard.string(forKey: remoteProjectRootKey) ?? ""
|
self.remoteProjectRoot = UserDefaults.standard.string(forKey: remoteProjectRootKey) ?? ""
|
||||||
|
self.remoteCliPath = UserDefaults.standard.string(forKey: remoteCliPathKey) ?? ""
|
||||||
self.canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
|
self.canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
|
||||||
self.peekabooBridgeEnabled = UserDefaults.standard
|
self.peekabooBridgeEnabled = UserDefaults.standard
|
||||||
.object(forKey: peekabooBridgeEnabledKey) as? Bool ?? true
|
.object(forKey: peekabooBridgeEnabledKey) as? Bool ?? true
|
||||||
@@ -368,6 +373,7 @@ extension AppState {
|
|||||||
state.remoteTarget = "user@example.com"
|
state.remoteTarget = "user@example.com"
|
||||||
state.remoteIdentity = "~/.ssh/id_ed25519"
|
state.remoteIdentity = "~/.ssh/id_ed25519"
|
||||||
state.remoteProjectRoot = "~/Projects/clawdis"
|
state.remoteProjectRoot = "~/Projects/clawdis"
|
||||||
|
state.remoteCliPath = ""
|
||||||
state.attachExistingGatewayOnly = false
|
state.attachExistingGatewayOnly = false
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ let connectionModeKey = "clawdis.connectionMode"
|
|||||||
let remoteTargetKey = "clawdis.remoteTarget"
|
let remoteTargetKey = "clawdis.remoteTarget"
|
||||||
let remoteIdentityKey = "clawdis.remoteIdentity"
|
let remoteIdentityKey = "clawdis.remoteIdentity"
|
||||||
let remoteProjectRootKey = "clawdis.remoteProjectRoot"
|
let remoteProjectRootKey = "clawdis.remoteProjectRoot"
|
||||||
|
let remoteCliPathKey = "clawdis.remoteCliPath"
|
||||||
let canvasEnabledKey = "clawdis.canvasEnabled"
|
let canvasEnabledKey = "clawdis.canvasEnabled"
|
||||||
let cameraEnabledKey = "clawdis.cameraEnabled"
|
let cameraEnabledKey = "clawdis.cameraEnabled"
|
||||||
let peekabooBridgeEnabledKey = "clawdis.peekabooBridgeEnabled"
|
let peekabooBridgeEnabledKey = "clawdis.peekabooBridgeEnabled"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ final class GatewayDiscoveryModel {
|
|||||||
var lanHost: String?
|
var lanHost: String?
|
||||||
var tailnetDns: String?
|
var tailnetDns: String?
|
||||||
var sshPort: Int
|
var sshPort: Int
|
||||||
|
var cliPath: String?
|
||||||
var stableID: String
|
var stableID: String
|
||||||
var debugID: String
|
var debugID: String
|
||||||
var isLocal: Bool
|
var isLocal: Bool
|
||||||
@@ -66,6 +67,7 @@ final class GatewayDiscoveryModel {
|
|||||||
var lanHost: String?
|
var lanHost: String?
|
||||||
var tailnetDns: String?
|
var tailnetDns: String?
|
||||||
var sshPort = 22
|
var sshPort = 22
|
||||||
|
var cliPath: String?
|
||||||
|
|
||||||
if let value = txt["lanHost"] {
|
if let value = txt["lanHost"] {
|
||||||
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
@@ -81,6 +83,10 @@ final class GatewayDiscoveryModel {
|
|||||||
{
|
{
|
||||||
sshPort = parsed
|
sshPort = parsed
|
||||||
}
|
}
|
||||||
|
if let value = txt["cliPath"] {
|
||||||
|
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
cliPath = trimmed.isEmpty ? nil : trimmed
|
||||||
|
}
|
||||||
|
|
||||||
let isLocal = Self.isLocalGateway(
|
let isLocal = Self.isLocalGateway(
|
||||||
lanHost: lanHost,
|
lanHost: lanHost,
|
||||||
@@ -93,6 +99,7 @@ final class GatewayDiscoveryModel {
|
|||||||
lanHost: lanHost,
|
lanHost: lanHost,
|
||||||
tailnetDns: tailnetDns,
|
tailnetDns: tailnetDns,
|
||||||
sshPort: sshPort,
|
sshPort: sshPort,
|
||||||
|
cliPath: cliPath,
|
||||||
stableID: BridgeEndpointID.stableID(result.endpoint),
|
stableID: BridgeEndpointID.stableID(result.endpoint),
|
||||||
debugID: BridgeEndpointID.prettyDescription(result.endpoint),
|
debugID: BridgeEndpointID.prettyDescription(result.endpoint),
|
||||||
isLocal: isLocal)
|
isLocal: isLocal)
|
||||||
|
|||||||
@@ -181,6 +181,11 @@ struct GeneralSettings: View {
|
|||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.frame(width: 280)
|
.frame(width: 280)
|
||||||
}
|
}
|
||||||
|
LabeledContent("CLI path") {
|
||||||
|
TextField("/Applications/Clawdis.app/.../clawdis", text: self.$state.remoteCliPath)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.frame(width: 280)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
} label: {
|
} label: {
|
||||||
@@ -612,6 +617,7 @@ extension GeneralSettings {
|
|||||||
target += ":\(gateway.sshPort)"
|
target += ":\(gateway.sshPort)"
|
||||||
}
|
}
|
||||||
self.state.remoteTarget = target
|
self.state.remoteTarget = target
|
||||||
|
self.state.remoteCliPath = gateway.cliPath ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -396,6 +396,14 @@ struct OnboardingView: View {
|
|||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.frame(width: fieldWidth)
|
.frame(width: fieldWidth)
|
||||||
}
|
}
|
||||||
|
GridRow {
|
||||||
|
Text("CLI path")
|
||||||
|
.font(.callout.weight(.semibold))
|
||||||
|
.frame(width: labelWidth, alignment: .leading)
|
||||||
|
TextField("/Applications/Clawdis.app/.../clawdis", text: self.$state.remoteCliPath)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.frame(width: fieldWidth)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Tip: keep Tailscale enabled so your gateway stays reachable.")
|
Text("Tip: keep Tailscale enabled so your gateway stays reachable.")
|
||||||
@@ -436,6 +444,7 @@ struct OnboardingView: View {
|
|||||||
}
|
}
|
||||||
self.state.remoteTarget = target
|
self.state.remoteTarget = target
|
||||||
}
|
}
|
||||||
|
self.state.remoteCliPath = gateway.cliPath ?? ""
|
||||||
|
|
||||||
self.state.connectionMode = .remote
|
self.state.connectionMode = .remote
|
||||||
MacNodeModeCoordinator.shared.setPreferredBridgeStableID(gateway.stableID)
|
MacNodeModeCoordinator.shared.setPreferredBridgeStableID(gateway.stableID)
|
||||||
|
|||||||
@@ -490,6 +490,7 @@ enum CommandResolver {
|
|||||||
].joined(separator: ":")
|
].joined(separator: ":")
|
||||||
let quotedArgs = ([subcommand] + extraArgs).map(self.shellQuote).joined(separator: " ")
|
let quotedArgs = ([subcommand] + extraArgs).map(self.shellQuote).joined(separator: " ")
|
||||||
let userPRJ = settings.projectRoot.trimmingCharacters(in: .whitespacesAndNewlines)
|
let userPRJ = settings.projectRoot.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let userCLI = settings.cliPath.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
let projectSection = if userPRJ.isEmpty {
|
let projectSection = if userPRJ.isEmpty {
|
||||||
"""
|
"""
|
||||||
@@ -506,9 +507,31 @@ enum CommandResolver {
|
|||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cliSection = if userCLI.isEmpty {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"""
|
||||||
|
CLI_HINT=\(self.shellQuote(userCLI))
|
||||||
|
if [ -n "$CLI_HINT" ]; then
|
||||||
|
if [ -x "$CLI_HINT" ]; then
|
||||||
|
CLI="$CLI_HINT"
|
||||||
|
"$CLI_HINT" \(quotedArgs);
|
||||||
|
exit $?;
|
||||||
|
elif [ -f "$CLI_HINT" ]; then
|
||||||
|
if command -v node >/dev/null 2>&1; then
|
||||||
|
CLI="node $CLI_HINT"
|
||||||
|
node "$CLI_HINT" \(quotedArgs);
|
||||||
|
exit $?;
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
let scriptBody = """
|
let scriptBody = """
|
||||||
PATH=\(exportedPath);
|
PATH=\(exportedPath);
|
||||||
CLI="";
|
CLI="";
|
||||||
|
\(cliSection)
|
||||||
\(projectSection)
|
\(projectSection)
|
||||||
if command -v clawdis >/dev/null 2>&1; then
|
if command -v clawdis >/dev/null 2>&1; then
|
||||||
CLI="$(command -v clawdis)"
|
CLI="$(command -v clawdis)"
|
||||||
@@ -543,6 +566,7 @@ enum CommandResolver {
|
|||||||
let target: String
|
let target: String
|
||||||
let identity: String
|
let identity: String
|
||||||
let projectRoot: String
|
let projectRoot: String
|
||||||
|
let cliPath: String
|
||||||
}
|
}
|
||||||
|
|
||||||
static func connectionSettings(defaults: UserDefaults = .standard) -> RemoteSettings {
|
static func connectionSettings(defaults: UserDefaults = .standard) -> RemoteSettings {
|
||||||
@@ -557,11 +581,13 @@ enum CommandResolver {
|
|||||||
let target = defaults.string(forKey: remoteTargetKey) ?? ""
|
let target = defaults.string(forKey: remoteTargetKey) ?? ""
|
||||||
let identity = defaults.string(forKey: remoteIdentityKey) ?? ""
|
let identity = defaults.string(forKey: remoteIdentityKey) ?? ""
|
||||||
let projectRoot = defaults.string(forKey: remoteProjectRootKey) ?? ""
|
let projectRoot = defaults.string(forKey: remoteProjectRootKey) ?? ""
|
||||||
|
let cliPath = defaults.string(forKey: remoteCliPathKey) ?? ""
|
||||||
return RemoteSettings(
|
return RemoteSettings(
|
||||||
mode: mode,
|
mode: mode,
|
||||||
target: self.sanitizedTarget(target),
|
target: self.sanitizedTarget(target),
|
||||||
identity: identity,
|
identity: identity,
|
||||||
projectRoot: projectRoot)
|
projectRoot: projectRoot,
|
||||||
|
cliPath: cliPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var attachExistingGatewayOnly: Bool {
|
static var attachExistingGatewayOnly: Bool {
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ The Gateway advertises small non-secret hints to make UI flows convenient:
|
|||||||
- `gatewayPort=<port>` (informational; the Gateway WS is typically loopback-only)
|
- `gatewayPort=<port>` (informational; the Gateway WS is typically loopback-only)
|
||||||
- `bridgePort=<port>` (only when bridge is enabled)
|
- `bridgePort=<port>` (only when bridge is enabled)
|
||||||
- `canvasPort=<port>` (only when the canvas host is running; enabled by default; default `18793`)
|
- `canvasPort=<port>` (only when the canvas host is running; enabled by default; default `18793`)
|
||||||
|
- `cliPath=<path>` (optional; absolute path to a runnable `clawdis` entrypoint or binary)
|
||||||
- `tailnetDns=<magicdns>` (optional hint; auto-detected from Tailscale when available; may be absent)
|
- `tailnetDns=<magicdns>` (optional hint; auto-detected from Tailscale when available; may be absent)
|
||||||
|
|
||||||
## Debugging on macOS
|
## Debugging on macOS
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ Troubleshooting and beacon details: `docs/bonjour.md`.
|
|||||||
- `gatewayPort=18789` (loopback WS port; informational)
|
- `gatewayPort=18789` (loopback WS port; informational)
|
||||||
- `bridgePort=18790` (when bridge is enabled)
|
- `bridgePort=18790` (when bridge is enabled)
|
||||||
- `canvasPort=18793` (when the canvas host is running; enabled by default)
|
- `canvasPort=18793` (when the canvas host is running; enabled by default)
|
||||||
- `tailnetDns=<magicdns>` (optional hint; auto-detected when Tailscale is available)
|
- `cliPath=<path>` (optional; absolute path to a runnable `clawdis` entrypoint or binary)
|
||||||
|
- `tailnetDns=<magicdns>` (optional hint; auto-detected when Tailscale is available)
|
||||||
|
|
||||||
Disable/override:
|
Disable/override:
|
||||||
- `CLAWDIS_DISABLE_BONJOUR=1` disables advertising.
|
- `CLAWDIS_DISABLE_BONJOUR=1` disables advertising.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ This flow lets the macOS app act as a full remote control for a Clawdis gateway
|
|||||||
- If the gateway is on the same LAN and advertises Bonjour, pick it from the discovered list to auto-fill this field.
|
- If the gateway is on the same LAN and advertises Bonjour, pick it from the discovered list to auto-fill this field.
|
||||||
- **Identity file** (advanced): path to your key.
|
- **Identity file** (advanced): path to your key.
|
||||||
- **Project root** (advanced): remote checkout path used for commands.
|
- **Project root** (advanced): remote checkout path used for commands.
|
||||||
|
- **CLI path** (advanced): optional path to a runnable `clawdis` entrypoint/binary (auto-filled when advertised).
|
||||||
3) Hit **Test remote**. Success indicates the remote `clawdis status --json` runs correctly. Failures usually mean PATH/CLI issues; exit 127 means the CLI isn’t found remotely.
|
3) Hit **Test remote**. Success indicates the remote `clawdis status --json` runs correctly. Failures usually mean PATH/CLI issues; exit 127 means the CLI isn’t found remotely.
|
||||||
4) Health checks and Web Chat will now run through this SSH tunnel automatically.
|
4) Health checks and Web Chat will now run through this SSH tunnel automatically.
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
type CanvasHostServer,
|
type CanvasHostServer,
|
||||||
startCanvasHost,
|
startCanvasHost,
|
||||||
} from "../canvas-host/server.js";
|
} from "../canvas-host/server.js";
|
||||||
|
import { handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
||||||
import { createDefaultDeps } from "../cli/deps.js";
|
import { createDefaultDeps } from "../cli/deps.js";
|
||||||
import { agentCommand } from "../commands/agent.js";
|
import { agentCommand } from "../commands/agent.js";
|
||||||
import { getHealthSnapshot, type HealthSummary } from "../commands/health.js";
|
import { getHealthSnapshot, type HealthSummary } from "../commands/health.js";
|
||||||
@@ -105,6 +106,37 @@ import { handleControlUiHttpRequest } from "./control-ui.js";
|
|||||||
|
|
||||||
ensureClawdisCliOnPath();
|
ensureClawdisCliOnPath();
|
||||||
|
|
||||||
|
function resolveBonjourCliPath(): string | undefined {
|
||||||
|
const envPath = process.env.CLAWDIS_CLI_PATH?.trim();
|
||||||
|
if (envPath) return envPath;
|
||||||
|
|
||||||
|
const isFile = (candidate: string) => {
|
||||||
|
try {
|
||||||
|
return fs.statSync(candidate).isFile();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const execDir = path.dirname(process.execPath);
|
||||||
|
const siblingCli = path.join(execDir, "clawdis");
|
||||||
|
if (isFile(siblingCli)) return siblingCli;
|
||||||
|
|
||||||
|
const argvPath = process.argv[1];
|
||||||
|
if (argvPath && isFile(argvPath)) {
|
||||||
|
const base = path.basename(argvPath);
|
||||||
|
if (!base.includes("gateway-daemon")) return argvPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const distCli = path.join(cwd, "dist", "index.js");
|
||||||
|
if (isFile(distCli)) return distCli;
|
||||||
|
const binCli = path.join(cwd, "bin", "clawdis.js");
|
||||||
|
if (isFile(binCli)) return binCli;
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
let stopBrowserControlServerIfStarted: (() => Promise<void>) | null = null;
|
let stopBrowserControlServerIfStarted: (() => Promise<void>) | null = null;
|
||||||
|
|
||||||
async function startBrowserControlServerIfEnabled(): Promise<void> {
|
async function startBrowserControlServerIfEnabled(): Promise<void> {
|
||||||
@@ -976,6 +1008,9 @@ export async function startGatewayServer(
|
|||||||
"gateway bind is tailnet, but no tailnet interface was found; refusing to start gateway",
|
"gateway bind is tailnet, but no tailnet interface was found; refusing to start gateway",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const tailnetIPv4 = pickPrimaryTailnetIPv4();
|
||||||
|
const tailnetIPv6 = pickPrimaryTailnetIPv6();
|
||||||
|
const hasTailnet = Boolean(tailnetIPv4 || tailnetIPv6);
|
||||||
const controlUiEnabled =
|
const controlUiEnabled =
|
||||||
opts.controlUiEnabled ?? cfgForServer.gateway?.controlUi?.enabled ?? true;
|
opts.controlUiEnabled ?? cfgForServer.gateway?.controlUi?.enabled ?? true;
|
||||||
if (!isLoopbackHost(bindHost) && !getGatewayToken()) {
|
if (!isLoopbackHost(bindHost) && !getGatewayToken()) {
|
||||||
@@ -988,13 +1023,20 @@ export async function startGatewayServer(
|
|||||||
// Don't interfere with WebSocket upgrades; ws handles the 'upgrade' event.
|
// Don't interfere with WebSocket upgrades; ws handles the 'upgrade' event.
|
||||||
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
||||||
|
|
||||||
if (controlUiEnabled) {
|
void (async () => {
|
||||||
if (handleControlUiHttpRequest(req, res)) return;
|
if (await handleA2uiHttpRequest(req, res)) return;
|
||||||
}
|
if (controlUiEnabled) {
|
||||||
|
if (handleControlUiHttpRequest(req, res)) return;
|
||||||
|
}
|
||||||
|
|
||||||
res.statusCode = 404;
|
res.statusCode = 404;
|
||||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
res.end("Not Found");
|
res.end("Not Found");
|
||||||
|
})().catch((err) => {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||||
|
res.end(String(err));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
let bonjourStop: (() => Promise<void>) | null = null;
|
let bonjourStop: (() => Promise<void>) | null = null;
|
||||||
let bridge: Awaited<ReturnType<typeof startNodeBridgeServer>> | null = null;
|
let bridge: Awaited<ReturnType<typeof startNodeBridgeServer>> | null = null;
|
||||||
@@ -1057,6 +1099,7 @@ export async function startGatewayServer(
|
|||||||
const canvasHostEnabled =
|
const canvasHostEnabled =
|
||||||
process.env.CLAWDIS_SKIP_CANVAS_HOST !== "1" &&
|
process.env.CLAWDIS_SKIP_CANVAS_HOST !== "1" &&
|
||||||
cfgAtStart.canvasHost?.enabled !== false;
|
cfgAtStart.canvasHost?.enabled !== false;
|
||||||
|
const preferGatewayA2uiHost = hasTailnet && !isLoopbackHost(bindHost);
|
||||||
|
|
||||||
if (canvasHostEnabled) {
|
if (canvasHostEnabled) {
|
||||||
try {
|
try {
|
||||||
@@ -2029,7 +2072,7 @@ export async function startGatewayServer(
|
|||||||
host: bridgeHost,
|
host: bridgeHost,
|
||||||
port: bridgePort,
|
port: bridgePort,
|
||||||
serverName: machineDisplayName,
|
serverName: machineDisplayName,
|
||||||
canvasHostPort: canvasHost?.port,
|
canvasHostPort: preferGatewayA2uiHost ? port : canvasHost?.port,
|
||||||
onRequest: (nodeId, req) => handleBridgeRequest(nodeId, req),
|
onRequest: (nodeId, req) => handleBridgeRequest(nodeId, req),
|
||||||
onAuthenticated: async (node) => {
|
onAuthenticated: async (node) => {
|
||||||
const host = node.displayName?.trim() || node.nodeId;
|
const host = node.displayName?.trim() || node.nodeId;
|
||||||
@@ -2148,6 +2191,7 @@ export async function startGatewayServer(
|
|||||||
canvasPort: canvasHost?.port,
|
canvasPort: canvasHost?.port,
|
||||||
sshPort,
|
sshPort,
|
||||||
tailnetDns,
|
tailnetDns,
|
||||||
|
cliPath: resolveBonjourCliPath(),
|
||||||
});
|
});
|
||||||
bonjourStop = bonjour.stop;
|
bonjourStop = bonjour.stop;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -2318,7 +2362,10 @@ export async function startGatewayServer(
|
|||||||
const remoteAddr = (
|
const remoteAddr = (
|
||||||
socket as WebSocket & { _socket?: { remoteAddress?: string } }
|
socket as WebSocket & { _socket?: { remoteAddress?: string } }
|
||||||
)._socket?.remoteAddress;
|
)._socket?.remoteAddress;
|
||||||
const canvasHostUrl = deriveCanvasHostUrl(req, canvasHost?.port);
|
const canvasHostUrl = deriveCanvasHostUrl(
|
||||||
|
req,
|
||||||
|
preferGatewayA2uiHost ? port : canvasHost?.port,
|
||||||
|
);
|
||||||
logWs("in", "open", { connId, remoteAddr });
|
logWs("in", "open", { connId, remoteAddr });
|
||||||
const isWebchatConnect = (params: ConnectParams | null | undefined) =>
|
const isWebchatConnect = (params: ConnectParams | null | undefined) =>
|
||||||
params?.client?.mode === "webchat" ||
|
params?.client?.mode === "webchat" ||
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ describe("gateway bonjour advertiser", () => {
|
|||||||
sshPort: 2222,
|
sshPort: 2222,
|
||||||
bridgePort: 18790,
|
bridgePort: 18790,
|
||||||
tailnetDns: "host.tailnet.ts.net",
|
tailnetDns: "host.tailnet.ts.net",
|
||||||
|
cliPath: "/opt/homebrew/bin/clawdis",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(createService).toHaveBeenCalledTimes(1);
|
expect(createService).toHaveBeenCalledTimes(1);
|
||||||
@@ -116,6 +117,9 @@ describe("gateway bonjour advertiser", () => {
|
|||||||
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.sshPort).toBe(
|
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.sshPort).toBe(
|
||||||
"2222",
|
"2222",
|
||||||
);
|
);
|
||||||
|
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.cliPath).toBe(
|
||||||
|
"/opt/homebrew/bin/clawdis",
|
||||||
|
);
|
||||||
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.transport).toBe(
|
expect((bridgeCall?.[0]?.txt as Record<string, string>)?.transport).toBe(
|
||||||
"bridge",
|
"bridge",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type GatewayBonjourAdvertiseOpts = {
|
|||||||
bridgePort?: number;
|
bridgePort?: number;
|
||||||
canvasPort?: number;
|
canvasPort?: number;
|
||||||
tailnetDns?: string;
|
tailnetDns?: string;
|
||||||
|
cliPath?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isDisabledByEnv() {
|
function isDisabledByEnv() {
|
||||||
@@ -115,6 +116,9 @@ export async function startGatewayBonjourAdvertiser(
|
|||||||
if (typeof opts.tailnetDns === "string" && opts.tailnetDns.trim()) {
|
if (typeof opts.tailnetDns === "string" && opts.tailnetDns.trim()) {
|
||||||
txtBase.tailnetDns = opts.tailnetDns.trim();
|
txtBase.tailnetDns = opts.tailnetDns.trim();
|
||||||
}
|
}
|
||||||
|
if (typeof opts.cliPath === "string" && opts.cliPath.trim()) {
|
||||||
|
txtBase.cliPath = opts.cliPath.trim();
|
||||||
|
}
|
||||||
|
|
||||||
const services: Array<{ label: string; svc: BonjourService }> = [];
|
const services: Array<{ label: string; svc: BonjourService }> = [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user