fix(agent): give launchd units a log sink (StandardOut/ErrorPath)

launchd has no default log destination (unlike systemd's journald), so a unit
that fails to start (bare node, missing env) was silent — which hid the EX_CONFIG
+ loadConfig failures during this deploy. Route both units' stdout+stderr to
<stateDir>/{base-app,agent}.log. 281 tests pass.
This commit is contained in:
Yaojia Wang
2026-07-19 07:57:22 +02:00
parent 1e398c7561
commit c98f5e6a1f
2 changed files with 20 additions and 3 deletions

View File

@@ -11,7 +11,7 @@
* - FIX M-host-2service: base-app env (BIND_HOST/ALLOWED_ORIGINS/PORT/…) is routed to the
* base-app unit ONLY; the agent unit (which supervises frpc) never carries it.
*/
import { dirname } from 'node:path'
import { dirname, join } from 'node:path'
import type { AgentConfig } from '../config/agentConfig.js'
import {
agentLabel,
@@ -225,9 +225,15 @@ export async function installService(
if (platform === 'launchd') {
const baseAppPath = launchdPlistPath(deps.homedir(), baseAppLabel())
deps.writeFile(baseAppPath, buildLaunchdPlist(baseAppExec, baseAppEnvWithPath, baseAppLabel()))
deps.writeFile(
baseAppPath,
buildLaunchdPlist(baseAppExec, baseAppEnvWithPath, baseAppLabel(), join(cfg.stateDir, 'base-app.log')),
)
const agentPath = launchdPlistPath(deps.homedir(), agentLabel())
deps.writeFile(agentPath, buildLaunchdPlist([nodePath, bin, 'run'], agentEnv, agentLabel()))
deps.writeFile(
agentPath,
buildLaunchdPlist([nodePath, bin, 'run'], agentEnv, agentLabel(), join(cfg.stateDir, 'agent.log')),
)
for (const path of [baseAppPath, agentPath]) {
const { cmd, args } = launchdLoadCommand(path)
await deps.runCommand(cmd, args)

View File

@@ -78,6 +78,7 @@ export function buildLaunchdPlist(
programArguments: readonly string[],
env: ServiceEnv = {},
label: string = AGENT_LABEL,
logPath?: string,
): string {
return [
'<?xml version="1.0" encoding="UTF-8"?>',
@@ -91,6 +92,16 @@ export function buildLaunchdPlist(
' <true/>',
' <key>KeepAlive</key>',
' <true/>',
// launchd's default has no log sink (unlike systemd's journald), so a crashing unit is silent.
// Route stdout+stderr to a file so `pair --install` failures are diagnosable out of the box.
...(logPath !== undefined
? [
' <key>StandardOutPath</key>',
` <string>${escapeXml(logPath)}</string>`,
' <key>StandardErrorPath</key>',
` <string>${escapeXml(logPath)}</string>`,
]
: []),
...environmentVariablesBlock(env),
'</dict>',
'</plist>',