import { createHash } from "node:crypto"; import type { UniDeskConfig } from "./config"; import { applyPk01CaddyBlock, capture, compactCapture, parseJsonOutput, publicDnsProbe, publicHttpProbe, publicServicePolicyChecks, publicServiceStatusScript, renderEnvSecretFrpcManifest, type PublicServiceRuntimeResources, } from "./platform-infra-public-service"; import { parseOpsApplyOptions, parseOpsCommonOptions, type OpsApplyOptions, type OpsCommonOptions } from "./platform-infra-ops-library"; import { readSub2RankConfig, resolveSub2RankTarget, sub2RankConfigLabel, type Sub2RankConfig, type Sub2RankTarget } from "./platform-infra-sub2rank-config"; const serviceId = "sub2rank"; export const sub2RankImagePlaceholder = "__SUB2RANK_IMAGE_REF__"; export const sub2RankConfigBase64Placeholder = "__SUB2RANK_CONFIG_BASE64__"; export const sub2RankConfigSha256Placeholder = "__SUB2RANK_CONFIG_SHA256__"; export const sub2RankSourceCommitPlaceholder = "__SUB2RANK_SOURCE_COMMIT__"; export function sub2RankHelp(): Record { return { command: "platform-infra sub2rank plan|public-exposure|status|validate", output: "json", usage: [ "bun scripts/cli.ts platform-infra sub2rank plan [--target NC01]", "bun scripts/cli.ts platform-infra sub2rank public-exposure [--target NC01] --dry-run", "bun scripts/cli.ts platform-infra sub2rank public-exposure [--target NC01] --confirm", "bun scripts/cli.ts platform-infra sub2rank status [--target NC01] [--full|--raw]", "bun scripts/cli.ts platform-infra sub2rank validate [--target NC01] [--full|--raw]", ], configTruth: sub2RankConfigLabel, applicationConfigRef: "/root/sub2rank/config/sub2rank.yaml", artifactPolicy: "Application PR merge is the sole delivery trigger; PaC/Tekton builds the image and publishes a digest-pinned GitOps manifest.", secretPolicy: "Resolves the existing Secret declaration from config/secrets-distribution.yaml and never reads or prints runtime Secret values.", }; } export async function runSub2RankCommand(config: UniDeskConfig, args: string[]): Promise> { const [action = "plan"] = args; if (action === "plan") return plan(parseOpsCommonOptions(args.slice(1))); if (action === "public-exposure") return await publicExposure(config, parseOpsApplyOptions(args.slice(1))); if (action === "status") return await status(config, parseOpsCommonOptions(args.slice(1))); if (action === "validate") return await validate(config, parseOpsCommonOptions(args.slice(1))); return { ok: false, error: "unsupported-platform-infra-sub2rank-command", args, help: sub2RankHelp() }; } function plan(options: OpsCommonOptions): Record { const sub2rank = readSub2RankConfig(); const target = resolveSub2RankTarget(sub2rank, options.targetId); const yaml = renderSub2RankManifest(sub2rank, target); const policy = policyChecks(sub2rank, target, yaml); return { ok: policy.every((item) => item.ok), action: "platform-infra-sub2rank-plan", mutation: false, warnings: sub2rank.warnings, config: configSummary(sub2rank, target), policy, artifact: { imageRepository: sub2rank.image.repository, buildDisposition: "pac-tekton-on-application-pr-merge", sourceRoot: sub2rank.application.sourceRoot, sourceCommit: sub2rank.application.sourceCommit, sourceClean: sub2rank.application.sourceClean, sourceRemotePresent: sub2rank.application.sourceRemotePresent, sourceRemoteMatches: sub2rank.application.sourceRemoteMatches, publication: { supported: sub2rank.delivery.enabled, authority: "GitHub PR merge -> Gitea snapshot -> PaC -> Tekton -> GitOps -> Argo", branch: sub2rank.application.branch, pipeline: sub2rank.delivery.pipeline.name, }, }, topology: `client -> PK01 Caddy -> PK01 frps:${target.publicExposure.frpc.remotePort} -> ${target.id} frpc -> ${sub2rank.runtime.service.name}.${target.namespace}.svc.cluster.local:${sub2rank.runtime.service.port}`, next: { secrets: "bun scripts/cli.ts secrets sync --config config/secrets-distribution.yaml --scope sub2rank --confirm", publicExposureDryRun: `bun scripts/cli.ts platform-infra sub2rank public-exposure --target ${target.id} --dry-run`, publicExposureApply: `bun scripts/cli.ts platform-infra sub2rank public-exposure --target ${target.id} --confirm`, status: `bun scripts/cli.ts platform-infra sub2rank status --target ${target.id}`, validate: `bun scripts/cli.ts platform-infra sub2rank validate --target ${target.id}`, }, }; } async function publicExposure(config: UniDeskConfig, options: OpsApplyOptions): Promise> { const sub2rank = readSub2RankConfig(); const target = resolveSub2RankTarget(sub2rank, options.targetId); if (options.dryRun) { return { ok: true, action: "platform-infra-sub2rank-public-exposure", mode: "dry-run", mutation: false, target: targetSummary(target), exposure: { publicBaseUrl: target.publicExposure.publicBaseUrl, upstream: `127.0.0.1:${target.publicExposure.frpc.remotePort}`, managedBlock: serviceId, }, next: `bun scripts/cli.ts platform-infra sub2rank public-exposure --target ${target.id} --confirm`, }; } const caddy = await applyPk01CaddyBlock(config, serviceId, target.publicExposure); return { ok: caddy.ok === true, action: "platform-infra-sub2rank-public-exposure", mode: "confirmed", mutation: true, target: targetSummary(target), pk01Caddy: caddy, next: `Merge the application PR only after the PaC repository and source snapshot chain are ready.`, }; } async function status(config: UniDeskConfig, options: OpsCommonOptions): Promise> { const sub2rank = readSub2RankConfig(); const target = resolveSub2RankTarget(sub2rank, options.targetId); const resources = runtimeResources(sub2rank, target); const remote = await capture(config, target.route, ["sh"], publicServiceStatusScript({ target, resources, healthPath: sub2rank.runtime.probes.healthPath, servicePort: sub2rank.runtime.service.port, })); const parsed = parseJsonOutput(remote.stdout); return { ok: remote.exitCode === 0 && parsed?.ok === true, action: "platform-infra-sub2rank-status", mutation: false, target: targetSummary(target), configRefs: configRefsSummary(sub2rank), summary: parsed, remote: compactCapture(remote, { full: options.full || remote.exitCode !== 0 }), ...(options.raw ? { raw: remote } : {}), }; } async function validate(config: UniDeskConfig, options: OpsCommonOptions): Promise> { const sub2rank = readSub2RankConfig(); const target = resolveSub2RankTarget(sub2rank, options.targetId); const runtime = await status(config, options); const dns = await publicDnsProbe(target.publicExposure.dns); const publicHttps = publicHttpProbe(target.publicExposure.publicBaseUrl, sub2rank.runtime.probes.healthPath); return { ok: runtime.ok === true && dns.ok === true && publicHttps.ok === true, action: "platform-infra-sub2rank-validate", mutation: false, target: targetSummary(target), runtime, dns, publicHttps, automaticCredit: { enabled: sub2rank.application.automaticCreditEnabled, source: sub2rank.application.configRef }, }; } export interface Sub2RankManifestRenderOptions { imageRef?: string; appConfigBase64?: string; appConfigSha256?: string; sourceCommit?: string; } export function renderSub2RankManifest(config: Sub2RankConfig, target: Sub2RankTarget, options: Sub2RankManifestRenderOptions = {}): string { const runtime = config.runtime; const image = options.imageRef ?? `${config.image.repository}:pac-preview`; const appConfigBase64 = options.appConfigBase64 ?? Buffer.from(config.application.configText, "utf8").toString("base64"); const appConfigSha256 = options.appConfigSha256 ?? config.application.configSha256; const sourceCommit = options.sourceCommit ?? config.application.sourceCommit; const configHash = createHash("sha256").update(JSON.stringify({ deployment: deploymentHashInput(config), target })).digest("hex").slice(0, 16); const appEnv = runtime.secret.appEnvTargetKeys.map((key) => ` - name: ${key}\n valueFrom:\n secretKeyRef:\n name: ${runtime.secret.secretName}\n key: ${key}`).join("\n"); const command = runtime.command.map((value) => ` - ${yamlQuoted(value)}`).join("\n"); return `apiVersion: v1 kind: ConfigMap metadata: name: ${runtime.configMap.name} namespace: ${target.namespace} labels: app.kubernetes.io/name: ${runtime.service.name} app.kubernetes.io/part-of: platform-infra app.kubernetes.io/managed-by: unidesk binaryData: ${runtime.configMap.key}: ${yamlQuoted(appConfigBase64)} --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ${runtime.storage.claimName} namespace: ${target.namespace} labels: app.kubernetes.io/name: ${runtime.service.name} app.kubernetes.io/part-of: platform-infra app.kubernetes.io/managed-by: unidesk spec: accessModes: - ReadWriteOnce resources: requests: storage: ${runtime.storage.size} --- apiVersion: v1 kind: Service metadata: name: ${runtime.service.name} namespace: ${target.namespace} labels: app.kubernetes.io/name: ${runtime.service.name} app.kubernetes.io/part-of: platform-infra app.kubernetes.io/managed-by: unidesk spec: type: ClusterIP selector: app.kubernetes.io/name: ${runtime.service.name} ports: - name: http port: ${runtime.service.port} targetPort: http --- apiVersion: apps/v1 kind: Deployment metadata: name: ${runtime.service.name} namespace: ${target.namespace} labels: app.kubernetes.io/name: ${runtime.service.name} app.kubernetes.io/part-of: platform-infra app.kubernetes.io/managed-by: unidesk spec: replicas: ${target.replicas} strategy: type: Recreate selector: matchLabels: app.kubernetes.io/name: ${runtime.service.name} template: metadata: labels: app.kubernetes.io/name: ${runtime.service.name} app.kubernetes.io/part-of: platform-infra annotations: unidesk.ai/sub2rank-config-sha256: "${appConfigSha256}" unidesk.ai/sub2rank-deployment-hash: "${configHash}" unidesk.ai/source-commit: "${sourceCommit}" unidesk.ai/public-base-url: "${target.publicExposure.publicBaseUrl}" spec: securityContext: fsGroup: 1000 containers: - name: ${runtime.service.name} image: ${yamlQuoted(image)} imagePullPolicy: ${config.image.pullPolicy} command: ${command} ports: - name: http containerPort: ${runtime.service.port} env: ${appEnv} readinessProbe: httpGet: path: ${runtime.probes.healthPath} port: http initialDelaySeconds: ${runtime.probes.initialDelaySeconds} periodSeconds: ${runtime.probes.periodSeconds} timeoutSeconds: ${runtime.probes.timeoutSeconds} failureThreshold: ${runtime.probes.failureThreshold} livenessProbe: httpGet: path: ${runtime.probes.healthPath} port: http initialDelaySeconds: ${runtime.probes.initialDelaySeconds} periodSeconds: ${runtime.probes.periodSeconds} timeoutSeconds: ${runtime.probes.timeoutSeconds} failureThreshold: ${runtime.probes.failureThreshold} volumeMounts: - name: app-config mountPath: ${runtime.configMap.mountPath} subPath: ${runtime.configMap.key} readOnly: true - name: data mountPath: ${runtime.storage.mountPath} volumes: - name: app-config configMap: name: ${runtime.configMap.name} - name: data persistentVolumeClaim: claimName: ${runtime.storage.claimName} ${renderEnvSecretFrpcManifest(target, { configMapName: target.publicExposure.frpc.configMapName, configKey: target.publicExposure.frpc.configKey, tokenSecretName: runtime.secret.secretName, tokenSecretKey: target.publicExposure.frpc.tokenTargetKey, tokenEnvName: target.publicExposure.frpc.tokenEnvName, })}`; } function policyChecks(config: Sub2RankConfig, target: Sub2RankTarget, yaml: string): Array<{ name: string; ok: boolean; detail: string }> { return [ ...publicServicePolicyChecks(yaml, target, serviceId, { requireAllowAllManifest: false }).map((item) => ({ name: String(item.name), ok: item.ok === true, detail: String(item.detail) })), { name: "shared-runtime-resources-not-owned", ok: !/^\s*kind:\s*(?:Namespace|NetworkPolicy)\s*$/mu.test(yaml) && config.runtime.sharedNetworkPolicyRef.managedBySub2Rank === false, detail: `Sub2Rank references ${target.namespace}/NetworkPolicy/${config.runtime.sharedNetworkPolicyRef.name} but does not apply or seize field ownership of shared runtime resources.`, }, { name: "existing-secret-source-ref", ok: config.runtime.secret.requiredTargetKeys.every((key) => config.runtime.secret.mappings.some((mapping) => mapping.targetKey === key)), detail: `Runtime keys resolve through ${config.runtime.secret.configRef} declaration ${config.runtime.secret.declarationId}; valuesPrinted=false.`, }, ]; } function configSummary(config: Sub2RankConfig, target: Sub2RankTarget): Record { return { configRefs: configRefsSummary(config), target: targetSummary(target), image: { repository: config.image.repository, pullPolicy: config.image.pullPolicy, authority: "PaC digest pin" }, delivery: { pipeline: config.delivery.pipeline.name, gitopsBranch: config.delivery.gitops.branch, gitopsManifestPath: config.delivery.gitops.manifestPath, argoApplication: config.delivery.gitops.application.name, }, runtime: { service: `${config.runtime.service.name}.${target.namespace}.svc.cluster.local:${config.runtime.service.port}`, storage: { claimName: config.runtime.storage.claimName, size: config.runtime.storage.size, mountPath: config.runtime.storage.mountPath }, secret: secretSummary(config), }, publicExposure: { publicBaseUrl: target.publicExposure.publicBaseUrl, expectedA: target.publicExposure.dns.expectedA, remotePort: target.publicExposure.frpc.remotePort, marker: serviceId, }, automaticCredit: { enabled: config.application.automaticCreditEnabled, source: config.application.configRef }, }; } function configRefsSummary(config: Sub2RankConfig): Record { return { warnings: config.warnings, deployment: { path: sub2RankConfigLabel, kind: config.kind }, application: { path: config.application.configRef, sha256: config.application.configSha256, deploymentBlockPresent: config.application.deploymentBlockPresent, sourceCommit: config.application.sourceCommit, sourceClean: config.application.sourceClean, sourceRemotePresent: config.application.sourceRemotePresent, sourceRemoteMatches: config.application.sourceRemoteMatches, sourceConfigPathMatches: config.application.sourceConfigPathMatches, configMatchesSourceCommit: config.application.configMatchesSourceCommit, }, secret: { path: config.runtime.secret.configRef, declarationId: config.runtime.secret.declarationId }, }; } function secretSummary(config: Sub2RankConfig): Record { return { name: config.runtime.secret.secretName, declarationId: config.runtime.secret.declarationId, mappings: config.runtime.secret.mappings.map((item) => ({ sourceRef: item.sourceRef, sourceKey: item.sourceKey, targetKey: item.targetKey })), requiredTargetKeys: config.runtime.secret.requiredTargetKeys, valuesPrinted: false, }; } function targetSummary(target: Sub2RankTarget): Record { return { id: target.id, route: target.route, namespace: target.namespace, replicas: target.replicas, publicBaseUrl: target.publicExposure.publicBaseUrl, }; } function runtimeResources(config: Sub2RankConfig, target: Sub2RankTarget): PublicServiceRuntimeResources { return { appDeploymentName: config.runtime.service.name, serviceName: config.runtime.service.name, persistentVolumeClaimName: config.runtime.storage.claimName, configMapName: config.runtime.configMap.name, secretName: config.runtime.secret.secretName, requiredSecretKeys: config.runtime.secret.requiredTargetKeys, sharedNetworkPolicyName: config.runtime.sharedNetworkPolicyRef.name, }; } function deploymentHashInput(config: Sub2RankConfig): Record { return { application: { repository: config.application.repository, branch: config.application.branch, sourceConfigPath: config.application.sourceConfigPath, dockerfile: config.application.dockerfile, runtimeTarget: config.application.runtimeTarget, }, image: config.image, runtime: config.runtime, }; } function yamlQuoted(value: string): string { return JSON.stringify(value); }