import { createHash } from "node:crypto"; import { existsSync, readFileSync } from "node:fs"; import { isAbsolute, join } from "node:path"; import type { UniDeskConfig } from "./config"; import { rootPath } from "./config"; import type { RenderedCliResult } from "./output"; import { capture, parseJsonOutput } from "./platform-infra-public-service"; import { asRecord, integerField, readYamlRecord, recordField, stringField } from "./platform-infra-ops-library"; const configPath = rootPath("config", "unidesk-host-k8s.yaml"); const configLabel = "config/unidesk-host-k8s.yaml"; const fieldManager = "unidesk-frontend-auth"; type Action = "plan" | "apply" | "status" | "validate"; interface RuntimeAuthConfig { targetId: string; source: { path: string; format: "linePair"; usernameLine: number; passwordLine: number }; target: { route: string; namespace: string; configMapName: string; usernameKey: string; secretName: string; passwordKey: string; rolloutDeployment: string; }; } interface AuthMaterial { sourcePath: string; sourcePathDisplay: string; sourceExists: boolean; username: string | null; password: string | null; usernameByteCount: number | null; passwordByteCount: number | null; fingerprint: string | null; } interface AuthOptions { confirm: boolean; dryRun: boolean; full: boolean; raw: boolean; } export async function runFrontendAuthCommand(config: UniDeskConfig, args: string[]): Promise | RenderedCliResult> { const [actionRaw = "plan", ...rest] = args; const action = parseAction(actionRaw); const options = parseOptions(rest); if (action === "plan") { const result = plan(); return options.full || options.raw ? result : renderPlan(result); } if (action === "apply") { const result = await apply(config, options); return options.full || options.raw ? result : renderApply(result); } if (action === "status") { const result = await status(config, options); return options.full || options.raw ? result : renderStatus(result); } const result = await validate(config, options); return options.full || options.raw ? result : renderValidate(result); } function plan(): Record { const auth = readRuntimeAuthConfig(); const material = readAuthMaterial(auth); return { ok: material.sourceExists && material.username !== null && material.password !== null, action: "frontend-auth-plan", mutation: false, config: configSummary(auth), localSource: materialSummary(auth, material), target: targetSummary(auth), policy: { sourceAuthority: `${configLabel}.runtimeAuth.sourceRef`, runtimeReverseEngineering: false, valuesPrinted: false, }, next: { apply: "bun scripts/cli.ts frontend auth apply --confirm", status: "bun scripts/cli.ts frontend auth status", validate: "bun scripts/cli.ts frontend auth validate", }, valuesPrinted: false, }; } async function apply(config: UniDeskConfig, options: AuthOptions): Promise> { const auth = readRuntimeAuthConfig(); const material = readAuthMaterial(auth); const base = { action: "frontend-auth-apply", config: configSummary(auth), localSource: materialSummary(auth, material), target: targetSummary(auth), valuesPrinted: false, }; if (!material.sourceExists || material.username === null || material.password === null || material.fingerprint === null) { return { ok: false, ...base, mutation: false, mode: "blocked-local-source" }; } if (!options.confirm || options.dryRun) { return { ok: true, ...base, mutation: false, mode: "dry-run" }; } const remote = await capture(config, auth.target.route, ["sh"], applyScript(auth, material)); const parsed = parseJsonOutput(remote.stdout); const runtime = await status(config, { ...options, raw: false }); return { ok: remote.exitCode === 0 && parsed?.ok === true && runtime.ok === true, ...base, mutation: true, mode: "confirmed", remote: remoteSummary(remote), summary: parsed ?? { ok: false, parseError: "remote-json-missing", valuesPrinted: false }, runtime: runtime.summary, }; } async function status(config: UniDeskConfig, options: AuthOptions): Promise> { const auth = readRuntimeAuthConfig(); const material = readAuthMaterial(auth); const remote = await capture(config, auth.target.route, ["sh"], statusScript(auth, material.fingerprint)); const parsed = parseJsonOutput(remote.stdout); return { ok: remote.exitCode === 0 && parsed?.ok === true, action: "frontend-auth-status", mutation: false, config: configSummary(auth), localSource: materialSummary(auth, material), target: targetSummary(auth), remote: remoteSummary(remote), summary: parsed ?? { ok: false, parseError: "remote-json-missing", valuesPrinted: false }, rawCaptureOmitted: options.raw ? true : undefined, valuesPrinted: false, }; } async function validate(config: UniDeskConfig, options: AuthOptions): Promise> { const planned = plan(); const state = await status(config, options); const localOk = planned.ok === true; const runtimeOk = state.ok === true; return { ok: localOk && runtimeOk, action: "frontend-auth-validate", mutation: false, checks: [ { name: "local-source", ok: localOk, detail: configLabel }, { name: "runtime-target", ok: runtimeOk, detail: `${targetSummary(readRuntimeAuthConfig()).namespace}/${targetSummary(readRuntimeAuthConfig()).secretName}` }, ], localSource: planned.localSource, runtime: state.summary, valuesPrinted: false, }; } function readRuntimeAuthConfig(): RuntimeAuthConfig { const root = readYamlRecord(configPath, "UniDeskHostK8sDeployment"); const target = recordField(root, "target", configLabel); const runtimeAuth = recordField(root, "runtimeAuth", configLabel); const source = recordField(runtimeAuth, "sourceRef", `${configLabel}.runtimeAuth`); const targetAuth = recordField(runtimeAuth, "target", `${configLabel}.runtimeAuth`); const format = stringField(source, "format", `${configLabel}.runtimeAuth.sourceRef`); if (format !== "linePair") throw new Error(`${configLabel}.runtimeAuth.sourceRef.format must be linePair`); return { targetId: stringField(target, "id", `${configLabel}.target`), source: { path: stringField(source, "path", `${configLabel}.runtimeAuth.sourceRef`), format, usernameLine: integerField(source, "usernameLine", `${configLabel}.runtimeAuth.sourceRef`), passwordLine: integerField(source, "passwordLine", `${configLabel}.runtimeAuth.sourceRef`), }, target: { route: stringField(targetAuth, "route", `${configLabel}.runtimeAuth.target`), namespace: stringField(targetAuth, "namespace", `${configLabel}.runtimeAuth.target`), configMapName: stringField(targetAuth, "configMapName", `${configLabel}.runtimeAuth.target`), usernameKey: stringField(targetAuth, "usernameKey", `${configLabel}.runtimeAuth.target`), secretName: stringField(targetAuth, "secretName", `${configLabel}.runtimeAuth.target`), passwordKey: stringField(targetAuth, "passwordKey", `${configLabel}.runtimeAuth.target`), rolloutDeployment: stringField(targetAuth, "rolloutDeployment", `${configLabel}.runtimeAuth.target`), }, }; } function readAuthMaterial(auth: RuntimeAuthConfig): AuthMaterial { const sourcePath = resolveSourcePath(auth.source.path); const sourcePathDisplay = displayPath(sourcePath); if (!existsSync(sourcePath)) { return { sourcePath, sourcePathDisplay, sourceExists: false, username: null, password: null, usernameByteCount: null, passwordByteCount: null, fingerprint: null }; } const lines = readFileSync(sourcePath, "utf8").split(/\r?\n/u); const username = lines[auth.source.usernameLine - 1]?.trim() || null; const password = lines[auth.source.passwordLine - 1]?.trim() || null; return { sourcePath, sourcePathDisplay, sourceExists: true, username, password, usernameByteCount: username === null ? null : Buffer.byteLength(username, "utf8"), passwordByteCount: password === null ? null : Buffer.byteLength(password, "utf8"), fingerprint: username === null || password === null ? null : fingerprint([username, password]), }; } function applyScript(auth: RuntimeAuthConfig, material: AuthMaterial): string { const manifest = renderManifest(auth, material); const manifestB64 = Buffer.from(manifest, "utf8").toString("base64"); const summaryB64 = Buffer.from(JSON.stringify({ namespace: auth.target.namespace, configMapName: auth.target.configMapName, usernameKey: auth.target.usernameKey, secretName: auth.target.secretName, passwordKey: auth.target.passwordKey, rolloutDeployment: auth.target.rolloutDeployment, sourceRef: auth.source.path, fingerprint: material.fingerprint, valuesPrinted: false, }), "utf8").toString("base64"); return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT manifest="$tmp/frontend-auth.yaml" printf '%s' '${manifestB64}' | base64 -d >"$manifest" kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f "$manifest" >"$tmp/apply.out" 2>"$tmp/apply.err" apply_rc=$? if [ "$apply_rc" -eq 0 ]; then kubectl -n ${sh(auth.target.namespace)} rollout restart deployment/${sh(auth.target.rolloutDeployment)} >"$tmp/rollout.out" 2>"$tmp/rollout.err" rollout_rc=$? else : >"$tmp/rollout.out" printf '%s\\n' 'skipped because apply failed' >"$tmp/rollout.err" rollout_rc=1 fi python3 - "$apply_rc" "$rollout_rc" "$tmp/apply.out" "$tmp/apply.err" "$tmp/rollout.out" "$tmp/rollout.err" <<'PY' import base64, json, sys apply_rc, rollout_rc = int(sys.argv[1]), int(sys.argv[2]) def tail(path, limit=4000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" summary = json.loads(base64.b64decode("${summaryB64}").decode("utf-8")) payload = { "ok": apply_rc == 0 and rollout_rc == 0, **summary, "steps": { "apply": {"exitCode": apply_rc, "stdoutTail": tail(sys.argv[3]), "stderrTail": tail(sys.argv[4])}, "rolloutRestart": {"exitCode": rollout_rc, "stdoutTail": tail(sys.argv[5]), "stderrTail": tail(sys.argv[6])}, }, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False)) sys.exit(0 if payload["ok"] else 1) PY `; } function statusScript(auth: RuntimeAuthConfig, expectedFingerprint: string | null): string { const expected = expectedFingerprint ?? ""; return ` set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT kubectl -n ${sh(auth.target.namespace)} get configmap ${sh(auth.target.configMapName)} -o json >"$tmp/cm.json" 2>"$tmp/cm.err" cm_rc=$? kubectl -n ${sh(auth.target.namespace)} get secret ${sh(auth.target.secretName)} -o json >"$tmp/secret.json" 2>"$tmp/secret.err" secret_rc=$? kubectl -n ${sh(auth.target.namespace)} get deployment ${sh(auth.target.rolloutDeployment)} -o json >"$tmp/deploy.json" 2>"$tmp/deploy.err" deploy_rc=$? python3 - "$tmp" "$cm_rc" "$secret_rc" "$deploy_rc" <<'PY' import json, os, sys tmp, cm_rc, secret_rc, deploy_rc = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4]) def load(name): try: return json.load(open(os.path.join(tmp, name), encoding="utf-8")) except Exception: return {} cm = load("cm.json") secret = load("secret.json") deploy = load("deploy.json") cm_ann = ((cm.get("metadata") or {}).get("annotations") or {}) secret_ann = ((secret.get("metadata") or {}).get("annotations") or {}) cm_data = cm.get("data") or {} secret_data = secret.get("data") or {} desired = "${expected}" observed_cm_fp = cm_ann.get("unidesk.ai/frontend-auth-fingerprint") observed_secret_fp = secret_ann.get("unidesk.ai/frontend-auth-fingerprint") available = int(((deploy.get("status") or {}).get("availableReplicas") or 0)) replicas = int(((deploy.get("status") or {}).get("replicas") or 0)) checks = [ {"name": "configmap", "ok": cm_rc == 0, "detail": "${auth.target.configMapName}"}, {"name": "username-key", "ok": "${auth.target.usernameKey}" in cm_data, "detail": "${auth.target.usernameKey}"}, {"name": "secret", "ok": secret_rc == 0, "detail": "${auth.target.secretName}"}, {"name": "password-key", "ok": "${auth.target.passwordKey}" in secret_data, "detail": "${auth.target.passwordKey}"}, {"name": "fingerprint", "ok": bool(desired) and observed_cm_fp == desired and observed_secret_fp == desired, "detail": observed_secret_fp or "-"}, {"name": "deployment", "ok": deploy_rc == 0 and replicas > 0 and available >= 1, "detail": "${auth.target.rolloutDeployment}"}, ] payload = { "ok": all(item["ok"] for item in checks), "namespace": "${auth.target.namespace}", "configMapName": "${auth.target.configMapName}", "usernameKey": "${auth.target.usernameKey}", "secretName": "${auth.target.secretName}", "passwordKey": "${auth.target.passwordKey}", "rolloutDeployment": "${auth.target.rolloutDeployment}", "expectedFingerprint": desired or None, "observedFingerprint": observed_secret_fp, "checks": checks, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False)) sys.exit(0 if payload["ok"] else 1) PY `; } function renderManifest(auth: RuntimeAuthConfig, material: AuthMaterial): string { if (material.username === null || material.password === null || material.fingerprint === null) throw new Error("auth material is incomplete"); const annotations = [ ` unidesk.ai/source-ref: ${yaml(auth.source.path)}`, ` unidesk.ai/frontend-auth-fingerprint: ${yaml(material.fingerprint)}`, ].join("\n"); return `apiVersion: v1 kind: ConfigMap metadata: name: ${auth.target.configMapName} namespace: ${auth.target.namespace} annotations: ${annotations} data: ${auth.target.usernameKey}: ${yaml(material.username)} --- apiVersion: v1 kind: Secret metadata: name: ${auth.target.secretName} namespace: ${auth.target.namespace} annotations: ${annotations} type: Opaque stringData: ${auth.target.passwordKey}: ${yaml(material.password)} `; } function parseAction(value: string): Action { if (value === "plan" || value === "apply" || value === "status" || value === "validate") return value; throw new Error(`frontend auth action must be plan, apply, status, or validate; got ${value}`); } function parseOptions(args: string[]): AuthOptions { let confirm = false; let dryRun = false; let full = false; let raw = false; for (const arg of args) { if (arg === "--confirm") confirm = true; else if (arg === "--dry-run") dryRun = true; else if (arg === "--full") full = true; else if (arg === "--raw") { raw = true; full = true; } else { throw new Error(`unsupported frontend auth option: ${arg}`); } } if (confirm && dryRun) throw new Error("frontend auth apply accepts only one of --confirm or --dry-run"); return { confirm, dryRun, full, raw }; } function configSummary(auth: RuntimeAuthConfig): Record { return { path: configLabel, targetId: auth.targetId, sourceRef: auth.source.path, format: auth.source.format, usernameLine: auth.source.usernameLine, passwordLine: auth.source.passwordLine, valuesPrinted: false, }; } function materialSummary(auth: RuntimeAuthConfig, material: AuthMaterial): Record { return { sourceRef: auth.source.path, sourcePath: material.sourcePathDisplay, exists: material.sourceExists, requiredLines: { username: auth.source.usernameLine, password: auth.source.passwordLine }, byteCounts: { username: material.usernameByteCount, password: material.passwordByteCount }, fingerprint: material.fingerprint, valuesPrinted: false, }; } function targetSummary(auth: RuntimeAuthConfig): Record { return { ...auth.target, valuesPrinted: false }; } function remoteSummary(result: { exitCode: number; stdout: string; stderr: string }): Record { return { exitCode: result.exitCode, stdoutBytes: Buffer.byteLength(result.stdout, "utf8"), stderrBytes: Buffer.byteLength(result.stderr, "utf8"), stdoutTailOmitted: true, stderrTailOmitted: true, valuesPrinted: false, }; } function renderPlan(result: Record): RenderedCliResult { const source = asRecord(result.localSource, "localSource"); const target = asRecord(result.target, "target"); return rendered(result, "frontend auth plan", [ "FRONTEND AUTH PLAN", ...table(["CHECK", "OK", "DETAIL"], [ ["source", bool(source.exists), String(source.sourceRef ?? "-")], ["line-pair", bool(source.fingerprint !== undefined && source.fingerprint !== null), `userLine=${source.requiredLines && asRecord(source.requiredLines, "requiredLines").username} passwordLine=${source.requiredLines && asRecord(source.requiredLines, "requiredLines").password}`], ["target", "yes", `${target.namespace}/${target.secretName}`], ]), "", `fingerprint: ${str(source.fingerprint)}`, `valuesPrinted: ${str(result.valuesPrinted)}`, "Next:", " bun scripts/cli.ts frontend auth apply --confirm", ]); } function renderApply(result: Record): RenderedCliResult { const summary = result.summary === undefined ? {} : asRecord(result.summary, "summary"); const steps = summary.steps === undefined ? {} : asRecord(summary.steps, "summary.steps"); if (Object.keys(summary).length === 0 || Object.keys(steps).length === 0) { const source = asRecord(result.localSource, "localSource"); return rendered(result, "frontend auth apply", [ "FRONTEND AUTH APPLY", `mode: ${str(result.mode)} mutation: ${str(result.mutation)} ok: ${bool(result.ok)}`, ...table(["CHECK", "OK", "DETAIL"], [ ["local-source", bool(source.fingerprint !== undefined && source.fingerprint !== null), str(source.sourceRef)], ["target", "yes", `${str(asRecord(result.target, "target").namespace)}/${str(asRecord(result.target, "target").secretName)}`], ]), `valuesPrinted: ${str(result.valuesPrinted)}`, ]); } return rendered(result, "frontend auth apply", [ "FRONTEND AUTH APPLY", `mode: ${str(result.mode)} mutation: ${str(result.mutation)} ok: ${bool(result.ok)}`, ...table(["STEP", "OK", "DETAIL"], [ ["local-source", bool(asRecord(result.localSource, "localSource").fingerprint !== undefined && asRecord(result.localSource, "localSource").fingerprint !== null), str(asRecord(result.localSource, "localSource").sourceRef)], ["apply", bool(asRecord(steps.apply, "steps.apply").exitCode === 0), compact(str(asRecord(steps.apply, "steps.apply").stderrTail, str(asRecord(steps.apply, "steps.apply").stdoutTail)))], ["rollout-restart", bool(asRecord(steps.rolloutRestart, "steps.rolloutRestart").exitCode === 0), compact(str(asRecord(steps.rolloutRestart, "steps.rolloutRestart").stdoutTail, str(asRecord(steps.rolloutRestart, "steps.rolloutRestart").stderrTail)))], ["runtime-status", bool(asRecord(result.runtime, "runtime").ok), str(asRecord(result.runtime, "runtime").observedFingerprint)], ]), `valuesPrinted: ${str(result.valuesPrinted)}`, ]); } function renderStatus(result: Record): RenderedCliResult { const summary = asRecord(result.summary, "summary"); const rows = Array.isArray(summary.checks) ? summary.checks.map((item) => { const row = asRecord(item, "check"); return [str(row.name), bool(row.ok), str(row.detail)]; }) : []; return rendered(result, "frontend auth status", [ "FRONTEND AUTH STATUS", ...table(["CHECK", "OK", "DETAIL"], rows), `fingerprint: ${str(summary.observedFingerprint)}`, `valuesPrinted: ${str(result.valuesPrinted)}`, ]); } function renderValidate(result: Record): RenderedCliResult { const rows = Array.isArray(result.checks) ? result.checks.map((item) => { const row = asRecord(item, "check"); return [str(row.name), bool(row.ok), str(row.detail)]; }) : []; return rendered(result, "frontend auth validate", [ "FRONTEND AUTH VALIDATE", ...table(["CHECK", "OK", "DETAIL"], rows), `valuesPrinted: ${str(result.valuesPrinted)}`, ]); } function rendered(result: Record, command: string, lines: string[]): RenderedCliResult { return { ok: result.ok !== false, command, renderedText: lines.join("\n"), contentType: "text/plain" }; } function resolveSourcePath(path: string): string { return isAbsolute(path) ? path : rootPath(path); } function displayPath(path: string): string { const root = rootPath(); return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path; } function fingerprint(parts: string[]): string { return createHash("sha256").update(parts.join("\n")).digest("hex").slice(0, 16); } function yaml(value: string): string { return JSON.stringify(value); } function sh(value: string): string { return `'${value.replaceAll("'", "'\"'\"'")}'`; } function str(value: unknown, fallback = "-"): string { return value === undefined || value === null || value === "" ? fallback : String(value); } function bool(value: unknown): string { return value === true ? "yes" : value === false ? "no" : "-"; } function compact(value: string): string { return value.replace(/\s+/gu, " ").trim().slice(0, 120) || "-"; } function table(headers: string[], rows: string[][]): string[] { const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0))); const renderRow = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index] ?? cell.length)).join(" ").trimEnd(); return [renderRow(headers), ...rows.map(renderRow)]; }