fix: harden onboarding for non-systemd environments
This commit is contained in:
@@ -19,6 +19,7 @@ import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
||||
import { resolvePreferredNodePath } from "../daemon/runtime-paths.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import { buildServiceEnvironment } from "../daemon/service-env.js";
|
||||
import { isSystemdUserServiceAvailable } from "../daemon/systemd.js";
|
||||
import { upsertSharedEnvVar } from "../infra/env-file.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
@@ -429,41 +430,53 @@ export async function runNonInteractiveOnboarding(
|
||||
const daemonRuntimeRaw = opts.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
|
||||
|
||||
if (opts.installDaemon) {
|
||||
if (!isGatewayDaemonRuntime(daemonRuntimeRaw)) {
|
||||
runtime.error("Invalid --daemon-runtime (use node or bun)");
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const service = resolveGatewayService();
|
||||
const devMode =
|
||||
process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
|
||||
process.argv[1]?.endsWith(".ts");
|
||||
const nodePath = await resolvePreferredNodePath({
|
||||
env: process.env,
|
||||
runtime: daemonRuntimeRaw,
|
||||
});
|
||||
const { programArguments, workingDirectory } =
|
||||
await resolveGatewayProgramArguments({
|
||||
port,
|
||||
dev: devMode,
|
||||
const systemdAvailable =
|
||||
process.platform === "linux"
|
||||
? await isSystemdUserServiceAvailable()
|
||||
: true;
|
||||
if (process.platform === "linux" && !systemdAvailable) {
|
||||
runtime.log(
|
||||
"Systemd user services are unavailable; skipping daemon install.",
|
||||
);
|
||||
} else {
|
||||
if (!isGatewayDaemonRuntime(daemonRuntimeRaw)) {
|
||||
runtime.error("Invalid --daemon-runtime (use node or bun)");
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const service = resolveGatewayService();
|
||||
const devMode =
|
||||
process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
|
||||
process.argv[1]?.endsWith(".ts");
|
||||
const nodePath = await resolvePreferredNodePath({
|
||||
env: process.env,
|
||||
runtime: daemonRuntimeRaw,
|
||||
nodePath,
|
||||
});
|
||||
const environment = buildServiceEnvironment({
|
||||
env: process.env,
|
||||
port,
|
||||
token: gatewayToken,
|
||||
launchdLabel:
|
||||
process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined,
|
||||
});
|
||||
await service.install({
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
programArguments,
|
||||
workingDirectory,
|
||||
environment,
|
||||
});
|
||||
await ensureSystemdUserLingerNonInteractive({ runtime });
|
||||
const { programArguments, workingDirectory } =
|
||||
await resolveGatewayProgramArguments({
|
||||
port,
|
||||
dev: devMode,
|
||||
runtime: daemonRuntimeRaw,
|
||||
nodePath,
|
||||
});
|
||||
const environment = buildServiceEnvironment({
|
||||
env: process.env,
|
||||
port,
|
||||
token: gatewayToken,
|
||||
launchdLabel:
|
||||
process.platform === "darwin"
|
||||
? GATEWAY_LAUNCH_AGENT_LABEL
|
||||
: undefined,
|
||||
});
|
||||
await service.install({
|
||||
env: process.env,
|
||||
stdout: process.stdout,
|
||||
programArguments,
|
||||
workingDirectory,
|
||||
environment,
|
||||
});
|
||||
await ensureSystemdUserLingerNonInteractive({ runtime });
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.skipHealth) {
|
||||
|
||||
@@ -27,6 +27,7 @@ export type ProviderChoice = ChatProviderId;
|
||||
|
||||
export type OnboardOptions = {
|
||||
mode?: OnboardMode;
|
||||
flow?: "quickstart" | "advanced";
|
||||
workspace?: string;
|
||||
nonInteractive?: boolean;
|
||||
authChoice?: AuthChoice;
|
||||
@@ -51,8 +52,10 @@ export type OnboardOptions = {
|
||||
tailscaleResetOnExit?: boolean;
|
||||
installDaemon?: boolean;
|
||||
daemonRuntime?: GatewayDaemonRuntime;
|
||||
skipProviders?: boolean;
|
||||
skipSkills?: boolean;
|
||||
skipHealth?: boolean;
|
||||
skipUi?: boolean;
|
||||
nodeManager?: NodeManagerChoice;
|
||||
remoteUrl?: string;
|
||||
remoteToken?: string;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { note as clackNote } from "@clack/prompts";
|
||||
|
||||
import {
|
||||
enableSystemdUserLinger,
|
||||
isSystemdUserServiceAvailable,
|
||||
readSystemdUserLingerStatus,
|
||||
} from "../daemon/systemd.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
@@ -32,6 +33,13 @@ export async function ensureSystemdUserLingerInteractive(params: {
|
||||
const env = params.env ?? process.env;
|
||||
const prompter = params.prompter ?? { note };
|
||||
const title = params.title ?? "Systemd";
|
||||
if (!(await isSystemdUserServiceAvailable())) {
|
||||
await prompter.note(
|
||||
"Systemd user services are unavailable. Skipping lingering checks.",
|
||||
title,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const status = await readSystemdUserLingerStatus(env);
|
||||
if (!status) {
|
||||
await prompter.note(
|
||||
@@ -98,6 +106,7 @@ export async function ensureSystemdUserLingerNonInteractive(params: {
|
||||
}): Promise<void> {
|
||||
if (process.platform !== "linux") return;
|
||||
const env = params.env ?? process.env;
|
||||
if (!(await isSystemdUserServiceAvailable())) return;
|
||||
const status = await readSystemdUserLingerStatus(env);
|
||||
if (!status || status.linger === "yes") return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user