Files
pikasTech-unidesk/scripts/src/platform-infra-host-proxy.ts
T
2026-06-29 14:34:15 +00:00

1174 lines
50 KiB
TypeScript

import { createHash } from "node:crypto";
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { runCommand, type CommandResult } from "./command";
import { resolveEgressProxySourceRef, type MasterShadowsocksSourceSpec } from "./egress-proxy-sources";
import type { RenderedCliResult } from "./output";
import { shQuote } from "./platform-infra-public-service";
import { fingerprintSecretValues, readEnvSourceFile, requiredEnvValue } from "./secrets";
import { transHostProxyEnvSummary } from "./trans-host-proxy";
const configPath = "config/platform-infra/host-proxy.yaml";
interface HostProxyConfig {
defaults: { targetId: string };
server: HostProxyServer;
sources: Record<string, HostProxySource>;
targets: Record<string, HostProxyTarget>;
}
interface HostProxyServer {
id: string;
enabled: boolean;
benchmarkRef: string;
implementationRef: string;
sourceConfigRef: string;
sourceRef: string;
sourceKey: string;
sourceFingerprint: string;
masterShadowsocks: MasterShadowsocksSourceSpec;
composeFile: string;
serviceName: string;
containerName: string;
image: string;
configPath: string;
listenHost: string;
listenPort: number;
health: { mode: "tcp"; host: string; port: number };
fingerprint: string;
}
interface HostProxySource {
id: string;
sourceType: "benchmark-validated-master-shadowsocks";
serverRef: string;
benchmarkRef: string;
implementationRef: string;
sourceConfigRef: string;
sourceRef: string;
sourceKey: string;
sourceFingerprint: string;
masterShadowsocks: MasterShadowsocksSourceSpec;
client: HostProxyClient;
proxyUrl: string;
externalProbeUrl: string;
fingerprint: string;
}
interface HostProxyClient {
mode: "trans-static-binary";
upstreamUrl: string;
version: string;
archiveCachePath: string;
archiveSha256: string;
archiveInstallPath: string;
binaryMember: string;
binaryCachePath: string;
binarySha256: string;
installPath: string;
configPath: string;
unitPath: string;
serviceName: string;
listenHost: string;
listenPort: number;
podAccess: HostProxyPodAccess | null;
clashApiListen: string;
healthUrl: string;
}
interface HostProxyPodAccess {
enabled: boolean;
listenHost: string;
listenPort: number;
proxyUrl: string;
}
interface HostProxyTarget {
id: string;
route: string;
enabled: boolean;
sourceRef: string;
source: HostProxySource;
env: {
httpProxy: string;
httpsProxy: string;
allProxy: string;
noProxy: string[];
};
files: {
envFile: string;
profile: string;
apt: string;
dockerSystemdDropIn: string;
k3sSystemdDropIn: string;
};
apply: {
reloadSystemd: boolean;
restartDocker: boolean;
restartK3s: boolean;
};
}
interface HostProxySecretMaterial {
serverConfigJson: string;
clientConfigJson: string;
fingerprint: string;
sourcePath: string;
valuesPrinted: false;
}
type HostProxyAction = "plan" | "apply" | "status";
interface HostProxyOptions {
action: HostProxyAction;
targetId: string;
confirm: boolean;
dryRun: boolean;
}
export async function runPlatformInfraHostProxyCommand(_config: UniDeskConfig, args: string[]): Promise<RenderedCliResult | Record<string, unknown>> {
const options = parseOptions(args);
const config = readHostProxyConfig();
const target = resolveTarget(config, options.targetId);
if (options.action === "plan") return renderPlan(plan(config.server, target));
if (options.action === "status") return renderStatus(status(config.server, target));
if (!options.confirm || options.dryRun) {
return renderPlan({
...plan(config.server, target),
mode: "dry-run",
mutation: false,
next: {
apply: `bun scripts/cli.ts platform-infra egress-proxy host apply --target ${target.id} --confirm`,
status: `bun scripts/cli.ts platform-infra egress-proxy host status --target ${target.id}`,
},
});
}
return renderApply(apply(config.server, target));
}
function parseOptions(args: string[]): HostProxyOptions {
const actionRaw = args[0] ?? "plan";
if (actionRaw !== "plan" && actionRaw !== "apply" && actionRaw !== "status") {
throw new Error("platform-infra egress-proxy host usage: host plan|apply|status --target JD01 [--dry-run|--confirm]");
}
const targetId = option(args, "--target");
const confirm = args.includes("--confirm");
const dryRun = args.includes("--dry-run") || !confirm;
if (confirm && args.includes("--dry-run")) throw new Error("host apply accepts only one of --confirm or --dry-run");
return { action: actionRaw, targetId: targetId ?? readHostProxyConfig().defaults.targetId, confirm, dryRun };
}
function readHostProxyConfig(): HostProxyConfig {
const root = record(Bun.YAML.parse(readFileSync(rootPath(configPath), "utf8")), configPath);
if (root.kind !== "platform-infra-host-proxy") throw new Error(`${configPath}.kind must be platform-infra-host-proxy`);
const defaultsRaw = record(root.defaults, `${configPath}.defaults`);
const server = parseServer(record(root.server, `${configPath}.server`), `${configPath}.server`);
const sources = Object.fromEntries(Object.entries(record(root.sources, `${configPath}.sources`)).map(([id, value]) => {
const source = parseSource(id, record(value, `${configPath}.sources.${id}`));
if (source.serverRef !== `server.${server.id}`) throw new Error(`${configPath}.sources.${id}.serverRef must be server.${server.id}`);
return [id, source];
}));
const targets: Record<string, HostProxyTarget> = {};
for (const [id, value] of Object.entries(record(root.targets, `${configPath}.targets`))) {
targets[id] = parseTarget(id, record(value, `${configPath}.targets.${id}`), sources);
}
return {
defaults: { targetId: stringField(defaultsRaw, "targetId", `${configPath}.defaults`) },
server,
sources,
targets,
};
}
function parseServer(raw: Record<string, unknown>, label: string): HostProxyServer {
const sourceConfigRef = stringField(raw, "sourceConfigRef", label);
const resolved = resolveEgressProxySourceRef(sourceConfigRef, `${label}.sourceConfigRef`);
if (resolved.sourceType !== "master-shadowsocks" || resolved.masterShadowsocks === null) {
throw new Error(`${label}.sourceConfigRef must reference a master-shadowsocks source`);
}
const healthRaw = record(raw.health, `${label}.health`);
const server = {
id: stringField(raw, "id", label),
enabled: booleanField(raw, "enabled", label),
benchmarkRef: stringField(raw, "benchmarkRef", label),
implementationRef: stringField(raw, "implementationRef", label),
sourceConfigRef,
sourceRef: resolved.sourceRef,
sourceKey: resolved.sourceKey,
sourceFingerprint: resolved.fingerprint,
masterShadowsocks: resolved.masterShadowsocks,
composeFile: repoYamlPathField(raw, "composeFile", label),
serviceName: stringField(raw, "serviceName", label),
containerName: stringField(raw, "containerName", label),
image: imageField(raw, "image", label),
configPath: absolutePathField(raw, "configPath", label),
listenHost: hostField(raw, "listenHost", label),
listenPort: portField(raw, "listenPort", label),
health: {
mode: enumField(healthRaw, "mode", `${label}.health`, ["tcp"] as const),
host: hostField(healthRaw, "host", `${label}.health`),
port: portField(healthRaw, "port", `${label}.health`),
},
fingerprint: "",
};
return {
...server,
fingerprint: fingerprint({
benchmarkRef: server.benchmarkRef,
implementationRef: server.implementationRef,
sourceConfigRef,
sourceFingerprint: server.sourceFingerprint,
composeFile: server.composeFile,
serviceName: server.serviceName,
containerName: server.containerName,
configPath: server.configPath,
listenHost: server.listenHost,
listenPort: server.listenPort,
health: server.health,
}),
};
}
function parseSource(id: string, raw: Record<string, unknown>): HostProxySource {
const sourceType = enumField(raw, "sourceType", `${configPath}.sources.${id}`, ["benchmark-validated-master-shadowsocks"] as const);
const sourceConfigRef = stringField(raw, "sourceConfigRef", `${configPath}.sources.${id}`);
const resolved = resolveEgressProxySourceRef(sourceConfigRef, `${configPath}.sources.${id}.sourceConfigRef`);
if (resolved.sourceType !== "master-shadowsocks" || resolved.masterShadowsocks === null) {
throw new Error(`${configPath}.sources.${id}.sourceConfigRef must reference a master-shadowsocks source`);
}
const client = clientSpec(record(raw.client, `${configPath}.sources.${id}.client`), `${configPath}.sources.${id}.client`);
const proxyUrl = urlField(raw, "proxyUrl", `${configPath}.sources.${id}`);
const expectedProxyUrl = `http://${client.listenHost}:${client.listenPort}`;
if (proxyUrl !== expectedProxyUrl) throw new Error(`${configPath}.sources.${id}.proxyUrl must be ${expectedProxyUrl}`);
const source = {
id,
sourceType,
serverRef: stringField(raw, "serverRef", `${configPath}.sources.${id}`),
benchmarkRef: stringField(raw, "benchmarkRef", `${configPath}.sources.${id}`),
implementationRef: stringField(raw, "implementationRef", `${configPath}.sources.${id}`),
sourceConfigRef,
sourceRef: resolved.sourceRef,
sourceKey: resolved.sourceKey,
sourceFingerprint: resolved.fingerprint,
masterShadowsocks: resolved.masterShadowsocks,
client,
proxyUrl,
externalProbeUrl: urlField(raw, "externalProbeUrl", `${configPath}.sources.${id}`),
fingerprint: "",
};
return {
...source,
fingerprint: fingerprint({
sourceType,
benchmarkRef: source.benchmarkRef,
implementationRef: source.implementationRef,
sourceConfigRef,
sourceFingerprint: source.sourceFingerprint,
client,
proxyUrl,
externalProbeUrl: source.externalProbeUrl,
}),
};
}
function clientSpec(raw: Record<string, unknown>, label: string): HostProxyClient {
const mode = enumField(raw, "mode", label, ["trans-static-binary"] as const);
const podAccess = raw.podAccess === undefined ? null : podAccessSpec(record(raw.podAccess, `${label}.podAccess`), `${label}.podAccess`);
const client = {
mode,
upstreamUrl: httpsUrlField(raw, "upstreamUrl", label),
version: stringField(raw, "version", label),
archiveCachePath: repoStatePathField(raw, "archiveCachePath", label),
archiveSha256: sha256Field(raw, "archiveSha256", label),
archiveInstallPath: absolutePathField(raw, "archiveInstallPath", label),
binaryMember: relativePathField(raw, "binaryMember", label),
binaryCachePath: repoStatePathField(raw, "binaryCachePath", label),
binarySha256: sha256Field(raw, "binarySha256", label),
installPath: absolutePathField(raw, "installPath", label),
configPath: absolutePathField(raw, "configPath", label),
unitPath: absolutePathField(raw, "unitPath", label),
serviceName: stringField(raw, "serviceName", label),
listenHost: hostField(raw, "listenHost", label),
listenPort: portField(raw, "listenPort", label),
podAccess,
clashApiListen: listenAddressField(raw, "clashApiListen", label),
healthUrl: urlField(raw, "healthUrl", label),
};
if (client.listenHost !== "127.0.0.1") throw new Error(`${label}.listenHost must stay 127.0.0.1 for host-local bootstrap proxy`);
if (client.unitPath !== `/etc/systemd/system/${client.serviceName}.service`) throw new Error(`${label}.unitPath must match serviceName`);
return client;
}
function podAccessSpec(raw: Record<string, unknown>, label: string): HostProxyPodAccess {
const enabled = booleanField(raw, "enabled", label);
const listenHost = hostField(raw, "listenHost", label);
const listenPort = portField(raw, "listenPort", label);
const proxyUrl = urlField(raw, "proxyUrl", label);
const expectedProxyUrl = `http://${listenHost}:${listenPort}`;
if (proxyUrl !== expectedProxyUrl) throw new Error(`${label}.proxyUrl must be ${expectedProxyUrl}`);
if (listenHost === "0.0.0.0" || listenHost === "127.0.0.1") {
throw new Error(`${label}.listenHost must be a pod-reachable private host address, not ${listenHost}`);
}
if (!privateIpv4(listenHost)) throw new Error(`${label}.listenHost must be a private IPv4 address`);
return { enabled, listenHost, listenPort, proxyUrl };
}
function parseTarget(id: string, raw: Record<string, unknown>, sources: Record<string, HostProxySource>): HostProxyTarget {
const sourceRef = stringField(raw, "sourceRef", `${configPath}.targets.${id}`);
if (!sourceRef.startsWith("sources.")) throw new Error(`${configPath}.targets.${id}.sourceRef must use sources.<id>`);
const sourceId = sourceRef.slice("sources.".length);
const source = sources[sourceId];
if (source === undefined) throw new Error(`${configPath}.targets.${id}.sourceRef points to missing source ${sourceId}`);
const envRaw = record(raw.env, `${configPath}.targets.${id}.env`);
const filesRaw = record(raw.files, `${configPath}.targets.${id}.files`);
const applyRaw = record(raw.apply, `${configPath}.targets.${id}.apply`);
const noProxy = stringArrayField(envRaw, "noProxy", `${configPath}.targets.${id}.env`);
if (!noProxy.includes("hyueapi.com") || !noProxy.includes(".hyueapi.com")) {
throw new Error(`${configPath}.targets.${id}.env.noProxy must preserve hyueapi.com and .hyueapi.com`);
}
const target: HostProxyTarget = {
id,
route: routeField(raw, "route", `${configPath}.targets.${id}`),
enabled: booleanField(raw, "enabled", `${configPath}.targets.${id}`),
sourceRef,
source,
env: {
httpProxy: urlField(envRaw, "httpProxy", `${configPath}.targets.${id}.env`),
httpsProxy: urlField(envRaw, "httpsProxy", `${configPath}.targets.${id}.env`),
allProxy: urlField(envRaw, "allProxy", `${configPath}.targets.${id}.env`),
noProxy,
},
files: {
envFile: absolutePathField(filesRaw, "envFile", `${configPath}.targets.${id}.files`),
profile: absolutePathField(filesRaw, "profile", `${configPath}.targets.${id}.files`),
apt: absolutePathField(filesRaw, "apt", `${configPath}.targets.${id}.files`),
dockerSystemdDropIn: absolutePathField(filesRaw, "dockerSystemdDropIn", `${configPath}.targets.${id}.files`),
k3sSystemdDropIn: absolutePathField(filesRaw, "k3sSystemdDropIn", `${configPath}.targets.${id}.files`),
},
apply: {
reloadSystemd: booleanField(applyRaw, "reloadSystemd", `${configPath}.targets.${id}.apply`),
restartDocker: booleanField(applyRaw, "restartDocker", `${configPath}.targets.${id}.apply`),
restartK3s: booleanField(applyRaw, "restartK3s", `${configPath}.targets.${id}.apply`),
},
};
if (target.env.httpProxy !== source.proxyUrl || target.env.httpsProxy !== source.proxyUrl || target.env.allProxy !== source.proxyUrl) {
throw new Error(`${configPath}.targets.${id}.env proxy URLs must match ${sourceRef}.proxyUrl`);
}
return target;
}
function resolveTarget(config: HostProxyConfig, targetId: string): HostProxyTarget {
const target = config.targets[targetId];
if (target === undefined) throw new Error(`${configPath}.targets.${targetId} is not defined`);
return target;
}
function plan(server: HostProxyServer, target: HostProxyTarget): Record<string, unknown> {
const client = target.source.client;
return {
ok: true,
command: "platform-infra egress-proxy host plan",
mode: "plan",
mutation: false,
server: serverSummary(server),
target: targetSummary(target),
artifact: artifactSummary(client),
files: target.files,
apply: target.apply,
env: { proxyUrl: target.env.httpProxy, noProxyCount: target.env.noProxy.length, noProxyIncludesHyueapi: true },
valuesPrinted: false,
next: {
dryRun: `bun scripts/cli.ts platform-infra egress-proxy host apply --target ${target.id} --dry-run`,
apply: `bun scripts/cli.ts platform-infra egress-proxy host apply --target ${target.id} --confirm`,
status: `bun scripts/cli.ts platform-infra egress-proxy host status --target ${target.id}`,
},
};
}
function apply(server: HostProxyServer, target: HostProxyTarget): Record<string, unknown> {
const material = proxySecretMaterial(server, target.source);
const serverApply = applyMasterProxyServer(server, material);
const artifact = prepareClientArtifact(target.source.client);
const remotePrepare = runTrans(target.route, remotePrepareScript(target.source.client), 30);
const remoteArchive = remotePrepare.exitCode === 0
? runTrans(target.route, remoteArchiveStatusScript(target.source.client), 30)
: remotePrepare;
const remoteArchiveParsed = record(parseJson(remoteArchive.stdout) ?? {});
const upload = remotePrepare.exitCode === 0 && remoteArchive.exitCode === 0 && remoteArchiveParsed.archiveSha256Ok === true
? skippedCommandResult(["bun", "scripts/ssh-cli.ts", "ssh", target.route, "upload", rootPath(target.source.client.archiveCachePath), target.source.client.archiveInstallPath], "remote archive already matches YAML sha256")
: remotePrepare.exitCode === 0
? runTransUpload(target.route, rootPath(target.source.client.archiveCachePath), target.source.client.archiveInstallPath, 1_800_000)
: remotePrepare;
const remoteApply = upload.exitCode === 0
? runTrans(target.route, remoteApplyScript(target, material.clientConfigJson), 60)
: upload;
const parsed = parseJson(remoteApply.stdout);
return {
ok: serverApply.ok === true && artifact.ok === true && remotePrepare.exitCode === 0 && upload.exitCode === 0 && remoteApply.exitCode === 0 && record(parsed).ok !== false,
command: "platform-infra egress-proxy host apply",
mode: "apply",
mutation: serverApply.ok === true && remoteApply.exitCode === 0,
server: serverSummary(server),
serverApply,
target: targetSummary(target),
artifact,
distribution: {
mode: target.source.client.mode,
artifact: "archive",
prepare: compactLocalResult(remotePrepare),
remoteArchive: compactLocalResult(remoteArchive),
upload: compactLocalResult(upload),
},
remote: parsed ?? { stdoutPreview: remoteApply.stdout.slice(0, 2000) },
stderrTail: remoteApply.stderr.slice(-2000),
valuesPrinted: false,
next: { status: `bun scripts/cli.ts platform-infra egress-proxy host status --target ${target.id}` },
};
}
function status(server: HostProxyServer, target: HostProxyTarget): Record<string, unknown> {
const serverStatus = masterProxyServerStatus(server);
const result = runTrans(target.route, remoteStatusScript(target), 45);
const parsed = parseJson(result.stdout);
return {
ok: serverStatus.ok === true && result.exitCode === 0 && record(parsed).ok !== false,
command: "platform-infra egress-proxy host status",
mode: "status",
mutation: false,
server: serverSummary(server),
serverStatus,
target: targetSummary(target),
remote: parsed ?? { stdoutPreview: result.stdout.slice(0, 2000) },
stderrTail: result.stderr.slice(-2000),
valuesPrinted: false,
};
}
function proxySecretMaterial(server: HostProxyServer, source: HostProxySource): HostProxySecretMaterial {
const envSource = readEnvSourceFile({
root: rootPath(".state", "secrets"),
sourceRef: source.sourceRef,
missingMessage: (sourcePath) => `host proxy requires ${sourcePath} with ${source.sourceKey}`,
});
const password = requiredEnvValue(envSource.values, source.sourceKey, source.sourceRef);
const serverConfigJson = renderMasterShadowsocksServerConfig(server, password);
const clientConfigJson = renderSingBoxClientConfig(source, password);
return {
serverConfigJson,
clientConfigJson,
sourcePath: envSource.sourcePathRedacted,
fingerprint: fingerprintSecretValues({ password, serverConfigJson, clientConfigJson }, ["password", "serverConfigJson", "clientConfigJson"]),
valuesPrinted: false,
};
}
function renderMasterShadowsocksServerConfig(server: HostProxyServer, password: string): string {
return `${JSON.stringify({
server: server.listenHost,
server_port: server.listenPort,
password,
method: server.masterShadowsocks.method,
timeout: 300,
mode: "tcp_only",
}, null, 2)}\n`;
}
function renderSingBoxClientConfig(source: HostProxySource, password: string): string {
const client = source.client;
const inbounds = [
{ type: "mixed", tag: "mixed-host-local", listen: client.listenHost, listen_port: client.listenPort },
...(client.podAccess?.enabled
? [{ type: "mixed", tag: "mixed-pod-access", listen: client.podAccess.listenHost, listen_port: client.podAccess.listenPort }]
: []),
];
return `${JSON.stringify({
log: { level: "info", timestamp: true },
experimental: { clash_api: { external_controller: client.clashApiListen } },
inbounds,
outbounds: [
{
type: "shadowsocks",
tag: "master-vpn",
server: source.masterShadowsocks.serverHost,
server_port: source.masterShadowsocks.serverPort,
method: source.masterShadowsocks.method,
password,
},
{ type: "direct", tag: "direct" },
{ type: "block", tag: "block" },
],
route: {
rules: [
{ ip_is_private: true, outbound: "direct" },
{ domain_suffix: ["cluster.local", "svc"], outbound: "direct" },
],
final: "master-vpn",
},
}, null, 2)}\n`;
}
function applyMasterProxyServer(server: HostProxyServer, material: HostProxySecretMaterial): Record<string, unknown> {
mkdirSync(dirname(server.configPath), { recursive: true });
writeFileSync(server.configPath, material.serverConfigJson, "utf8");
chmodSync(server.configPath, 0o600);
const compose = runCommand(["docker", "compose", "-f", rootPath(server.composeFile), "up", "-d", server.serviceName], rootPath(), { timeoutMs: 60_000 });
const health = masterProxyTcpHealth(server);
const inspect = runCommand(["docker", "inspect", server.containerName, "--format", "{{.State.Status}} {{.State.Running}} {{.Config.Image}}"], rootPath(), { timeoutMs: 15_000 });
const existingHealthy = compose.exitCode !== 0
&& /container name .* already in use|Conflict\. The container name/i.test(compose.stderr)
&& inspect.exitCode === 0
&& /\brunning true\b/.test(inspect.stdout.trim())
&& health.ok === true;
return {
ok: server.enabled && (compose.exitCode === 0 || existingHealthy) && health.ok,
enabled: server.enabled,
benchmarkRef: server.benchmarkRef,
implementationRef: server.implementationRef,
sourceConfigRef: server.sourceConfigRef,
sourceFingerprint: server.sourceFingerprint,
materialFingerprint: material.fingerprint,
compose: compactLocalResult(compose),
existingHealthy,
inspect: compactLocalResult(inspect),
health,
valuesPrinted: false,
};
}
function masterProxyServerStatus(server: HostProxyServer): Record<string, unknown> {
const inspect = runCommand(["docker", "inspect", server.containerName, "--format", "{{.State.Status}} {{.State.Running}} {{.Config.Image}}"], rootPath(), { timeoutMs: 15_000 });
const health = masterProxyTcpHealth(server);
return {
ok: server.enabled && inspect.exitCode === 0 && health.ok,
enabled: server.enabled,
benchmarkRef: server.benchmarkRef,
implementationRef: server.implementationRef,
sourceConfigRef: server.sourceConfigRef,
sourceFingerprint: server.sourceFingerprint,
container: inspect.exitCode === 0 ? inspect.stdout.trim() : "missing",
inspect: compactLocalResult(inspect),
health,
valuesPrinted: false,
};
}
function prepareClientArtifact(client: HostProxyClient): Record<string, unknown> {
const binaryPath = rootPath(client.binaryCachePath);
const archivePath = rootPath(client.archiveCachePath);
let archiveAction = "existing";
let binaryAction = "existing";
if (!existsSync(binaryPath) || sha256File(binaryPath) !== client.binarySha256) {
if (!existsSync(archivePath) || sha256File(archivePath) !== client.archiveSha256) {
mkdirSync(dirname(archivePath), { recursive: true });
const download = runCommand(["curl", "-fL", "--connect-timeout", "15", "--max-time", "240", "-o", archivePath, client.upstreamUrl], rootPath(), { timeoutMs: 300_000 });
if (download.exitCode !== 0) return { ok: false, action: "download", result: compactLocalResult(download), valuesPrinted: false };
archiveAction = "downloaded";
}
if (sha256File(archivePath) !== client.archiveSha256) {
return { ok: false, action: archiveAction, reason: "archive-sha256-mismatch", path: client.archiveCachePath, valuesPrinted: false };
}
mkdirSync(dirname(binaryPath), { recursive: true });
const extract = runCommand(["bash", "-lc", `tar -xOf ${shQuote(archivePath)} ${shQuote(client.binaryMember)} > ${shQuote(binaryPath)} && chmod 0755 ${shQuote(binaryPath)}`], rootPath(), { timeoutMs: 120_000 });
if (extract.exitCode !== 0) return { ok: false, action: "extract", result: compactLocalResult(extract), valuesPrinted: false };
binaryAction = "extracted";
}
const binarySha = sha256File(binaryPath);
return {
ok: binarySha === client.binarySha256,
mode: client.mode,
version: client.version,
archiveCachePath: client.archiveCachePath,
binaryCachePath: client.binaryCachePath,
archiveAction,
binaryAction,
binarySha256: binarySha,
installPath: client.installPath,
valuesPrinted: false,
};
}
function remotePrepareScript(client: HostProxyClient): string {
return `
set -eu
mkdir -p ${shQuote(dirname(client.installPath))} ${shQuote(dirname(client.configPath))} ${shQuote(dirname(client.unitPath))} ${shQuote(dirname(client.archiveInstallPath))}
`;
}
function remoteArchiveStatusScript(client: HostProxyClient): string {
return `
set -eu
archive_sha="$(sha256sum ${shQuote(client.archiveInstallPath)} 2>/dev/null | awk '{print $1}' || true)"
python3 - "$archive_sha" <<'PY'
import json
import sys
archive_sha = sys.argv[1]
payload = {
"ok": True,
"archivePresent": bool(archive_sha),
"archiveSha256Ok": archive_sha == "${client.archiveSha256}",
"archiveSha256Prefix": archive_sha[:12] if archive_sha else "",
"valuesPrinted": False,
}
print(json.dumps(payload, ensure_ascii=False, indent=2))
PY
`;
}
function remoteApplyScript(target: HostProxyTarget, clientConfigJson: string): string {
const client = target.source.client;
const envFile = renderEnvFile(target);
const profile = renderProfile(target);
const apt = renderApt(target);
const dockerDropIn = renderSystemdDropIn(target.files.envFile);
const k3sDropIn = renderSystemdDropIn(target.files.envFile);
const unit = renderClientUnit(client);
const daemonReload = target.apply.reloadSystemd ? "systemctl daemon-reload >/dev/null 2>&1 || true" : ": # reloadSystemd disabled by YAML";
const restartDocker = target.apply.restartDocker
? "nohup sh -c 'sleep 1; systemctl restart docker >/tmp/unidesk-host-proxy-docker-restart.out 2>/tmp/unidesk-host-proxy-docker-restart.err || true' >/dev/null 2>&1 &"
: ": # restartDocker disabled by YAML";
const restartK3s = target.apply.restartK3s
? "nohup sh -c 'sleep 1; systemctl restart k3s >/tmp/unidesk-host-proxy-k3s-restart.out 2>/tmp/unidesk-host-proxy-k3s-restart.err || true' >/dev/null 2>&1 &"
: ": # restartK3s disabled by YAML";
return `
set -eu
write_b64() {
path="$1"
mode="$2"
data="$3"
mkdir -p "$(dirname "$path")"
tmp="$path.tmp.$$"
printf '%s' "$data" | base64 -d >"$tmp"
chmod "$mode" "$tmp"
mv "$tmp" "$path"
}
write_b64 ${shQuote(client.configPath)} 0600 ${shQuote(b64(clientConfigJson))}
write_b64 ${shQuote(client.unitPath)} 0644 ${shQuote(b64(unit))}
write_b64 ${shQuote(target.files.envFile)} 0644 ${shQuote(b64(envFile))}
write_b64 ${shQuote(target.files.profile)} 0644 ${shQuote(b64(profile))}
write_b64 ${shQuote(target.files.apt)} 0644 ${shQuote(b64(apt))}
write_b64 ${shQuote(target.files.dockerSystemdDropIn)} 0644 ${shQuote(b64(dockerDropIn))}
write_b64 ${shQuote(target.files.k3sSystemdDropIn)} 0644 ${shQuote(b64(k3sDropIn))}
archive_sha="$(sha256sum ${shQuote(client.archiveInstallPath)} 2>/dev/null | awk '{print $1}' || true)"
if [ "$archive_sha" != ${shQuote(client.archiveSha256)} ]; then
printf 'archive sha256 mismatch\\n' >&2
exit 23
fi
tmp_bin="${client.installPath}.tmp.$$"
tar -xOf ${shQuote(client.archiveInstallPath)} ${shQuote(client.binaryMember)} >"$tmp_bin"
chmod 0755 "$tmp_bin"
binary_sha="$(sha256sum "$tmp_bin" | awk '{print $1}')"
if [ "$binary_sha" != ${shQuote(client.binarySha256)} ]; then
rm -f "$tmp_bin"
printf 'binary sha256 mismatch\\n' >&2
exit 24
fi
mv -f "$tmp_bin" ${shQuote(client.installPath)}
chmod 0755 ${shQuote(client.installPath)}
${daemonReload}
systemctl enable --now ${shQuote(client.serviceName)} >/tmp/unidesk-host-proxy-systemctl.out 2>/tmp/unidesk-host-proxy-systemctl.err || true
systemctl restart ${shQuote(client.serviceName)} >/tmp/unidesk-host-proxy-restart.out 2>/tmp/unidesk-host-proxy-restart.err || true
sleep 2
${remoteStatusProbeShell(target)}
${restartDocker}
${restartK3s}
`;
}
function remoteStatusScript(target: HostProxyTarget): string {
return `
set -eu
${remoteStatusProbeShell(target)}
`;
}
function remoteStatusProbeShell(target: HostProxyTarget): string {
const client = target.source.client;
const podAccess = client.podAccess;
const podAccessShell = podAccess?.enabled
? `
pod_probe_rc=0
pod_probe_status="$(HTTP_PROXY=${shQuote(podAccess.proxyUrl)} HTTPS_PROXY=${shQuote(podAccess.proxyUrl)} ALL_PROXY=${shQuote(podAccess.proxyUrl)} NO_PROXY=${shQuote(target.env.noProxy.join(","))} curl -fsS -o /dev/null -w '%{http_code}' --max-time 20 ${shQuote(target.source.externalProbeUrl)} 2>/tmp/unidesk-host-proxy-pod-probe.err)" || pod_probe_rc=$?
`
: `
pod_probe_rc=0
pod_probe_status="skipped"
`;
return `
exists() { [ -f "$1" ] && printf present || printf missing; }
contains() { grep -F "$2" "$1" >/dev/null 2>&1 && printf true || printf false; }
service_active="$(systemctl is-active ${shQuote(client.serviceName)} 2>/dev/null || true)"
binary_sha="$(sha256sum ${shQuote(client.installPath)} 2>/dev/null | awk '{print $1}' || true)"
archive_sha="$(sha256sum ${shQuote(client.archiveInstallPath)} 2>/dev/null | awk '{print $1}' || true)"
health_rc=0
curl -fsS --max-time 5 ${shQuote(client.healthUrl)} >/tmp/unidesk-host-proxy-health.out 2>/tmp/unidesk-host-proxy-health.err || health_rc=$?
probe_rc=0
probe_status="$(HTTP_PROXY=${shQuote(target.env.httpProxy)} HTTPS_PROXY=${shQuote(target.env.httpsProxy)} ALL_PROXY=${shQuote(target.env.allProxy)} NO_PROXY=${shQuote(target.env.noProxy.join(","))} curl -fsS -o /dev/null -w '%{http_code}' --max-time 20 ${shQuote(target.source.externalProbeUrl)} 2>/tmp/unidesk-host-proxy-probe.err)" || probe_rc=$?
${podAccessShell}
python3 - "$health_rc" "$probe_rc" "$probe_status" "$(exists ${shQuote(target.files.envFile)})" "$(exists ${shQuote(target.files.profile)})" "$(exists ${shQuote(target.files.apt)})" "$(exists ${shQuote(target.files.dockerSystemdDropIn)})" "$(exists ${shQuote(target.files.k3sSystemdDropIn)})" "$(contains ${shQuote(target.files.envFile)} hyueapi.com)" "$(contains ${shQuote(target.files.envFile)} .hyueapi.com)" "$service_active" "$binary_sha" "$archive_sha" ${shQuote(podAccess?.enabled ? "true" : "false")} "$pod_probe_rc" "$pod_probe_status" <<'PY'
import json
import sys
health_rc = int(sys.argv[1])
probe_rc = int(sys.argv[2])
service_active = sys.argv[11]
binary_sha = sys.argv[12]
archive_sha = sys.argv[13]
pod_access_enabled = sys.argv[14] == "true"
pod_probe_rc = int(sys.argv[15])
payload = {
"ok": health_rc == 0 and probe_rc == 0 and (not pod_access_enabled or pod_probe_rc == 0) and service_active == "active" and binary_sha == "${client.binarySha256}" and archive_sha == "${client.archiveSha256}" and all(value == "present" for value in sys.argv[4:9]) and sys.argv[9] == "true" and sys.argv[10] == "true",
"target": "${target.id}",
"route": "${target.route}",
"sourceRef": "${target.sourceRef}",
"sourceFingerprint": "${target.source.fingerprint}",
"client": {
"mode": "${client.mode}",
"service": "${client.serviceName}",
"serviceActive": service_active,
"binarySha256Ok": binary_sha == "${client.binarySha256}",
"archiveSha256Ok": archive_sha == "${client.archiveSha256}",
"listen": "${client.listenHost}:${client.listenPort}",
"podAccess": {
"enabled": pod_access_enabled,
"listen": "${podAccess?.enabled ? `${podAccess.listenHost}:${podAccess.listenPort}` : ""}",
"proxyUrl": "${podAccess?.enabled ? podAccess.proxyUrl : ""}",
},
},
"files": {
"envFile": sys.argv[4],
"profile": sys.argv[5],
"apt": sys.argv[6],
"dockerSystemdDropIn": sys.argv[7],
"k3sSystemdDropIn": sys.argv[8],
},
"noProxy": {"hyueapi": sys.argv[9] == "true", "wildcardHyueapi": sys.argv[10] == "true"},
"health": {"exitCode": health_rc, "url": "${client.healthUrl}"},
"externalProbe": {"exitCode": probe_rc, "status": sys.argv[3], "url": "${target.source.externalProbeUrl}"},
"podAccessProbe": {"enabled": pod_access_enabled, "exitCode": pod_probe_rc, "status": sys.argv[16], "url": "${target.source.externalProbeUrl}"},
"valuesPrinted": False,
}
print(json.dumps(payload, ensure_ascii=False, indent=2))
sys.exit(0 if payload["ok"] else 1)
PY
`;
}
function renderClientUnit(client: HostProxyClient): string {
return [
"[Unit]",
"Description=UniDesk host egress proxy client",
"After=network-online.target",
"Wants=network-online.target",
"",
"[Service]",
"Type=simple",
`ExecStart=${client.installPath} run -c ${client.configPath}`,
"Restart=always",
"RestartSec=5",
"LimitNOFILE=1048576",
"NoNewPrivileges=true",
"",
"[Install]",
"WantedBy=multi-user.target",
"",
].join("\n");
}
function renderEnvFile(target: HostProxyTarget): string {
const noProxy = target.env.noProxy.join(",");
return [
"# Generated by: bun scripts/cli.ts platform-infra egress-proxy host apply",
"# Source: config/platform-infra/host-proxy.yaml",
`HTTP_PROXY=${target.env.httpProxy}`,
`HTTPS_PROXY=${target.env.httpsProxy}`,
`ALL_PROXY=${target.env.allProxy}`,
`NO_PROXY=${noProxy}`,
`http_proxy=${target.env.httpProxy}`,
`https_proxy=${target.env.httpsProxy}`,
`all_proxy=${target.env.allProxy}`,
`no_proxy=${noProxy}`,
"",
].join("\n");
}
function renderProfile(target: HostProxyTarget): string {
return renderEnvFile(target)
.split("\n")
.filter((line) => line.length > 0 && !line.startsWith("#"))
.map((line) => `export ${line}`)
.join("\n") + "\n";
}
function renderApt(target: HostProxyTarget): string {
return [
`Acquire::http::Proxy "${target.env.httpProxy}";`,
`Acquire::https::Proxy "${target.env.httpsProxy}";`,
"",
].join("\n");
}
function renderSystemdDropIn(envFile: string): string {
return ["[Service]", `EnvironmentFile=-${envFile}`, ""].join("\n");
}
function serverSummary(server: HostProxyServer): Record<string, unknown> {
return {
id: server.id,
enabled: server.enabled,
benchmarkRef: server.benchmarkRef,
implementationRef: server.implementationRef,
sourceConfigRef: server.sourceConfigRef,
sourceFingerprint: server.sourceFingerprint,
composeFile: server.composeFile,
serviceName: server.serviceName,
containerName: server.containerName,
image: server.image,
listen: `${server.listenHost}:${server.listenPort}`,
health: `${server.health.host}:${server.health.port}`,
fingerprint: server.fingerprint,
valuesPrinted: false,
};
}
function targetSummary(target: HostProxyTarget): Record<string, unknown> {
const client = target.source.client;
return {
id: target.id,
route: target.route,
enabled: target.enabled,
sourceRef: target.sourceRef,
sourceType: target.source.sourceType,
sourceFingerprint: target.source.fingerprint,
proxyUrl: target.source.proxyUrl,
benchmarkRef: target.source.benchmarkRef,
implementationRef: target.source.implementationRef,
client: {
mode: client.mode,
version: client.version,
installPath: client.installPath,
configPath: client.configPath,
unitPath: client.unitPath,
serviceName: client.serviceName,
listen: `${client.listenHost}:${client.listenPort}`,
},
trans: {
hostProxyEnv: transHostProxyEnvSummary(target.id),
},
};
}
function artifactSummary(client: HostProxyClient): Record<string, unknown> {
return {
mode: client.mode,
version: client.version,
upstreamUrl: client.upstreamUrl,
archiveCachePath: client.archiveCachePath,
archiveSha256: client.archiveSha256,
archiveInstallPath: client.archiveInstallPath,
binaryCachePath: client.binaryCachePath,
binarySha256: client.binarySha256,
installPath: client.installPath,
};
}
function masterProxyTcpHealth(server: HostProxyServer): Record<string, unknown> {
const result = runCommand(["bash", "-lc", `timeout 5 bash -lc ${shQuote(`</dev/tcp/${server.health.host}/${server.health.port}`)}`], rootPath(), { timeoutMs: 8_000 });
return { ok: result.exitCode === 0, mode: server.health.mode, host: server.health.host, port: server.health.port, result: compactLocalResult(result) };
}
function runTrans(route: string, script: string, timeoutSeconds: number): CommandResult {
return runCommand(["bun", "scripts/ssh-cli.ts", "ssh", route, "bash", "--", script], rootPath(), { timeoutMs: timeoutSeconds * 1000 });
}
function runTransUpload(route: string, localPath: string, remotePath: string, timeoutMs: number): CommandResult {
return runCommand(["bun", "scripts/ssh-cli.ts", "ssh", route, "upload", localPath, remotePath], rootPath(), { timeoutMs });
}
function skippedCommandResult(command: string[], reason: string): CommandResult {
return {
command,
cwd: rootPath(),
exitCode: 0,
stdout: JSON.stringify({ ok: true, skipped: true, reason }, null, 2),
stderr: "",
signal: null,
timedOut: false,
};
}
function sha256File(path: string): string {
return createHash("sha256").update(readFileSync(path)).digest("hex");
}
function compactLocalResult(result: CommandResult): Record<string, unknown> {
return {
exitCode: result.exitCode,
timedOut: result.timedOut,
stdoutTail: result.stdout.slice(-1000),
stderrTail: result.stderr.slice(-1000),
};
}
function renderPlan(result: Record<string, unknown>): RenderedCliResult {
const server = record(result.server);
const target = record(result.target);
const env = record(result.env);
const artifact = record(result.artifact);
const next = record(result.next);
const lines = [
"PLATFORM-INFRA HOST PROXY PLAN",
...table(["SERVER", "LISTEN", "BENCHMARK", "SOURCE"], [[text(server.id), text(server.listen), text(server.benchmarkRef), text(server.sourceConfigRef)]]),
"",
...table(["TARGET", "ROUTE", "SOURCE", "PROXY"], [[text(target.id), text(target.route), text(target.sourceRef), text(target.proxyUrl)]]),
"",
...table(["CLIENT_MODE", "VERSION", "INSTALL", "SHA256"], [[text(artifact.mode), text(artifact.version), text(artifact.installPath), shortSha(text(artifact.binarySha256))]]),
"",
...table(["PROXY", "NO_PROXY_COUNT", "HYUEAPI", "VALUES"], [[text(env.proxyUrl), text(env.noProxyCount), "preserved", "printed=false"]]),
"",
...transHostProxyEnvTable(target),
"",
"NEXT",
` dry-run: ${text(next.dryRun, "")}`,
` apply: ${text(next.apply, "")}`,
` status: ${text(next.status, "")}`,
"",
"Disclosure: Secret material and generated proxy config are not printed.",
];
return { ok: result.ok !== false, command: text(result.command, "platform-infra egress-proxy host plan"), renderedText: lines.join("\n"), contentType: "text/plain" };
}
function renderApply(result: Record<string, unknown>): RenderedCliResult {
const server = record(result.server);
const serverApply = record(result.serverApply);
const serverHealth = record(serverApply.health);
const target = record(result.target);
const artifact = record(result.artifact);
const distribution = record(result.distribution);
const remote = record(result.remote);
const client = record(remote.client);
const podProbe = record(remote.podAccessProbe);
const probe = record(remote.externalProbe);
const lines = [
"PLATFORM-INFRA HOST PROXY APPLY",
...table(["SERVER", "OK", "LISTEN", "BENCHMARK"], [[text(server.id), text(serverApply.ok), text(server.listen), text(server.benchmarkRef)]]),
"",
...table(["SERVER_HEALTH", "CLIENT_BINARY", "DISTRIBUTION"], [[text(serverHealth.ok), text(artifact.binaryAction), text(distribution.mode)]]),
"",
...table(["TARGET", "ROUTE", "OK", "CLIENT"], [[text(target.id), text(target.route), result.ok === false ? "false" : "true", text(client.service)]]),
"",
...table(["SERVICE_ACTIVE", "BINARY_SHA", "HOST_PROBE", "POD_PROBE", "VALUES"], [[text(client.serviceActive), text(client.binarySha256Ok), text(probe.status), text(podProbe.status, "skipped"), "printed=false"]]),
"",
...transHostProxyEnvTable(target),
"",
`NEXT\n ${text(record(result.next).status, "")}`,
];
return { ok: result.ok !== false, command: "platform-infra egress-proxy host apply", renderedText: lines.join("\n"), contentType: "text/plain" };
}
function renderStatus(result: Record<string, unknown>): RenderedCliResult {
const server = record(result.server);
const serverStatus = record(result.serverStatus);
const target = record(result.target);
const remote = record(result.remote);
const client = record(remote.client);
const files = record(remote.files);
const noProxy = record(remote.noProxy);
const health = record(remote.health);
const probe = record(remote.externalProbe);
const podProbe = record(remote.podAccessProbe);
const lines = [
"PLATFORM-INFRA HOST PROXY STATUS",
...table(["SERVER", "OK", "LISTEN", "CONTAINER"], [[text(server.id), text(serverStatus.ok), text(server.listen), text(serverStatus.container)]]),
"",
...table(["TARGET", "ROUTE", "OK", "CLIENT"], [[text(target.id), text(target.route), result.ok === false ? "false" : "true", text(client.service)]]),
"",
...table(["SERVICE_ACTIVE", "BINARY_SHA", "HEALTH_EXIT", "HOST_PROBE", "POD_PROBE"], [[text(client.serviceActive), text(client.binarySha256Ok), text(health.exitCode), text(probe.status), text(podProbe.status, "skipped")]]),
"",
...table(["ENV", "PROFILE", "APT", "DOCKER_DROPIN", "K3S_DROPIN"], [[text(files.envFile), text(files.profile), text(files.apt), text(files.dockerSystemdDropIn), text(files.k3sSystemdDropIn)]]),
"",
...table(["NO_PROXY_HYUEAPI", "NO_PROXY_WILDCARD", "VALUES"], [[text(noProxy.hyueapi), text(noProxy.wildcardHyueapi), "printed=false"]]),
"",
...transHostProxyEnvTable(target),
"",
"Disclosure: file content, Secret values and generated proxy config are not printed.",
];
return { ok: result.ok !== false, command: "platform-infra egress-proxy host status", renderedText: lines.join("\n"), contentType: "text/plain" };
}
function transHostProxyEnvTable(target: Record<string, unknown>): string[] {
const trans = record(target.trans);
const rule = record(trans.hostProxyEnv);
return table(
["TRANS_PROXY_ENV", "ENV_REF", "APPLY_TO", "ENV_FILE"],
[[text(rule.enabled, "false"), text(rule.envFileRef, ""), text(rule.applyTo, "disabled"), text(rule.envFile, "")]],
);
}
function option(args: string[], name: string): string | null {
const index = args.indexOf(name);
if (index === -1) return null;
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`);
return value;
}
function parseJson(value: string): unknown {
const trimmed = value.trim();
if (trimmed.length === 0) return null;
try { return JSON.parse(trimmed) as unknown; } catch {
const start = trimmed.indexOf("{");
const end = trimmed.lastIndexOf("}");
if (start >= 0 && end > start) {
try { return JSON.parse(trimmed.slice(start, end + 1)) as unknown; } catch {}
}
}
return null;
}
function record(value: unknown, _label = "value"): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
return value as Record<string, unknown>;
}
function stringField(obj: Record<string, unknown>, key: string, label: string): string {
const value = obj[key];
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${label}.${key} must be a non-empty string`);
return value.trim();
}
function routeField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
if (!/^[A-Za-z0-9:_./-]+$/u.test(value) || value.includes("..")) throw new Error(`${label}.${key} has an unsupported route format`);
return value;
}
function repoYamlPathField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
if (value.startsWith("/") || value.includes("..") || !value.startsWith("config/") || !value.endsWith(".yaml")) {
throw new Error(`${label}.${key} must be a repo-relative config/*.yaml path without ..`);
}
return value;
}
function repoStatePathField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
if (value.startsWith("/") || value.includes("..") || !value.startsWith(".state/")) {
throw new Error(`${label}.${key} must be a repo-relative .state/ path without ..`);
}
return value;
}
function relativePathField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
if (value.startsWith("/") || value.includes("..")) throw new Error(`${label}.${key} must be a relative path without ..`);
return value;
}
function absolutePathField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
if (!value.startsWith("/") || value.includes("..")) throw new Error(`${label}.${key} must be an absolute path without ..`);
return value;
}
function booleanField(obj: Record<string, unknown>, key: string, label: string): boolean {
const value = obj[key];
if (typeof value !== "boolean") throw new Error(`${label}.${key} must be a boolean`);
return value;
}
function stringArrayField(obj: Record<string, unknown>, key: string, label: string): string[] {
const value = obj[key];
if (!Array.isArray(value) || !value.every((item) => typeof item === "string" && item.trim().length > 0)) {
throw new Error(`${label}.${key} must be an array of non-empty strings`);
}
return value.map((item) => item.trim());
}
function enumField<const T extends readonly string[]>(obj: Record<string, unknown>, key: string, label: string, values: T): T[number] {
const value = stringField(obj, key, label);
if (!(values as readonly string[]).includes(value)) throw new Error(`${label}.${key} must be one of ${values.join(", ")}`);
return value as T[number];
}
function urlField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
const url = new URL(value);
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error(`${label}.${key} must use http:// or https://`);
if (url.username || url.password || url.hash) throw new Error(`${label}.${key} must not include credentials or hash`);
return value;
}
function httpsUrlField(obj: Record<string, unknown>, key: string, label: string): string {
const value = urlField(obj, key, label);
if (!value.startsWith("https://")) throw new Error(`${label}.${key} must use https://`);
return value;
}
function hostField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
if (!/^[A-Za-z0-9._:-]+$/u.test(value)) throw new Error(`${label}.${key} has an unsupported host/listen format`);
return value;
}
function privateIpv4(value: string): boolean {
const parts = value.split(".").map((part) => Number(part));
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return false;
return parts[0] === 10
|| (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
|| (parts[0] === 192 && parts[1] === 168);
}
function portField(obj: Record<string, unknown>, key: string, label: string): number {
const value = obj[key];
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 65535) throw new Error(`${label}.${key} must be a TCP port`);
return value;
}
function imageField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
if (!/^[A-Za-z0-9._:/@+-]+$/u.test(value) || value.includes("..")) throw new Error(`${label}.${key} has an unsupported image reference format`);
return value;
}
function listenAddressField(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label);
const [host, portRaw, extra] = value.split(":");
if (extra !== undefined || host === undefined || portRaw === undefined || host.length === 0) throw new Error(`${label}.${key} must be host:port`);
const port = Number(portRaw);
if (!/^[A-Za-z0-9._-]+$/u.test(host) || !Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`${label}.${key} must be host:port`);
return value;
}
function sha256Field(obj: Record<string, unknown>, key: string, label: string): string {
const value = stringField(obj, key, label).toLowerCase();
if (!/^[0-9a-f]{64}$/u.test(value)) throw new Error(`${label}.${key} must be a sha256 hex digest`);
return value;
}
function b64(value: string): string {
return Buffer.from(value, "utf8").toString("base64");
}
function fingerprint(value: unknown): string {
return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16)}`;
}
function text(value: unknown, fallback = "-"): string {
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
return fallback;
}
function shortSha(value: string): string {
return /^[0-9a-f]{64}$/u.test(value) ? value.slice(0, 12) : value;
}
function table(headers: string[], rows: string[][]): string[] {
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
const format = (row: string[]) => row.map((cell, index) => (cell ?? "").padEnd(widths[index] ?? 0)).join(" ").trimEnd();
return [format(headers), format(headers.map((header, index) => "-".repeat(widths[index] ?? header.length))), ...rows.map(format)];
}