feat: expand daemon status diagnostics
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const callGateway = vi.fn(async () => ({ ok: true }));
|
const callGateway = vi.fn(async () => ({ ok: true }));
|
||||||
const resolveGatewayProgramArguments = vi.fn(async () => ({
|
const resolveGatewayProgramArguments = vi.fn(async () => ({
|
||||||
@@ -13,8 +13,8 @@ const serviceIsLoaded = vi.fn().mockResolvedValue(false);
|
|||||||
const serviceReadCommand = vi.fn().mockResolvedValue(null);
|
const serviceReadCommand = vi.fn().mockResolvedValue(null);
|
||||||
const serviceReadRuntime = vi.fn().mockResolvedValue({ status: "running" });
|
const serviceReadRuntime = vi.fn().mockResolvedValue({ status: "running" });
|
||||||
const findExtraGatewayServices = vi.fn(async () => []);
|
const findExtraGatewayServices = vi.fn(async () => []);
|
||||||
const inspectPortUsage = vi.fn(async () => ({
|
const inspectPortUsage = vi.fn(async (port: number) => ({
|
||||||
port: 18789,
|
port,
|
||||||
status: "free",
|
status: "free",
|
||||||
listeners: [],
|
listeners: [],
|
||||||
hints: [],
|
hints: [],
|
||||||
@@ -77,6 +77,39 @@ vi.mock("./deps.js", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe("daemon-cli coverage", () => {
|
describe("daemon-cli coverage", () => {
|
||||||
|
const originalEnv = {
|
||||||
|
CLAWDBOT_STATE_DIR: process.env.CLAWDBOT_STATE_DIR,
|
||||||
|
CLAWDBOT_CONFIG_PATH: process.env.CLAWDBOT_CONFIG_PATH,
|
||||||
|
CLAWDBOT_GATEWAY_PORT: process.env.CLAWDBOT_GATEWAY_PORT,
|
||||||
|
CLAWDBOT_PROFILE: process.env.CLAWDBOT_PROFILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = "/tmp/clawdbot-cli-state";
|
||||||
|
process.env.CLAWDBOT_CONFIG_PATH = "/tmp/clawdbot-cli-state/clawdbot.json";
|
||||||
|
delete process.env.CLAWDBOT_GATEWAY_PORT;
|
||||||
|
delete process.env.CLAWDBOT_PROFILE;
|
||||||
|
serviceReadCommand.mockResolvedValue(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (originalEnv.CLAWDBOT_STATE_DIR !== undefined)
|
||||||
|
process.env.CLAWDBOT_STATE_DIR = originalEnv.CLAWDBOT_STATE_DIR;
|
||||||
|
else delete process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
|
||||||
|
if (originalEnv.CLAWDBOT_CONFIG_PATH !== undefined)
|
||||||
|
process.env.CLAWDBOT_CONFIG_PATH = originalEnv.CLAWDBOT_CONFIG_PATH;
|
||||||
|
else delete process.env.CLAWDBOT_CONFIG_PATH;
|
||||||
|
|
||||||
|
if (originalEnv.CLAWDBOT_GATEWAY_PORT !== undefined)
|
||||||
|
process.env.CLAWDBOT_GATEWAY_PORT = originalEnv.CLAWDBOT_GATEWAY_PORT;
|
||||||
|
else delete process.env.CLAWDBOT_GATEWAY_PORT;
|
||||||
|
|
||||||
|
if (originalEnv.CLAWDBOT_PROFILE !== undefined)
|
||||||
|
process.env.CLAWDBOT_PROFILE = originalEnv.CLAWDBOT_PROFILE;
|
||||||
|
else delete process.env.CLAWDBOT_PROFILE;
|
||||||
|
});
|
||||||
|
|
||||||
it("probes gateway status by default", async () => {
|
it("probes gateway status by default", async () => {
|
||||||
runtimeLogs.length = 0;
|
runtimeLogs.length = 0;
|
||||||
runtimeErrors.length = 0;
|
runtimeErrors.length = 0;
|
||||||
@@ -97,6 +130,51 @@ describe("daemon-cli coverage", () => {
|
|||||||
expect(inspectPortUsage).toHaveBeenCalled();
|
expect(inspectPortUsage).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("derives probe URL from service args + env (json)", async () => {
|
||||||
|
runtimeLogs.length = 0;
|
||||||
|
runtimeErrors.length = 0;
|
||||||
|
callGateway.mockClear();
|
||||||
|
inspectPortUsage.mockClear();
|
||||||
|
|
||||||
|
serviceReadCommand.mockResolvedValueOnce({
|
||||||
|
programArguments: ["/bin/node", "cli", "gateway", "--port", "19001"],
|
||||||
|
environment: {
|
||||||
|
CLAWDBOT_PROFILE: "dev",
|
||||||
|
CLAWDBOT_STATE_DIR: "/tmp/clawdbot-daemon-state",
|
||||||
|
CLAWDBOT_CONFIG_PATH: "/tmp/clawdbot-daemon-state/clawdbot.json",
|
||||||
|
CLAWDBOT_GATEWAY_PORT: "19001",
|
||||||
|
},
|
||||||
|
sourcePath: "/tmp/com.clawdbot.gateway.plist",
|
||||||
|
});
|
||||||
|
|
||||||
|
const { registerDaemonCli } = await import("./daemon-cli.js");
|
||||||
|
const program = new Command();
|
||||||
|
program.exitOverride();
|
||||||
|
registerDaemonCli(program);
|
||||||
|
|
||||||
|
await program.parseAsync(["daemon", "status", "--json"], { from: "user" });
|
||||||
|
|
||||||
|
expect(callGateway).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
url: "ws://127.0.0.1:19001",
|
||||||
|
method: "status",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(inspectPortUsage).toHaveBeenCalledWith(19001);
|
||||||
|
|
||||||
|
const parsed = JSON.parse(runtimeLogs[0] ?? "{}") as {
|
||||||
|
gateway?: { port?: number; portSource?: string; probeUrl?: string };
|
||||||
|
config?: { mismatch?: boolean };
|
||||||
|
rpc?: { url?: string; ok?: boolean };
|
||||||
|
};
|
||||||
|
expect(parsed.gateway?.port).toBe(19001);
|
||||||
|
expect(parsed.gateway?.portSource).toBe("service args");
|
||||||
|
expect(parsed.gateway?.probeUrl).toBe("ws://127.0.0.1:19001");
|
||||||
|
expect(parsed.config?.mismatch).toBe(true);
|
||||||
|
expect(parsed.rpc?.url).toBe("ws://127.0.0.1:19001");
|
||||||
|
expect(parsed.rpc?.ok).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("passes deep scan flag for daemon status", async () => {
|
it("passes deep scan flag for daemon status", async () => {
|
||||||
findExtraGatewayServices.mockClear();
|
findExtraGatewayServices.mockClear();
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ import {
|
|||||||
GATEWAY_SYSTEMD_SERVICE_NAME,
|
GATEWAY_SYSTEMD_SERVICE_NAME,
|
||||||
GATEWAY_WINDOWS_TASK_NAME,
|
GATEWAY_WINDOWS_TASK_NAME,
|
||||||
} from "../daemon/constants.js";
|
} from "../daemon/constants.js";
|
||||||
|
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
||||||
import {
|
import {
|
||||||
type FindExtraGatewayServicesOptions,
|
type FindExtraGatewayServicesOptions,
|
||||||
findExtraGatewayServices,
|
findExtraGatewayServices,
|
||||||
renderGatewayServiceCleanupHints,
|
renderGatewayServiceCleanupHints,
|
||||||
} from "../daemon/inspect.js";
|
} from "../daemon/inspect.js";
|
||||||
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
|
||||||
import { resolveGatewayLogPaths } from "../daemon/launchd.js";
|
import { resolveGatewayLogPaths } from "../daemon/launchd.js";
|
||||||
import { findLegacyGatewayServices } from "../daemon/legacy.js";
|
import { findLegacyGatewayServices } from "../daemon/legacy.js";
|
||||||
import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
||||||
@@ -165,8 +165,11 @@ function parsePortFromArgs(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickProbeHostForBind(bindMode: string, tailnetIPv4: string | null) {
|
function pickProbeHostForBind(
|
||||||
if (bindMode === "tailnet") return tailnetIPv4;
|
bindMode: string,
|
||||||
|
tailnetIPv4: string | undefined,
|
||||||
|
) {
|
||||||
|
if (bindMode === "tailnet") return tailnetIPv4 ?? "127.0.0.1";
|
||||||
if (bindMode === "auto") return tailnetIPv4 ?? "127.0.0.1";
|
if (bindMode === "auto") return tailnetIPv4 ?? "127.0.0.1";
|
||||||
return "127.0.0.1";
|
return "127.0.0.1";
|
||||||
}
|
}
|
||||||
@@ -330,7 +333,10 @@ async function gatherDaemonStatus(opts: {
|
|||||||
...(serviceEnv ?? {}),
|
...(serviceEnv ?? {}),
|
||||||
} satisfies Record<string, string | undefined>;
|
} satisfies Record<string, string | undefined>;
|
||||||
|
|
||||||
const cliConfigPath = resolveConfigPath(process.env, resolveStateDir(process.env));
|
const cliConfigPath = resolveConfigPath(
|
||||||
|
process.env,
|
||||||
|
resolveStateDir(process.env),
|
||||||
|
);
|
||||||
const daemonConfigPath = resolveConfigPath(
|
const daemonConfigPath = resolveConfigPath(
|
||||||
mergedDaemonEnv as NodeJS.ProcessEnv,
|
mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||||
resolveStateDir(mergedDaemonEnv as NodeJS.ProcessEnv),
|
resolveStateDir(mergedDaemonEnv as NodeJS.ProcessEnv),
|
||||||
@@ -359,12 +365,15 @@ async function gatherDaemonStatus(opts: {
|
|||||||
path: daemonSnapshot?.path ?? daemonConfigPath,
|
path: daemonSnapshot?.path ?? daemonConfigPath,
|
||||||
exists: daemonSnapshot?.exists ?? false,
|
exists: daemonSnapshot?.exists ?? false,
|
||||||
valid: daemonSnapshot?.valid ?? true,
|
valid: daemonSnapshot?.valid ?? true,
|
||||||
...(daemonSnapshot?.issues?.length ? { issues: daemonSnapshot.issues } : {}),
|
...(daemonSnapshot?.issues?.length
|
||||||
|
? { issues: daemonSnapshot.issues }
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
const configMismatch = cliConfigSummary.path !== daemonConfigSummary.path;
|
const configMismatch = cliConfigSummary.path !== daemonConfigSummary.path;
|
||||||
|
|
||||||
const portFromArgs = parsePortFromArgs(command?.programArguments);
|
const portFromArgs = parsePortFromArgs(command?.programArguments);
|
||||||
const daemonPort = portFromArgs ?? resolveGatewayPort(daemonCfg, mergedDaemonEnv);
|
const daemonPort =
|
||||||
|
portFromArgs ?? resolveGatewayPort(daemonCfg, mergedDaemonEnv);
|
||||||
const portSource: GatewayStatusSummary["portSource"] = portFromArgs
|
const portSource: GatewayStatusSummary["portSource"] = portFromArgs
|
||||||
? "service args"
|
? "service args"
|
||||||
: "env/config";
|
: "env/config";
|
||||||
@@ -510,7 +519,9 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
|||||||
defaultRuntime.log(`Config (cli): ${cliCfg}`);
|
defaultRuntime.log(`Config (cli): ${cliCfg}`);
|
||||||
if (!status.config.cli.valid && status.config.cli.issues?.length) {
|
if (!status.config.cli.valid && status.config.cli.issues?.length) {
|
||||||
for (const issue of status.config.cli.issues.slice(0, 5)) {
|
for (const issue of status.config.cli.issues.slice(0, 5)) {
|
||||||
defaultRuntime.error(`Config issue: ${issue.path || "<root>"}: ${issue.message}`);
|
defaultRuntime.error(
|
||||||
|
`Config issue: ${issue.path || "<root>"}: ${issue.message}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (status.config.daemon) {
|
if (status.config.daemon) {
|
||||||
@@ -555,7 +566,9 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
|||||||
} else {
|
} else {
|
||||||
defaultRuntime.error("RPC probe: failed");
|
defaultRuntime.error("RPC probe: failed");
|
||||||
if (rpc.url) defaultRuntime.error(`RPC target: ${rpc.url}`);
|
if (rpc.url) defaultRuntime.error(`RPC target: ${rpc.url}`);
|
||||||
const lines = String(rpc.error ?? "unknown").split(/\r?\n/).filter(Boolean);
|
const lines = String(rpc.error ?? "unknown")
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.filter(Boolean);
|
||||||
for (const line of lines.slice(0, 12)) {
|
for (const line of lines.slice(0, 12)) {
|
||||||
defaultRuntime.error(` ${line}`);
|
defaultRuntime.error(` ${line}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,16 @@ export function resolveGatewayLogPaths(
|
|||||||
stderrPath: string;
|
stderrPath: string;
|
||||||
} {
|
} {
|
||||||
const home = resolveHomeDir(env);
|
const home = resolveHomeDir(env);
|
||||||
const logDir = path.join(home, ".clawdbot", "logs");
|
const stateOverride =
|
||||||
|
env.CLAWDBOT_STATE_DIR?.trim() || env.CLAWDIS_STATE_DIR?.trim();
|
||||||
|
const profile = env.CLAWDBOT_PROFILE?.trim();
|
||||||
|
const suffix =
|
||||||
|
profile && profile.toLowerCase() !== "default" ? `-${profile}` : "";
|
||||||
|
const defaultStateDir = path.join(home, `.clawdbot${suffix}`);
|
||||||
|
const stateDir = stateOverride
|
||||||
|
? resolveUserPathWithHome(stateOverride, home)
|
||||||
|
: defaultStateDir;
|
||||||
|
const logDir = path.join(stateDir, "logs");
|
||||||
return {
|
return {
|
||||||
logDir,
|
logDir,
|
||||||
stdoutPath: path.join(logDir, "gateway.log"),
|
stdoutPath: path.join(logDir, "gateway.log"),
|
||||||
@@ -47,6 +56,16 @@ export function resolveGatewayLogPaths(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveUserPathWithHome(input: string, home: string): string {
|
||||||
|
const trimmed = input.trim();
|
||||||
|
if (!trimmed) return trimmed;
|
||||||
|
if (trimmed.startsWith("~")) {
|
||||||
|
const expanded = trimmed.replace(/^~(?=$|[\\/])/, home);
|
||||||
|
return path.resolve(expanded);
|
||||||
|
}
|
||||||
|
return path.resolve(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
function plistEscape(value: string): string {
|
function plistEscape(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replaceAll("&", "&")
|
.replaceAll("&", "&")
|
||||||
@@ -88,7 +107,12 @@ function renderEnvDict(
|
|||||||
|
|
||||||
export async function readLaunchAgentProgramArguments(
|
export async function readLaunchAgentProgramArguments(
|
||||||
env: Record<string, string | undefined>,
|
env: Record<string, string | undefined>,
|
||||||
): Promise<{ programArguments: string[]; workingDirectory?: string } | null> {
|
): Promise<{
|
||||||
|
programArguments: string[];
|
||||||
|
workingDirectory?: string;
|
||||||
|
environment?: Record<string, string>;
|
||||||
|
sourcePath?: string;
|
||||||
|
} | null> {
|
||||||
const plistPath = resolveLaunchAgentPlistPath(env);
|
const plistPath = resolveLaunchAgentPlistPath(env);
|
||||||
try {
|
try {
|
||||||
const plist = await fs.readFile(plistPath, "utf8");
|
const plist = await fs.readFile(plistPath, "utf8");
|
||||||
@@ -105,9 +129,25 @@ export async function readLaunchAgentProgramArguments(
|
|||||||
const workingDirectory = workingDirMatch
|
const workingDirectory = workingDirMatch
|
||||||
? plistUnescape(workingDirMatch[1] ?? "").trim()
|
? plistUnescape(workingDirMatch[1] ?? "").trim()
|
||||||
: "";
|
: "";
|
||||||
|
const envMatch = plist.match(
|
||||||
|
/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/i,
|
||||||
|
);
|
||||||
|
const environment: Record<string, string> = {};
|
||||||
|
if (envMatch) {
|
||||||
|
for (const pair of envMatch[1].matchAll(
|
||||||
|
/<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/gi,
|
||||||
|
)) {
|
||||||
|
const key = plistUnescape(pair[1] ?? "").trim();
|
||||||
|
if (!key) continue;
|
||||||
|
const value = plistUnescape(pair[2] ?? "").trim();
|
||||||
|
environment[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
programArguments: args.filter(Boolean),
|
programArguments: args.filter(Boolean),
|
||||||
...(workingDirectory ? { workingDirectory } : {}),
|
...(workingDirectory ? { workingDirectory } : {}),
|
||||||
|
...(Object.keys(environment).length > 0 ? { environment } : {}),
|
||||||
|
sourcePath: plistPath,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ export type GatewayService = {
|
|||||||
readCommand: (env: Record<string, string | undefined>) => Promise<{
|
readCommand: (env: Record<string, string | undefined>) => Promise<{
|
||||||
programArguments: string[];
|
programArguments: string[];
|
||||||
workingDirectory?: string;
|
workingDirectory?: string;
|
||||||
|
environment?: Record<string, string>;
|
||||||
|
sourcePath?: string;
|
||||||
} | null>;
|
} | null>;
|
||||||
readRuntime: (
|
readRuntime: (
|
||||||
env: Record<string, string | undefined>,
|
env: Record<string, string | undefined>,
|
||||||
|
|||||||
@@ -191,12 +191,18 @@ function parseSystemdExecStart(value: string): string[] {
|
|||||||
|
|
||||||
export async function readSystemdServiceExecStart(
|
export async function readSystemdServiceExecStart(
|
||||||
env: Record<string, string | undefined>,
|
env: Record<string, string | undefined>,
|
||||||
): Promise<{ programArguments: string[]; workingDirectory?: string } | null> {
|
): Promise<{
|
||||||
|
programArguments: string[];
|
||||||
|
workingDirectory?: string;
|
||||||
|
environment?: Record<string, string>;
|
||||||
|
sourcePath?: string;
|
||||||
|
} | null> {
|
||||||
const unitPath = resolveSystemdUnitPath(env);
|
const unitPath = resolveSystemdUnitPath(env);
|
||||||
try {
|
try {
|
||||||
const content = await fs.readFile(unitPath, "utf8");
|
const content = await fs.readFile(unitPath, "utf8");
|
||||||
let execStart = "";
|
let execStart = "";
|
||||||
let workingDirectory = "";
|
let workingDirectory = "";
|
||||||
|
const environment: Record<string, string> = {};
|
||||||
for (const rawLine of content.split("\n")) {
|
for (const rawLine of content.split("\n")) {
|
||||||
const line = rawLine.trim();
|
const line = rawLine.trim();
|
||||||
if (!line || line.startsWith("#")) continue;
|
if (!line || line.startsWith("#")) continue;
|
||||||
@@ -204,6 +210,10 @@ export async function readSystemdServiceExecStart(
|
|||||||
execStart = line.slice("ExecStart=".length).trim();
|
execStart = line.slice("ExecStart=".length).trim();
|
||||||
} else if (line.startsWith("WorkingDirectory=")) {
|
} else if (line.startsWith("WorkingDirectory=")) {
|
||||||
workingDirectory = line.slice("WorkingDirectory=".length).trim();
|
workingDirectory = line.slice("WorkingDirectory=".length).trim();
|
||||||
|
} else if (line.startsWith("Environment=")) {
|
||||||
|
const raw = line.slice("Environment=".length).trim();
|
||||||
|
const parsed = parseSystemdEnvAssignment(raw);
|
||||||
|
if (parsed) environment[parsed.key] = parsed.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!execStart) return null;
|
if (!execStart) return null;
|
||||||
@@ -211,12 +221,47 @@ export async function readSystemdServiceExecStart(
|
|||||||
return {
|
return {
|
||||||
programArguments,
|
programArguments,
|
||||||
...(workingDirectory ? { workingDirectory } : {}),
|
...(workingDirectory ? { workingDirectory } : {}),
|
||||||
|
...(Object.keys(environment).length > 0 ? { environment } : {}),
|
||||||
|
sourcePath: unitPath,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseSystemdEnvAssignment(
|
||||||
|
raw: string,
|
||||||
|
): { key: string; value: string } | null {
|
||||||
|
const trimmed = raw.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
|
||||||
|
const unquoted = (() => {
|
||||||
|
if (!(trimmed.startsWith('"') && trimmed.endsWith('"'))) return trimmed;
|
||||||
|
let out = "";
|
||||||
|
let escapeNext = false;
|
||||||
|
for (const ch of trimmed.slice(1, -1)) {
|
||||||
|
if (escapeNext) {
|
||||||
|
out += ch;
|
||||||
|
escapeNext = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === "\\") {
|
||||||
|
escapeNext = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out += ch;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const eq = unquoted.indexOf("=");
|
||||||
|
if (eq <= 0) return null;
|
||||||
|
const key = unquoted.slice(0, eq).trim();
|
||||||
|
if (!key) return null;
|
||||||
|
const value = unquoted.slice(eq + 1);
|
||||||
|
return { key, value };
|
||||||
|
}
|
||||||
|
|
||||||
export type SystemdServiceInfo = {
|
export type SystemdServiceInfo = {
|
||||||
activeState?: string;
|
activeState?: string;
|
||||||
subState?: string;
|
subState?: string;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { loadConfig, resolveGatewayPort } from "../config/config.js";
|
import {
|
||||||
|
loadConfig,
|
||||||
|
resolveConfigPath,
|
||||||
|
resolveGatewayPort,
|
||||||
|
resolveStateDir,
|
||||||
|
} from "../config/config.js";
|
||||||
import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
|
import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
|
||||||
import { GatewayClient } from "./client.js";
|
import { GatewayClient } from "./client.js";
|
||||||
import { PROTOCOL_VERSION } from "./protocol/index.js";
|
import { PROTOCOL_VERSION } from "./protocol/index.js";
|
||||||
@@ -34,6 +39,10 @@ export function buildGatewayConnectionDetails(
|
|||||||
options: { config?: ClawdbotConfig; url?: string } = {},
|
options: { config?: ClawdbotConfig; url?: string } = {},
|
||||||
): GatewayConnectionDetails {
|
): GatewayConnectionDetails {
|
||||||
const config = options.config ?? loadConfig();
|
const config = options.config ?? loadConfig();
|
||||||
|
const configPath = resolveConfigPath(
|
||||||
|
process.env,
|
||||||
|
resolveStateDir(process.env),
|
||||||
|
);
|
||||||
const isRemoteMode = config.gateway?.mode === "remote";
|
const isRemoteMode = config.gateway?.mode === "remote";
|
||||||
const remote = isRemoteMode ? config.gateway?.remote : undefined;
|
const remote = isRemoteMode ? config.gateway?.remote : undefined;
|
||||||
const localPort = resolveGatewayPort(config);
|
const localPort = resolveGatewayPort(config);
|
||||||
@@ -70,6 +79,7 @@ export function buildGatewayConnectionDetails(
|
|||||||
const message = [
|
const message = [
|
||||||
`Gateway target: ${url}`,
|
`Gateway target: ${url}`,
|
||||||
`Source: ${urlSource}`,
|
`Source: ${urlSource}`,
|
||||||
|
`Config: ${configPath}`,
|
||||||
bindDetail,
|
bindDetail,
|
||||||
remoteFallbackNote,
|
remoteFallbackNote,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ function parseLsofFieldOutput(output: string): PortListener[] {
|
|||||||
current = Number.isFinite(pid) ? { pid } : {};
|
current = Number.isFinite(pid) ? { pid } : {};
|
||||||
} else if (line.startsWith("c")) {
|
} else if (line.startsWith("c")) {
|
||||||
current.command = line.slice(1);
|
current.command = line.slice(1);
|
||||||
|
} else if (line.startsWith("n")) {
|
||||||
|
// TCP 127.0.0.1:18789 (LISTEN)
|
||||||
|
// TCP *:18789 (LISTEN)
|
||||||
|
if (!current.address) current.address = line.slice(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (current.pid || current.command) listeners.push(current);
|
if (current.pid || current.command) listeners.push(current);
|
||||||
@@ -81,7 +85,7 @@ async function readUnixListeners(
|
|||||||
"-nP",
|
"-nP",
|
||||||
`-iTCP:${port}`,
|
`-iTCP:${port}`,
|
||||||
"-sTCP:LISTEN",
|
"-sTCP:LISTEN",
|
||||||
"-FpFc",
|
"-FpFcn",
|
||||||
]);
|
]);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const listeners = parseLsofFieldOutput(res.stdout);
|
const listeners = parseLsofFieldOutput(res.stdout);
|
||||||
|
|||||||
Reference in New Issue
Block a user