refactor: extract runtime PATH env builder for SSOT
This commit is contained in:
@@ -3,7 +3,7 @@ import os from 'os';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { afterEach, describe, expect, it } from 'vitest';
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import { buildStackRestartSystemdUnit } from './service.js';
|
import { buildRuntimePathEnv, buildStackRestartSystemdUnit } from './service.js';
|
||||||
import { getServiceDefs } from './service-defs.js';
|
import { getServiceDefs } from './service-defs.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +19,8 @@ function generatePlist(
|
|||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
homeDir: string,
|
homeDir: string,
|
||||||
): string {
|
): string {
|
||||||
|
const runtimePath = buildRuntimePathEnv(nodePath, homeDir);
|
||||||
|
|
||||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
@@ -39,7 +41,7 @@ function generatePlist(
|
|||||||
<key>EnvironmentVariables</key>
|
<key>EnvironmentVariables</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>PATH</key>
|
<key>PATH</key>
|
||||||
<string>/usr/local/bin:/usr/bin:/bin:${homeDir}/.local/bin</string>
|
<string>${runtimePath}</string>
|
||||||
<key>HOME</key>
|
<key>HOME</key>
|
||||||
<string>${homeDir}</string>
|
<string>${homeDir}</string>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -57,6 +59,8 @@ function generateSystemdUnit(
|
|||||||
homeDir: string,
|
homeDir: string,
|
||||||
isSystem: boolean,
|
isSystem: boolean,
|
||||||
): string {
|
): string {
|
||||||
|
const runtimePath = buildRuntimePathEnv(nodePath, homeDir);
|
||||||
|
|
||||||
return `[Unit]
|
return `[Unit]
|
||||||
Description=EJClaw Personal Assistant
|
Description=EJClaw Personal Assistant
|
||||||
After=network.target
|
After=network.target
|
||||||
@@ -68,7 +72,7 @@ WorkingDirectory=${projectRoot}
|
|||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
Environment=HOME=${homeDir}
|
Environment=HOME=${homeDir}
|
||||||
Environment=PATH=/usr/local/bin:/usr/bin:/bin:${homeDir}/.local/bin
|
Environment=PATH=${runtimePath}
|
||||||
StandardOutput=append:${projectRoot}/logs/ejclaw.log
|
StandardOutput=append:${projectRoot}/logs/ejclaw.log
|
||||||
StandardError=append:${projectRoot}/logs/ejclaw.error.log
|
StandardError=append:${projectRoot}/logs/ejclaw.error.log
|
||||||
|
|
||||||
@@ -116,6 +120,12 @@ describe('plist generation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('systemd unit generation', () => {
|
describe('systemd unit generation', () => {
|
||||||
|
it('shares the runtime PATH builder across service formats', () => {
|
||||||
|
expect(buildRuntimePathEnv('/usr/bin/bun', '/home/user')).toBe(
|
||||||
|
'/usr/bin:/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin:/home/user/.npm-global/bin',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('user unit uses default.target', () => {
|
it('user unit uses default.target', () => {
|
||||||
const unit = generateSystemdUnit(
|
const unit = generateSystemdUnit(
|
||||||
'/usr/bin/node',
|
'/usr/bin/node',
|
||||||
|
|||||||
@@ -89,6 +89,47 @@ export async function run(_args: string[]): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildRuntimePathEnv(nodePath: string, homeDir: string): string {
|
||||||
|
return `${path.dirname(nodePath)}:/usr/local/bin:/usr/bin:/bin:${homeDir}/.local/bin:${homeDir}/.npm-global/bin`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLaunchdEnvironmentEntries(
|
||||||
|
nodePath: string,
|
||||||
|
homeDir: string,
|
||||||
|
extraEnv?: Record<string, string>,
|
||||||
|
): string[] {
|
||||||
|
const envEntries = [
|
||||||
|
` <key>PATH</key>`,
|
||||||
|
` <string>${buildRuntimePathEnv(nodePath, homeDir)}</string>`,
|
||||||
|
` <key>HOME</key>`,
|
||||||
|
` <string>${homeDir}</string>`,
|
||||||
|
];
|
||||||
|
if (extraEnv) {
|
||||||
|
for (const [k, v] of Object.entries(extraEnv)) {
|
||||||
|
envEntries.push(` <key>${k}</key>`);
|
||||||
|
envEntries.push(` <string>${v}</string>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSystemdEnvironmentLines(
|
||||||
|
nodePath: string,
|
||||||
|
homeDir: string,
|
||||||
|
extraEnv?: Record<string, string>,
|
||||||
|
): string[] {
|
||||||
|
const envLines = [
|
||||||
|
`Environment=HOME=${homeDir}`,
|
||||||
|
`Environment=PATH=${buildRuntimePathEnv(nodePath, homeDir)}`,
|
||||||
|
];
|
||||||
|
if (extraEnv) {
|
||||||
|
for (const [k, v] of Object.entries(extraEnv)) {
|
||||||
|
envLines.push(`Environment=${k}=${v}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envLines;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* macOS (launchd) */
|
/* macOS (launchd) */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -107,19 +148,11 @@ function setupLaunchd(
|
|||||||
);
|
);
|
||||||
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
||||||
|
|
||||||
// Build extra env dict entries
|
const envEntries = buildLaunchdEnvironmentEntries(
|
||||||
const envEntries = [
|
nodePath,
|
||||||
` <key>PATH</key>`,
|
homeDir,
|
||||||
` <string>${path.dirname(nodePath)}:/usr/local/bin:/usr/bin:/bin:${homeDir}/.local/bin:${homeDir}/.npm-global/bin</string>`,
|
def.extraEnv,
|
||||||
` <key>HOME</key>`,
|
);
|
||||||
` <string>${homeDir}</string>`,
|
|
||||||
];
|
|
||||||
if (def.extraEnv) {
|
|
||||||
for (const [k, v] of Object.entries(def.extraEnv)) {
|
|
||||||
envEntries.push(` <key>${k}</key>`);
|
|
||||||
envEntries.push(` <string>${v}</string>`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
@@ -331,16 +364,11 @@ function setupSystemdUnit(
|
|||||||
const unitPath = getUnitPath(def.name, homeDir, runningAsRoot);
|
const unitPath = getUnitPath(def.name, homeDir, runningAsRoot);
|
||||||
fs.mkdirSync(path.dirname(unitPath), { recursive: true });
|
fs.mkdirSync(path.dirname(unitPath), { recursive: true });
|
||||||
|
|
||||||
// Build Environment= lines
|
const envLines = buildSystemdEnvironmentLines(
|
||||||
const envLines = [
|
nodePath,
|
||||||
`Environment=HOME=${homeDir}`,
|
homeDir,
|
||||||
`Environment=PATH=${path.dirname(nodePath)}:/usr/local/bin:/usr/bin:/bin:${homeDir}/.local/bin:${homeDir}/.npm-global/bin`,
|
def.extraEnv,
|
||||||
];
|
);
|
||||||
if (def.extraEnv) {
|
|
||||||
for (const [k, v] of Object.entries(def.extraEnv)) {
|
|
||||||
envLines.push(`Environment=${k}=${v}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnvironmentFile line (optional — for codex service loading .env.codex)
|
// EnvironmentFile line (optional — for codex service loading .env.codex)
|
||||||
const envFileLine = def.environmentFile
|
const envFileLine = def.environmentFile
|
||||||
@@ -373,10 +401,7 @@ export function buildStackRestartSystemdUnit(
|
|||||||
nodePath: string,
|
nodePath: string,
|
||||||
homeDir: string,
|
homeDir: string,
|
||||||
): string {
|
): string {
|
||||||
const envLines = [
|
const envLines = buildSystemdEnvironmentLines(nodePath, homeDir);
|
||||||
`Environment=HOME=${homeDir}`,
|
|
||||||
`Environment=PATH=${path.dirname(nodePath)}:/usr/local/bin:/usr/bin:/bin:${homeDir}/.local/bin:${homeDir}/.npm-global/bin`,
|
|
||||||
];
|
|
||||||
|
|
||||||
return `[Unit]
|
return `[Unit]
|
||||||
Description=EJClaw Stack Restart Orchestrator
|
Description=EJClaw Stack Restart Orchestrator
|
||||||
|
|||||||
Reference in New Issue
Block a user