- 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).
82 lines
3.0 KiB
TypeScript
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`)
|
|
}
|