Files
web-terminal/agent/src/service/originConfig.ts
Yaojia Wang d0c249c739 feat(agent): inject S0 env into launchd/systemd via install CLI (S2)
- launchd writer emits <EnvironmentVariables>; systemd writer emits EnvironmentFile/Environment;
  originConfig zone parameterized (default 'term' preserved for relay callers).
- InstallOptions threaded CLI install -> createCliDeps -> installService seam -> writers, so generated
  units carry BIND_HOST (defaults 127.0.0.1 for tunnel hosts), ALLOWED_ORIGINS, PORT, SHELL_PATH,
  IDLE_TTL, USE_TMUX. systemd rejects control chars in env values.
Verified: agent typecheck+build clean; vitest 166/166 (154 baseline + 12 new).
2026-07-07 09:42:12 +02:00

82 lines
3.0 KiB
TypeScript

/**
* The ONE base-app touch-point — PLAN_RELAY_AGENT T17 (INDEX §0, EXPLORE §3 "Zero code change").
* APPENDS `https://<subdomain>.<zone>.<domain>` to the base app's ALLOWED_ORIGINS env (idempotent),
* as CONFIG — NO `src/` code edit. AUGMENTS, never weakens, the Origin/CSWSH check: existing
* origins are always preserved (EXPLORE §3 "do not weaken the check").
*
* PLAN_NATIVE_TUNNEL S2: the DNS zone label is PARAMETERIZED. Relay callers keep the historical
* `term` zone (default), while native-tunnel hosts pass `terminal` so the base app trusts
* `https://<name>.terminal.<domain>`. The default is preserved so existing callers/tests are
* unaffected — the zone is opt-in per call, never hard-flipped.
*/
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
export interface OriginFsDeps {
exists(path: string): boolean
read(path: string): string
write(path: string, content: string): void
}
const defaultFs: OriginFsDeps = {
exists: existsSync,
read: (p) => readFileSync(p, 'utf8'),
write: (p, c) => writeFileSync(p, c),
}
/** Default DNS zone label (relay hosts). Native-tunnel hosts pass `terminal`. */
export const DEFAULT_ORIGIN_ZONE = 'term'
const KEY = 'ALLOWED_ORIGINS'
/** Compose the subdomain origin the base app must trust: `https://<subdomain>.<zone>.<domain>`. */
export function subdomainOrigin(
subdomain: string,
domain: string,
zone: string = DEFAULT_ORIGIN_ZONE,
): string {
return `https://${subdomain}.${zone}.${domain}`
}
/**
* Merge `origin` into a comma-separated ALLOWED_ORIGINS value, preserving every existing origin and
* de-duplicating. Returns the merged CSV; never removes an origin. Pure/immutable.
*/
export function mergeOrigins(current: string | undefined, origin: string): string {
const origins = (current ?? '')
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0)
if (origins.includes(origin)) return origins.join(',')
return [...origins, origin].join(',')
}
function upsertOriginLine(content: string, origin: string): string {
const lines = content.length === 0 ? [] : content.split('\n')
let found = false
const next = lines.map((line) => {
if (!line.startsWith(`${KEY}=`)) return line
found = true
return `${KEY}=${mergeOrigins(line.slice(KEY.length + 1), origin)}`
})
if (!found) next.push(`${KEY}=${origin}`)
return next.join('\n')
}
/**
* Idempotently append the subdomain origin to ALLOWED_ORIGINS in `baseAppEnvPath`. Never removes an
* existing origin. Creates the file/line if absent. `zone` selects the DNS zone label (default
* preserves relay callers).
*/
export function ensureAllowedOrigin(
baseAppEnvPath: string,
subdomain: string,
domain: string,
fs: OriginFsDeps = defaultFs,
zone: string = DEFAULT_ORIGIN_ZONE,
): void {
const origin = subdomainOrigin(subdomain, domain, zone)
const existing = fs.exists(baseAppEnvPath) ? fs.read(baseAppEnvPath) : ''
const updated = upsertOriginLine(existing, origin)
fs.write(baseAppEnvPath, updated.endsWith('\n') ? updated : `${updated}\n`)
}