Skip to content

Commit eb898c7

Browse files
committed
fix(cli): set executor web base url for daemon
1 parent 16f8d1a commit eb898c7

1 file changed

Lines changed: 109 additions & 82 deletions

File tree

apps/cli/src/main.ts

Lines changed: 109 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,17 @@ const parseDaemonUrl = (baseUrl: string) =>
202202
const daemonBaseUrl = (hostname: string, port: number): string =>
203203
`http://${canonicalDaemonHost(hostname)}:${port}`;
204204

205+
const installDefaultExecutorWebBaseUrl = (baseUrl: string): (() => void) => {
206+
if (process.env.EXECUTOR_WEB_BASE_URL !== undefined) {
207+
return () => {};
208+
}
209+
210+
process.env.EXECUTOR_WEB_BASE_URL = baseUrl;
211+
return () => {
212+
delete process.env.EXECUTOR_WEB_BASE_URL;
213+
};
214+
};
215+
205216
const cleanupPointer = (input: { hostname: string; scopeId: string; port: number }) =>
206217
Effect.gen(function* () {
207218
yield* removeDaemonPointer({ hostname: input.hostname, scopeId: input.scopeId }).pipe(
@@ -543,41 +554,49 @@ const runForegroundSession = (input: {
543554
authPassword: string | undefined;
544555
}) =>
545556
Effect.gen(function* () {
546-
const server = yield* Effect.promise(() =>
547-
startServer({
548-
port: input.port,
549-
hostname: input.hostname,
550-
allowedHosts: input.allowedHosts,
551-
authToken: input.authToken,
552-
authPassword: input.authPassword,
553-
embeddedWebUI,
554-
}),
555-
);
556-
557557
const displayHost =
558558
input.hostname === "0.0.0.0" || input.hostname === "::" ? "localhost" : input.hostname;
559-
const baseUrl = `http://${displayHost}:${server.port}`;
560-
console.log(`Executor is ready.`);
561-
console.log(`Web: ${baseUrl}`);
562-
console.log(`MCP: ${baseUrl}/mcp`);
563-
console.log(`OpenAPI: ${baseUrl}/api/docs`);
564-
if (input.hostname !== "127.0.0.1" && input.hostname !== "localhost") {
565-
console.log(
566-
`\n⚠ Listening on ${input.hostname}. Executor runs arbitrary commands — only expose on trusted networks.`,
559+
const restoreWebBaseUrl = installDefaultExecutorWebBaseUrl(
560+
`http://${displayHost}:${input.port}`,
561+
);
562+
563+
try {
564+
const server = yield* Effect.promise(() =>
565+
startServer({
566+
port: input.port,
567+
hostname: input.hostname,
568+
allowedHosts: input.allowedHosts,
569+
authToken: input.authToken,
570+
authPassword: input.authPassword,
571+
embeddedWebUI,
572+
}),
567573
);
568-
if (input.allowedHosts.length > 0) {
569-
console.log(` Extra allowed Host headers: ${input.allowedHosts.join(", ")}`);
570-
}
571-
if (input.authPassword) {
572-
console.log(" Basic authentication is enabled.");
573-
} else if (input.authToken) {
574-
console.log(" Token authentication is enabled.");
574+
575+
const baseUrl = `http://${displayHost}:${server.port}`;
576+
console.log(`Executor is ready.`);
577+
console.log(`Web: ${baseUrl}`);
578+
console.log(`MCP: ${baseUrl}/mcp`);
579+
console.log(`OpenAPI: ${baseUrl}/api/docs`);
580+
if (input.hostname !== "127.0.0.1" && input.hostname !== "localhost") {
581+
console.log(
582+
`\n⚠ Listening on ${input.hostname}. Executor runs arbitrary commands — only expose on trusted networks.`,
583+
);
584+
if (input.allowedHosts.length > 0) {
585+
console.log(` Extra allowed Host headers: ${input.allowedHosts.join(", ")}`);
586+
}
587+
if (input.authPassword) {
588+
console.log(" Basic authentication is enabled.");
589+
} else if (input.authToken) {
590+
console.log(" Token authentication is enabled.");
591+
}
575592
}
576-
}
577-
console.log(`\nPress Ctrl+C to stop.`);
593+
console.log(`\nPress Ctrl+C to stop.`);
578594

579-
yield* waitForShutdownSignal();
580-
yield* Effect.promise(() => server.stop());
595+
yield* waitForShutdownSignal();
596+
yield* Effect.promise(() => server.stop());
597+
} finally {
598+
restoreWebBaseUrl();
599+
}
581600
});
582601

583602
const runDaemonSession = (input: {
@@ -589,67 +608,75 @@ const runDaemonSession = (input: {
589608
}) =>
590609
Effect.gen(function* () {
591610
const daemonHost = canonicalDaemonHost(input.hostname);
611+
const restoreWebBaseUrl = installDefaultExecutorWebBaseUrl(
612+
daemonBaseUrl(daemonHost, input.port),
613+
);
592614
const scopeId = currentDaemonScopeId();
593-
const existing = yield* readDaemonPointer({ hostname: daemonHost, scopeId });
594615

595-
if (existing) {
596-
const existingUrl = daemonBaseUrl(existing.hostname, existing.port);
597-
if (isPidAlive(existing.pid) && (yield* isServerReachable(existingUrl))) {
598-
return yield* Effect.fail(
599-
new Error(
600-
[
601-
`A daemon is already running for scope ${scopeId} on ${daemonHost}.`,
602-
`Existing daemon: ${existingUrl} (pid ${existing.pid}).`,
603-
`Stop it first: ${cliPrefix} daemon stop`,
604-
].join("\n"),
605-
),
606-
);
616+
try {
617+
const existing = yield* readDaemonPointer({ hostname: daemonHost, scopeId });
618+
619+
if (existing) {
620+
const existingUrl = daemonBaseUrl(existing.hostname, existing.port);
621+
if (isPidAlive(existing.pid) && (yield* isServerReachable(existingUrl))) {
622+
return yield* Effect.fail(
623+
new Error(
624+
[
625+
`A daemon is already running for scope ${scopeId} on ${daemonHost}.`,
626+
`Existing daemon: ${existingUrl} (pid ${existing.pid}).`,
627+
`Stop it first: ${cliPrefix} daemon stop`,
628+
].join("\n"),
629+
),
630+
);
631+
}
632+
yield* cleanupPointer({ hostname: existing.hostname, scopeId, port: existing.port });
607633
}
608-
yield* cleanupPointer({ hostname: existing.hostname, scopeId, port: existing.port });
609-
}
610634

611-
const server = yield* Effect.promise(() =>
612-
startServer({
613-
port: input.port,
614-
hostname: input.hostname,
615-
allowedHosts: input.allowedHosts,
616-
authToken: input.authToken,
617-
authPassword: input.authPassword,
618-
embeddedWebUI,
619-
}),
620-
);
635+
const server = yield* Effect.promise(() =>
636+
startServer({
637+
port: input.port,
638+
hostname: input.hostname,
639+
allowedHosts: input.allowedHosts,
640+
authToken: input.authToken,
641+
authPassword: input.authPassword,
642+
embeddedWebUI,
643+
}),
644+
);
621645

622-
const daemonPort = server.port;
623-
const token = randomUUID();
646+
const daemonPort = server.port;
647+
const token = randomUUID();
624648

625-
yield* writeDaemonRecord({
626-
hostname: daemonHost,
627-
port: daemonPort,
628-
pid: process.pid,
629-
scopeDir: process.env.EXECUTOR_SCOPE_DIR ?? null,
630-
});
631-
yield* writeDaemonPointer({
632-
hostname: daemonHost,
633-
port: daemonPort,
634-
pid: process.pid,
635-
scopeId,
636-
scopeDir: process.env.EXECUTOR_SCOPE_DIR ?? null,
637-
token,
638-
});
649+
try {
650+
yield* writeDaemonRecord({
651+
hostname: daemonHost,
652+
port: daemonPort,
653+
pid: process.pid,
654+
scopeDir: process.env.EXECUTOR_SCOPE_DIR ?? null,
655+
});
656+
yield* writeDaemonPointer({
657+
hostname: daemonHost,
658+
port: daemonPort,
659+
pid: process.pid,
660+
scopeId,
661+
scopeDir: process.env.EXECUTOR_SCOPE_DIR ?? null,
662+
token,
663+
});
639664

640-
console.log(`Daemon ready on http://${daemonHost}:${daemonPort}`);
641-
if (input.authPassword) {
642-
console.log("Basic authentication is enabled.");
643-
} else if (input.authToken) {
644-
console.log("Token authentication is enabled.");
645-
}
665+
console.log(`Daemon ready on http://${daemonHost}:${daemonPort}`);
666+
if (input.authPassword) {
667+
console.log("Basic authentication is enabled.");
668+
} else if (input.authToken) {
669+
console.log("Token authentication is enabled.");
670+
}
646671

647-
try {
648-
yield* waitForShutdownSignal();
672+
yield* waitForShutdownSignal();
673+
} finally {
674+
yield* Effect.promise(() => server.stop());
675+
yield* removeDaemonRecord({ hostname: daemonHost, port: daemonPort });
676+
yield* removeDaemonPointer({ hostname: daemonHost, scopeId }).pipe(Effect.ignore);
677+
}
649678
} finally {
650-
yield* Effect.promise(() => server.stop());
651-
yield* removeDaemonRecord({ hostname: daemonHost, port: daemonPort });
652-
yield* removeDaemonPointer({ hostname: daemonHost, scopeId }).pipe(Effect.ignore);
679+
restoreWebBaseUrl();
653680
}
654681
});
655682

0 commit comments

Comments
 (0)