RELAY-PHASE1 Wave A2/B/C/D/E (12-agent workflow, all tsc-clean, 314/314 tests pass): - A2: control-plane server.ts entry + boot/redis.ts revocation-bus wiring + start script. - B1: relay-run shared-store EnforceDeps (relay-auth ports over the SAME Postgres+Redis as P3). - B2: registry-backed MtlsVerifier (verifyAgentCert, fail-closed, INV14). - B3: store-backed RouteResolver (subdomain->hostId). - B4: Redis relay:revocations subscriber -> tunnel teardown (INV12). - B5: main-phase1.ts production entry (public bind, real TLS, async-mTLS prefetch bridge) + staging /auth/mint. - B6 (PARTIAL): relay-web operator login + browser DPoP; proof offered via term.dpop.<b64u> subprotocol. - C: agent dist/cli.js build (esbuild) + runTunnel run-loop + CliDeps. - D1: same-origin static serve of relay-web/public from the browser WSS. - E: systemd units + gen-ca/gen-capability-key/issue-tls-cert scripts + deploy/RUNBOOK.md. Adversarial review: all hard invariants PASS. Follow-ups (B7): close DPoP-subprotocol read on browser-server (blocks browser connect); rate-limit /auth/mint (F1); wire activeSessionCount (F2); scrub error logs (F5). Excludes unrelated public/style.css (concurrent iOS job).
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
/**
|
|
* CliDeps factory — PLAN_RELAY_PHASE1 C2. Wires the abstract `CliDeps` seams (consumed by
|
|
* `runCli`) to their real implementations: env-driven config, the on-disk keystore, Ed25519
|
|
* identity generation, §4.5 pairing redemption, the supervised tunnel, and OS service install.
|
|
* All side effects live here so `cli.ts`/`runCli` stay pure and unit-testable.
|
|
*/
|
|
import { execFile } from 'node:child_process'
|
|
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
import { homedir, userInfo } from 'node:os'
|
|
import { dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import type { CliDeps } from '../cli.js'
|
|
import type { AgentConfig } from '../config/agentConfig.js'
|
|
import { loadAgentConfig } from '../config/agentConfig.js'
|
|
import { openKeystore } from '../keys/keystore.js'
|
|
import { generateIdentity } from '../keys/identity.js'
|
|
import { redeemPairingCode } from '../enroll/pair.js'
|
|
import { runTunnel } from '../transport/runTunnel.js'
|
|
import {
|
|
detectPlatform,
|
|
installService as installServiceUnit,
|
|
uninstallService as uninstallServiceUnit,
|
|
type InstallDeps,
|
|
type ServicePlatform,
|
|
} from '../service/install.js'
|
|
|
|
/** Resolve this process's own executable path (the bundled `dist/cli.js`) for the service unit. */
|
|
function selfBinPath(): string {
|
|
return fileURLToPath(import.meta.url)
|
|
}
|
|
|
|
function runCommand(cmd: string, args: readonly string[]): Promise<void> {
|
|
return new Promise<void>((resolve, reject) => {
|
|
execFile(cmd, [...args], (err) => (err ? reject(err) : resolve()))
|
|
})
|
|
}
|
|
|
|
function realInstallDeps(): InstallDeps {
|
|
return {
|
|
writeFile: (path, content) => {
|
|
mkdirSync(dirname(path), { recursive: true })
|
|
writeFileSync(path, content)
|
|
},
|
|
runCommand,
|
|
getuid: () => (typeof process.getuid === 'function' ? process.getuid() : 0),
|
|
homedir,
|
|
username: () => userInfo().username,
|
|
binPath: selfBinPath,
|
|
}
|
|
}
|
|
|
|
/** Map the current OS to its service manager, or fail fast with a clear message. */
|
|
function requirePlatform(): ServicePlatform {
|
|
const platform = detectPlatform(process.platform)
|
|
if (platform === null) {
|
|
throw new Error(`service install/uninstall is unsupported on platform '${process.platform}'`)
|
|
}
|
|
return platform
|
|
}
|
|
|
|
/** Build the concrete CliDeps used by the real CLI entrypoint. */
|
|
export function createCliDeps(): CliDeps {
|
|
return {
|
|
loadConfig: () => loadAgentConfig(process.env),
|
|
openKeystore: (stateDir) => openKeystore(stateDir),
|
|
generateIdentity: () => generateIdentity(),
|
|
redeem: (cfg: AgentConfig, code, id, ks) => redeemPairingCode(cfg.enrollUrl, code, id, ks),
|
|
runTunnel: async (cfg, ks) => {
|
|
const handle = await runTunnel(cfg, ks)
|
|
const onSignal = (): void => {
|
|
void handle.stop()
|
|
}
|
|
process.once('SIGTERM', onSignal)
|
|
process.once('SIGINT', onSignal)
|
|
return handle.done
|
|
},
|
|
installService: (cfg) => installServiceUnit(cfg, requirePlatform(), realInstallDeps()),
|
|
uninstallService: () => uninstallServiceUnit(requirePlatform(), { runCommand, homedir }),
|
|
print: (line) => {
|
|
process.stdout.write(`${line}\n`)
|
|
},
|
|
}
|
|
}
|