perf(cli): trim gateway status startup imports

This commit is contained in:
Peter Steinberger
2026-04-04 08:59:42 +01:00
parent be15805a84
commit bc75968074
8 changed files with 231 additions and 76 deletions

View File

@@ -6,18 +6,20 @@ import {
} from "../../config/config.js";
import type {
OpenClawConfig,
ConfigFileSnapshot,
GatewayBindMode,
GatewayControlUiConfig,
} from "../../config/types.js";
import { readLastGatewayErrorLine } from "../../daemon/diagnostics.js";
import type { FindExtraGatewayServicesOptions } from "../../daemon/inspect.js";
import { findExtraGatewayServices } from "../../daemon/inspect.js";
import type { ServiceConfigAudit } from "../../daemon/service-audit.js";
import { auditGatewayServiceConfig } from "../../daemon/service-audit.js";
import type { GatewayServiceRuntime } from "../../daemon/service-runtime.js";
import { resolveGatewayService } from "../../daemon/service.js";
import { isGatewaySecretRefUnavailableError, trimToUndefined } from "../../gateway/credentials.js";
import { resolveGatewayProbeAuthWithSecretInputs } from "../../gateway/probe-auth.js";
import {
isGatewaySecretRefUnavailableError,
resolveGatewayProbeCredentialsFromConfig,
trimToUndefined,
} from "../../gateway/credentials.js";
import {
inspectBestEffortPrimaryTailnetIPv4,
resolveBestEffortGatewayBindHostForDisplay,
@@ -29,9 +31,7 @@ import {
type PortListener,
type PortUsageStatus,
} from "../../infra/ports.js";
import { loadGatewayTlsRuntime } from "../../infra/tls/gateway.js";
import { probeGatewayStatus } from "./probe.js";
import { inspectGatewayRestart } from "./restart-health.js";
import { resolveConfiguredLogFilePath } from "../../logging/log-file-path.js";
import { normalizeListenerAddress, parsePortFromArgs, pickProbeHostForBind } from "./shared.js";
import type { GatewayRpcOpts } from "./types.js";
@@ -76,6 +76,52 @@ type ResolvedGatewayStatus = {
probeUrlOverride: string | null;
};
let gatewayProbeAuthModulePromise:
| Promise<typeof import("../../gateway/probe-auth.js")>
| undefined;
let daemonInspectModulePromise: Promise<typeof import("../../daemon/inspect.js")> | undefined;
let serviceAuditModulePromise: Promise<typeof import("../../daemon/service-audit.js")> | undefined;
let gatewayTlsModulePromise: Promise<typeof import("../../infra/tls/gateway.js")> | undefined;
let daemonProbeModulePromise: Promise<typeof import("./probe.js")> | undefined;
let restartHealthModulePromise: Promise<typeof import("./restart-health.js")> | undefined;
function loadGatewayProbeAuthModule() {
gatewayProbeAuthModulePromise ??= import("../../gateway/probe-auth.js");
return gatewayProbeAuthModulePromise;
}
function loadDaemonInspectModule() {
daemonInspectModulePromise ??= import("../../daemon/inspect.js");
return daemonInspectModulePromise;
}
function loadServiceAuditModule() {
serviceAuditModulePromise ??= import("../../daemon/service-audit.js");
return serviceAuditModulePromise;
}
function loadGatewayTlsModule() {
gatewayTlsModulePromise ??= import("../../infra/tls/gateway.js");
return gatewayTlsModulePromise;
}
function loadDaemonProbeModule() {
daemonProbeModulePromise ??= import("./probe.js");
return daemonProbeModulePromise;
}
function loadRestartHealthModule() {
restartHealthModulePromise ??= import("./restart-health.js");
return restartHealthModulePromise;
}
function resolveSnapshotRuntimeConfig(snapshot: ConfigFileSnapshot | null): OpenClawConfig | null {
if (!snapshot?.valid || !snapshot.runtimeConfig) {
return null;
}
return snapshot.runtimeConfig;
}
function appendProbeNote(
existing: string | undefined,
extra: string | undefined,
@@ -87,6 +133,7 @@ function appendProbeNote(
return [...new Set(values)].join(" ");
}
export type DaemonStatus = {
logFile?: string;
service: {
label: string;
loaded: boolean;
@@ -162,17 +209,27 @@ async function loadDaemonConfigContext(
);
const cliIO = createConfigIO({ env: process.env, configPath: cliConfigPath });
const daemonIO = createConfigIO({
env: mergedDaemonEnv,
configPath: daemonConfigPath,
});
const sharesDaemonConfigContext = !serviceEnv && cliConfigPath === daemonConfigPath;
const daemonIO = sharesDaemonConfigContext
? cliIO
: createConfigIO({
env: mergedDaemonEnv,
configPath: daemonConfigPath,
});
const cliSnapshotPromise = cliIO.readConfigFileSnapshot().catch(() => null);
const daemonSnapshotPromise = sharesDaemonConfigContext
? cliSnapshotPromise
: daemonIO.readConfigFileSnapshot().catch(() => null);
const [cliSnapshot, daemonSnapshot] = await Promise.all([
cliIO.readConfigFileSnapshot().catch(() => null),
daemonIO.readConfigFileSnapshot().catch(() => null),
cliSnapshotPromise,
daemonSnapshotPromise,
]);
const cliCfg = cliIO.loadConfig();
const daemonCfg = daemonIO.loadConfig();
const cliCfg = resolveSnapshotRuntimeConfig(cliSnapshot) ?? cliIO.loadConfig();
const daemonCfg =
sharesDaemonConfigContext && cliSnapshot === daemonSnapshot
? cliCfg
: (resolveSnapshotRuntimeConfig(daemonSnapshot) ?? daemonIO.loadConfig());
const cliConfigSummary: ConfigSummary = {
path: cliSnapshot?.path ?? cliConfigPath,
@@ -300,10 +357,14 @@ export async function gatherDaemonStatus(
service.isLoaded({ env: serviceEnv }).catch(() => false),
service.readRuntime(serviceEnv).catch((err) => ({ status: "unknown", detail: String(err) })),
]);
const configAudit = await auditGatewayServiceConfig({
env: process.env,
command,
});
const configAudit = command
? await loadServiceAuditModule().then(({ auditGatewayServiceConfig }) =>
auditGatewayServiceConfig({
env: process.env,
command,
}),
)
: { ok: true, issues: [] satisfies ServiceConfigAudit["issues"] };
const {
mergedDaemonEnv,
cliCfg,
@@ -324,25 +385,33 @@ export async function gatherDaemonStatus(
cliPort,
});
const extraServices = await findExtraGatewayServices(
process.env as Record<string, string | undefined>,
{ deep: Boolean(opts.deep) },
).catch(() => []);
const extraServices = opts.deep
? await loadDaemonInspectModule()
.then(({ findExtraGatewayServices }) =>
findExtraGatewayServices(process.env as Record<string, string | undefined>, {
deep: true,
}),
)
.catch(() => [])
: [];
const timeoutMs = parseStrictPositiveInteger(opts.rpc.timeout ?? "10000") ?? 10_000;
const tlsEnabled = daemonCfg.gateway?.tls?.enabled === true;
const shouldUseLocalTlsRuntime = opts.probe && !probeUrlOverride && tlsEnabled;
const tlsRuntime = shouldUseLocalTlsRuntime
? await loadGatewayTlsRuntime(daemonCfg.gateway?.tls)
? await loadGatewayTlsModule().then(({ loadGatewayTlsRuntime }) =>
loadGatewayTlsRuntime(daemonCfg.gateway?.tls),
)
: undefined;
let daemonProbeAuth: { token?: string; password?: string } | undefined;
let rpcAuthWarning: string | undefined;
if (opts.probe) {
const probeMode = daemonCfg.gateway?.mode === "remote" ? "remote" : "local";
try {
daemonProbeAuth = await resolveGatewayProbeAuthWithSecretInputs({
daemonProbeAuth = resolveGatewayProbeCredentialsFromConfig({
cfg: daemonCfg,
mode: daemonCfg.gateway?.mode === "remote" ? "remote" : "local",
mode: probeMode,
env: mergedDaemonEnv as NodeJS.ProcessEnv,
explicitAuth: {
token: opts.rpc.token,
@@ -354,36 +423,61 @@ export async function gatherDaemonStatus(
if (!refPath) {
throw error;
}
daemonProbeAuth = undefined;
rpcAuthWarning = `${refPath} SecretRef is unavailable in this command path; probing without configured auth credentials.`;
try {
daemonProbeAuth = await loadGatewayProbeAuthModule().then(
({ resolveGatewayProbeAuthWithSecretInputs }) =>
resolveGatewayProbeAuthWithSecretInputs({
cfg: daemonCfg,
mode: probeMode,
env: mergedDaemonEnv as NodeJS.ProcessEnv,
explicitAuth: {
token: opts.rpc.token,
password: opts.rpc.password,
},
}),
);
} catch (fallbackError) {
const fallbackRefPath = parseGatewaySecretRefPathFromError(fallbackError);
if (!fallbackRefPath) {
throw fallbackError;
}
daemonProbeAuth = undefined;
rpcAuthWarning = `${fallbackRefPath} SecretRef is unavailable in this command path; probing without configured auth credentials.`;
}
}
}
const rpc = opts.probe
? await probeGatewayStatus({
url: gateway.probeUrl,
token: daemonProbeAuth?.token,
password: daemonProbeAuth?.password,
tlsFingerprint:
shouldUseLocalTlsRuntime && tlsRuntime?.enabled
? tlsRuntime.fingerprintSha256
: undefined,
timeoutMs,
json: opts.rpc.json,
requireRpc: opts.requireRpc,
configPath: daemonConfigSummary.path,
})
? await loadDaemonProbeModule().then(({ probeGatewayStatus }) =>
probeGatewayStatus({
url: gateway.probeUrl,
token: daemonProbeAuth?.token,
password: daemonProbeAuth?.password,
tlsFingerprint:
shouldUseLocalTlsRuntime && tlsRuntime?.enabled
? tlsRuntime.fingerprintSha256
: undefined,
timeoutMs,
json: opts.rpc.json,
requireRpc: opts.requireRpc,
configPath: daemonConfigSummary.path,
}),
)
: undefined;
if (rpc?.ok) {
rpcAuthWarning = undefined;
}
const health =
opts.probe && loaded
? await inspectGatewayRestart({
service,
port: daemonPort,
env: serviceEnv,
}).catch(() => undefined)
? await loadRestartHealthModule()
.then(({ inspectGatewayRestart }) =>
inspectGatewayRestart({
service,
port: daemonPort,
env: serviceEnv,
}),
)
.catch(() => undefined)
: undefined;
let lastError: string | undefined;
@@ -392,6 +486,7 @@ export async function gatherDaemonStatus(
}
return {
logFile: resolveConfiguredLogFilePath(cliCfg),
service: {
label: service.label,
loaded,