Files
pikasTech-unidesk/scripts/src/artifact-registry.ts
T
2026-05-23 16:21:45 +00:00

3466 lines
164 KiB
TypeScript

import { createHash } from "node:crypto";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { runCommand, type CommandResult } from "./command";
import { readConfig, type UniDeskConfig, repoRoot, rootPath } from "./config";
import { startJob } from "./jobs";
import {
compareDeployJsonExecutorMirrors,
deployJsonCommitImage,
deployJsonDriftResult,
deployJsonSourceOfTruth,
hasDeployJsonExecutorContract,
k3sManifestExecutorMirror,
parseDeployJsonServiceContractBase64,
readDeployJsonServiceContractFromFile,
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "./deploy-json-contract";
import { d601K3sGuardShellLines } from "./d601-k3s-guard";
export type ArtifactRegistryAction = "plan" | "render" | "status" | "health" | "install" | "deploy-backend-core" | "deploy-service";
type ArtifactDeployEnvironment = "prod" | "dev";
export interface ArtifactRegistryOptions {
environment: ArtifactDeployEnvironment | null;
providerId: string;
host: string;
port: number;
image: string;
baseDir: string;
storageDir: string;
unitName: string;
composeProject: string;
serviceName: string;
containerName: string;
timeoutMs: number;
dryRun: boolean;
runNow: boolean;
commit: string | null;
targetImage: string | null;
serviceId: string | null;
sourceRepo: string;
sourceRepoExplicit: boolean;
deployRef: string | null;
deployJsonService: DeployJsonServiceContract | null;
}
export interface ArtifactRegistryCommandRuntime {
env?: NodeJS.ProcessEnv;
runRemoteScriptForTest?: (options: ArtifactRegistryOptions, script: string, timeoutMs: number) => CommandResult;
runCliForTest?: (command: string[], timeoutMs: number) => CommandResult;
}
interface RenderedFile {
path: string;
mode: string;
sha256: string;
content: string;
}
interface RenderedBundle {
files: RenderedFile[];
paths: {
unit: string;
compose: string;
config: string;
storage: string;
baseDir: string;
};
}
export interface ArtifactRegistryReadonlyProbe {
action: "status" | "health";
providerId: string;
script: string;
timeoutMs: number;
healthMode: boolean;
options: ArtifactRegistryOptions;
remoteCommandShape: string;
}
export type ArtifactRegistryFailureClassification =
| "control-plane-missing"
| "local-docker-required"
| "provider-ssh-command-missing"
| "ssh-helper-command-shape-incompatible"
| "remote-command-timeout"
| "registry-not-installed"
| "registry-unhealthy";
const defaultOptions: ArtifactRegistryOptions = {
environment: null,
providerId: "D601",
host: "127.0.0.1",
port: 5000,
image: "registry:2.8.3",
baseDir: "/home/ubuntu/.unidesk/artifact-registry",
storageDir: "/home/ubuntu/.unidesk/registry-storage",
unitName: "unidesk-artifact-registry.service",
composeProject: "unidesk-artifact-registry",
serviceName: "registry",
containerName: "unidesk-artifact-registry",
timeoutMs: 30_000,
dryRun: false,
runNow: false,
commit: null,
targetImage: null,
serviceId: null,
sourceRepo: "https://github.com/pikasTech/unidesk",
sourceRepoExplicit: false,
deployRef: null,
deployJsonService: null,
};
const supportedArtifactConsumerServices = [
"auth-broker",
"backend-core",
"baidu-netdisk",
"claudeqq",
"code-queue",
"code-queue-mgr",
"decision-center",
"findjob",
"frontend",
"k3sctl-adapter",
"mdtodo",
"met-nonlinear",
"oa-event-flow",
"pipeline",
"project-manager",
"todo-note",
] as const;
type SupportedArtifactConsumerService = typeof supportedArtifactConsumerServices[number];
const legacyDeployBackendCoreDisabled = true;
interface ArtifactConsumerSpec {
serviceId: SupportedArtifactConsumerService;
environment?: ArtifactDeployEnvironment;
kind: "compose" | "d601-compose" | "d601-k3s";
registryRepository: string;
sourceRepo?: string;
dockerfile: string;
targets: Partial<Record<ArtifactDeployEnvironment, ArtifactConsumerTarget>>;
prodLiveApply: "enabled" | "supervisor-only" | "unsupported";
prodLiveBlockReason?: string;
devLiveApply?: "enabled" | "supervisor-only";
devLiveBlockReason?: string;
runtimeVerification?: "strict" | "blocked";
runtimeVerificationBlockReason?: string;
}
interface ArtifactConsumerTarget {
targetImage: string;
targetCommitImage: (commit: string) => string;
deployRef: string;
compose?: {
serviceName: string;
containerName: string;
deployEnvPrefix: string;
healthProbeCommand: string;
requireHealthCommit: boolean;
workDir?: string;
composeFile?: string;
projectHint?: string;
requiredRuntimeSecrets?: RuntimeSecretRequirement[];
authHealthGate?: AuthHealthGate;
syntheticHealthDeployProof?: SyntheticHealthDeployProof;
};
k3s?: {
namespace: string;
manifestRepoPath: string;
deploymentName: string;
serviceName: string;
servicePort: number;
containerName: string;
healthPath: string;
extraDeployments?: Array<string | { name: string; containerName?: string }>;
applySelector?: string;
podLabelSelector?: string;
};
}
export interface RuntimeSecretRequirement {
sourceEnvName: string;
containerEnvName: string;
}
interface AuthHealthGate {
requiredFields: string[];
recoveryHint: string;
}
interface SyntheticHealthDeployProof {
kind: "compose-container-runtime-metadata";
description: string;
}
interface ComposeArtifactRuntime {
workDir: string;
composeFile: string;
composeEnvFile: string;
envFile: string;
project: string;
command: string[];
}
export interface RuntimeSecretPresence {
sourceEnvName: string;
containerEnvName: string;
present: boolean;
length: number;
}
export interface RuntimeSecretSource {
kind: "compose-env-file";
path: string;
exists: boolean;
configSource: "config.providerGateway.upgrade.hostProjectRoot + config.providerGateway.upgrade.composeEnvFile";
workDir: string;
composeEnvFile: string;
composeService: string | null;
containerName: string | null;
valuesPrinted: false;
}
export interface RuntimeSecretRequirementStatus {
sourceEnvName: string;
containerEnvName: string;
present: boolean;
valuePrinted: false;
}
export interface RuntimeSecretContract {
check: "runtime-secret-presence";
secretSource: RuntimeSecretSource;
requiredSecretsPresent: boolean;
missingSecretKeys: string[];
recommendedAction: string;
valuesPrinted: false;
requirements: RuntimeSecretRequirementStatus[];
dryRunDisposition: "not-required" | "ready-for-live-apply" | "secret-source-blocked";
}
const todoNoteSyntheticHealthDeployProof: SyntheticHealthDeployProof = {
kind: "compose-container-runtime-metadata",
description: "todo-note /api/health does not natively expose deploy metadata, so the artifact consumer synthesizes health.deploy from the recreated container's UNIDESK_DEPLOY_* env and verifies it against image/container labels.",
};
function todoNoteHealthProbeCommand(): string {
return "bun -e \"fetch('http://127.0.0.1:4211/api/health').then(async r=>{const text=await r.text(); let body; try{body=JSON.parse(text)}catch{body={ok:r.ok,raw:text}}; console.log(JSON.stringify(body)); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"";
}
export const baiduNetdiskRuntimeSecretRequirements: RuntimeSecretRequirement[] = [
{ sourceEnvName: "UNIDESK_BAIDU_NETDISK_CLIENT_ID", containerEnvName: "BAIDU_NETDISK_CLIENT_ID" },
{ sourceEnvName: "UNIDESK_BAIDU_NETDISK_CLIENT_SECRET", containerEnvName: "BAIDU_NETDISK_CLIENT_SECRET" },
{ sourceEnvName: "UNIDESK_BAIDU_NETDISK_TOKEN_KEY", containerEnvName: "BAIDU_NETDISK_TOKEN_KEY" },
];
const baiduNetdiskAuthHealthGate: AuthHealthGate = {
requiredFields: ["configured", "clientIdConfigured", "clientSecretConfigured", "tokenKeyConfigured", "loggedIn"],
recoveryHint: "Restore UNIDESK_BAIDU_NETDISK_CLIENT_ID, UNIDESK_BAIDU_NETDISK_CLIENT_SECRET, and UNIDESK_BAIDU_NETDISK_TOKEN_KEY in the canonical .state/docker-compose.env, recreate only baidu-netdisk with the canonical env file, then verify microservice health baidu-netdisk.",
};
const artifactConsumerSpecs: Record<string, ArtifactConsumerSpec> = {
"auth-broker": {
serviceId: "auth-broker",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/auth-broker",
dockerfile: "src/components/microservices/auth-broker/Dockerfile",
prodLiveApply: "supervisor-only",
prodLiveBlockReason: "auth-broker is registered for source/contract/profile dry-run only; live production apply requires a separate credential mounting and exposure review.",
devLiveApply: "supervisor-only",
devLiveBlockReason: "auth-broker DEV live apply requires explicit operator authorization after reviewing credential mounting, private exposure, and dry-run evidence.",
targets: {
dev: {
targetImage: "auth-broker",
targetCommitImage: (commit: string) => `auth-broker:${commit}`,
deployRef: "deploy.json#environments.dev.services.auth-broker",
compose: {
serviceName: "auth-broker",
containerName: "auth-broker-backend",
deployEnvPrefix: "UNIDESK_AUTH_BROKER_DEPLOY",
healthProbeCommand: "auth-broker --healthcheck",
requireHealthCommit: false,
},
},
prod: {
targetImage: "auth-broker",
targetCommitImage: (commit: string) => `auth-broker:${commit}`,
deployRef: "deploy.json#environments.prod.services.auth-broker",
compose: {
serviceName: "auth-broker",
containerName: "auth-broker-backend",
deployEnvPrefix: "UNIDESK_AUTH_BROKER_DEPLOY",
healthProbeCommand: "auth-broker --healthcheck",
requireHealthCommit: false,
},
},
},
},
"backend-core": {
serviceId: "backend-core",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/backend-core",
dockerfile: "src/components/backend-core/Dockerfile",
prodLiveApply: "enabled",
targets: {
prod: {
targetImage: "unidesk-backend-core",
targetCommitImage: (commit: string) => `unidesk-backend-core:${commit}`,
deployRef: "deploy.json#environments.prod.services.backend-core",
compose: {
serviceName: "backend-core",
containerName: "unidesk-backend-core",
deployEnvPrefix: "UNIDESK_DEPLOY",
healthProbeCommand: "if command -v backend-core >/dev/null 2>&1; then backend-core --fetch-json http://127.0.0.1:8080/health --require-ok; elif command -v bun >/dev/null 2>&1; then bun -e \"fetch('http://127.0.0.1:8080/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"; else exit 1; fi",
requireHealthCommit: false,
},
},
},
},
"dev:backend-core": {
serviceId: "backend-core",
environment: "dev",
kind: "d601-k3s",
registryRepository: "unidesk/backend-core",
dockerfile: "src/components/backend-core/Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "unidesk-backend-core:dev",
targetCommitImage: (commit: string) => `unidesk-backend-core:${commit}`,
deployRef: "origin/master:deploy.json#environments.dev.services.backend-core",
k3s: {
namespace: "unidesk-dev",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml",
deploymentName: "backend-core-dev",
serviceName: "backend-core-dev",
servicePort: 8080,
containerName: "backend-core",
healthPath: "/health",
applySelector: "app.kubernetes.io/name=backend-core",
podLabelSelector: "app.kubernetes.io/name=backend-core,app.kubernetes.io/component=core,unidesk.ai/environment=dev",
},
},
},
},
"baidu-netdisk": {
serviceId: "baidu-netdisk",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/baidu-netdisk",
dockerfile: "src/components/microservices/baidu-netdisk/Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "baidu-netdisk",
targetCommitImage: (commit: string) => `baidu-netdisk:${commit}`,
deployRef: "deploy.json#environments.dev.services.baidu-netdisk",
compose: {
serviceName: "baidu-netdisk",
containerName: "baidu-netdisk-backend",
deployEnvPrefix: "UNIDESK_BAIDU_NETDISK_DEPLOY",
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4244/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
requireHealthCommit: true,
requiredRuntimeSecrets: baiduNetdiskRuntimeSecretRequirements,
authHealthGate: baiduNetdiskAuthHealthGate,
},
},
prod: {
targetImage: "baidu-netdisk",
targetCommitImage: (commit: string) => `baidu-netdisk:${commit}`,
deployRef: "deploy.json#environments.prod.services.baidu-netdisk",
compose: {
serviceName: "baidu-netdisk",
containerName: "baidu-netdisk-backend",
deployEnvPrefix: "UNIDESK_BAIDU_NETDISK_DEPLOY",
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4244/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
requireHealthCommit: true,
requiredRuntimeSecrets: baiduNetdiskRuntimeSecretRequirements,
authHealthGate: baiduNetdiskAuthHealthGate,
},
},
},
},
"code-queue-mgr": {
serviceId: "code-queue-mgr",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/code-queue-mgr",
dockerfile: "src/components/microservices/code-queue-mgr/Dockerfile",
prodLiveApply: "supervisor-only",
prodLiveBlockReason: "code-queue-mgr is the main-server Code Queue control-plane sidecar; live production apply requires explicit supervisor confirmation.",
targets: {
dev: {
targetImage: "code-queue-mgr",
targetCommitImage: (commit: string) => `code-queue-mgr:${commit}`,
deployRef: "deploy.json#environments.dev.services.code-queue-mgr",
compose: {
serviceName: "code-queue-mgr",
containerName: "code-queue-mgr-backend",
deployEnvPrefix: "UNIDESK_CODE_QUEUE_MGR_DEPLOY",
healthProbeCommand: "code-queue-mgr --print-health",
requireHealthCommit: true,
},
},
prod: {
targetImage: "code-queue-mgr",
targetCommitImage: (commit: string) => `code-queue-mgr:${commit}`,
deployRef: "deploy.json#environments.prod.services.code-queue-mgr",
compose: {
serviceName: "code-queue-mgr",
containerName: "code-queue-mgr-backend",
deployEnvPrefix: "UNIDESK_CODE_QUEUE_MGR_DEPLOY",
healthProbeCommand: "code-queue-mgr --print-health",
requireHealthCommit: true,
},
},
},
},
"decision-center": {
serviceId: "decision-center",
environment: "prod",
kind: "d601-k3s",
registryRepository: "unidesk/decision-center",
dockerfile: "src/components/microservices/decision-center/Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "unidesk-decision-center:dev",
targetCommitImage: (commit: string) => `unidesk-decision-center:${commit}`,
deployRef: "deploy.json#environments.dev.services.decision-center",
k3s: {
namespace: "unidesk-dev",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-decision-center.k8s.yaml",
deploymentName: "decision-center-dev",
serviceName: "decision-center-dev",
servicePort: 4277,
containerName: "decision-center",
healthPath: "/health",
podLabelSelector: "app.kubernetes.io/name=decision-center,unidesk.ai/environment=dev",
},
},
prod: {
targetImage: "unidesk-decision-center:d601",
targetCommitImage: (commit: string) => `unidesk-decision-center:${commit}`,
deployRef: "deploy.json#environments.prod.services.decision-center",
k3s: {
namespace: "unidesk",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/decision-center.k8s.yaml",
deploymentName: "decision-center",
serviceName: "decision-center",
servicePort: 4277,
containerName: "decision-center",
healthPath: "/health",
},
},
},
},
"mdtodo": {
serviceId: "mdtodo",
environment: "prod",
kind: "d601-k3s",
registryRepository: "unidesk/mdtodo",
dockerfile: "src/components/microservices/mdtodo/Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "unidesk-mdtodo:dev",
targetCommitImage: (commit: string) => `unidesk-mdtodo:${commit}`,
deployRef: "deploy.json#environments.dev.services.mdtodo",
k3s: {
namespace: "unidesk-dev",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-mdtodo.k8s.yaml",
deploymentName: "mdtodo-dev",
serviceName: "mdtodo-dev",
servicePort: 4267,
containerName: "mdtodo",
healthPath: "/health",
podLabelSelector: "app.kubernetes.io/name=mdtodo,unidesk.ai/environment=dev",
},
},
prod: {
targetImage: "unidesk-mdtodo:d601",
targetCommitImage: (commit: string) => `unidesk-mdtodo:${commit}`,
deployRef: "deploy.json#environments.prod.services.mdtodo",
k3s: {
namespace: "unidesk",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/mdtodo.k8s.yaml",
deploymentName: "mdtodo",
serviceName: "mdtodo",
servicePort: 4267,
containerName: "mdtodo",
healthPath: "/health",
},
},
},
},
"claudeqq": {
serviceId: "claudeqq",
environment: "prod",
kind: "d601-k3s",
registryRepository: "unidesk/claudeqq",
dockerfile: "claudeqq/Dockerfile",
sourceRepo: "https://gitee.com/lyon1998/agent_skills",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "unidesk-claudeqq:dev",
targetCommitImage: (commit: string) => `unidesk-claudeqq:${commit}`,
deployRef: "deploy.json#environments.dev.services.claudeqq",
k3s: {
namespace: "unidesk-dev",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-claudeqq.k8s.yaml",
deploymentName: "claudeqq-dev",
serviceName: "claudeqq-dev",
servicePort: 3290,
containerName: "claudeqq",
healthPath: "/health",
podLabelSelector: "app.kubernetes.io/name=claudeqq,unidesk.ai/environment=dev",
},
},
prod: {
targetImage: "unidesk-claudeqq:d601",
targetCommitImage: (commit: string) => `unidesk-claudeqq:${commit}`,
deployRef: "deploy.json#environments.prod.services.claudeqq",
k3s: {
namespace: "unidesk",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/claudeqq.k8s.yaml",
deploymentName: "claudeqq",
serviceName: "claudeqq",
servicePort: 3290,
containerName: "claudeqq",
healthPath: "/health",
},
},
},
},
"code-queue": {
serviceId: "code-queue",
environment: "dev",
kind: "d601-k3s",
registryRepository: "unidesk/code-queue",
dockerfile: "src/components/microservices/code-queue/Dockerfile",
prodLiveApply: "unsupported",
prodLiveBlockReason: "code-queue is dev-only for artifact consumer validation and has no production artifact deploy, production rollout, or production manifest mutation target.",
devLiveApply: "supervisor-only",
devLiveBlockReason: "Code Queue DEV live apply is self-bootstrap sensitive: a running Code Queue task may plan only dry-run evidence. A human operator or supervisor must explicitly authorize DEV apply outside Code Queue after reviewing the CI artifact digest and dry-run target list.",
targets: {
dev: {
targetImage: "unidesk-code-queue:dev",
targetCommitImage: (commit: string) => `unidesk-code-queue:${commit}`,
deployRef: "deploy.json#environments.dev.services.code-queue",
k3s: {
namespace: "unidesk-dev",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml",
deploymentName: "code-queue-scheduler-dev",
extraDeployments: [
{ name: "d601-dev-provider-egress-proxy", containerName: "provider-egress-proxy" },
"code-queue-read-dev",
"code-queue-write-dev",
],
serviceName: "code-queue-scheduler-dev",
servicePort: 4222,
containerName: "code-queue",
healthPath: "/health",
podLabelSelector: "app.kubernetes.io/name=code-queue,app.kubernetes.io/component=scheduler,unidesk.ai/environment=dev",
},
},
},
},
"frontend": {
serviceId: "frontend",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/frontend",
dockerfile: "src/components/frontend/Dockerfile",
prodLiveApply: "enabled",
targets: {
prod: {
targetImage: "unidesk-frontend",
targetCommitImage: (commit: string) => `unidesk-frontend:${commit}`,
deployRef: "deploy.json#environments.prod.services.frontend",
compose: {
serviceName: "frontend",
containerName: "unidesk-frontend",
deployEnvPrefix: "UNIDESK_FRONTEND_DEPLOY",
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:8080/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
requireHealthCommit: true,
},
},
},
},
"findjob": {
serviceId: "findjob",
environment: "prod",
kind: "d601-compose",
sourceRepo: "https://gitee.com/Lyon1998/findjob",
registryRepository: "unidesk/findjob",
dockerfile: "Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "findjob-server",
targetCommitImage: (commit: string) => `findjob-server:${commit}`,
deployRef: "deploy.json#environments.dev.services.findjob",
compose: {
serviceName: "server",
containerName: "findjob-server",
deployEnvPrefix: "UNIDESK_FINDJOB_DEPLOY",
workDir: "/home/ubuntu/findjob",
composeFile: "docker-compose.yml",
projectHint: "findjob",
healthProbeCommand: "curl -fsS --max-time 12 http://127.0.0.1:3254/api/health",
requireHealthCommit: false,
},
},
prod: {
targetImage: "findjob-server",
targetCommitImage: (commit: string) => `findjob-server:${commit}`,
deployRef: "deploy.json#environments.prod.services.findjob",
compose: {
serviceName: "server",
containerName: "findjob-server",
deployEnvPrefix: "UNIDESK_FINDJOB_DEPLOY",
workDir: "/home/ubuntu/findjob",
composeFile: "docker-compose.yml",
projectHint: "findjob",
healthProbeCommand: "curl -fsS --max-time 12 http://127.0.0.1:3254/api/health",
requireHealthCommit: false,
},
},
},
},
"k3sctl-adapter": {
serviceId: "k3sctl-adapter",
environment: "prod",
kind: "d601-compose",
registryRepository: "unidesk/k3sctl-adapter",
dockerfile: "src/components/microservices/k3sctl-adapter/Dockerfile",
prodLiveApply: "supervisor-only",
prodLiveBlockReason: "k3sctl-adapter is an infrastructure control bridge; this executor exposes artifact consumer plan/dry-run only. Real production deployment requires supervisor confirmation outside this task.",
targets: {
prod: {
targetImage: "unidesk-k3sctl-adapter:d601",
targetCommitImage: (commit: string) => `unidesk-k3sctl-adapter:${commit}`,
deployRef: "deploy.json#environments.prod.services.k3sctl-adapter",
compose: {
serviceName: "k3sctl-adapter",
containerName: "k3sctl-adapter",
deployEnvPrefix: "UNIDESK_K3SCTL_ADAPTER_DEPLOY",
workDir: "/home/ubuntu/cq-deploy",
composeFile: "src/components/microservices/k3sctl-adapter/docker-compose.d601.yml",
projectHint: "k3sctl-adapter",
healthProbeCommand: "curl -fsS --max-time 10 http://127.0.0.1:4266/health",
requireHealthCommit: false,
},
},
},
},
"met-nonlinear": {
serviceId: "met-nonlinear",
environment: "prod",
kind: "d601-compose",
sourceRepo: "https://github.com/pikasTech/met_nonlinear",
registryRepository: "unidesk/met-nonlinear",
dockerfile: "docker/unidesk/Dockerfile.ml",
prodLiveApply: "unsupported",
prodLiveBlockReason: "met-nonlinear is blocked for live artifact deploy because config.json points at docker/unidesk/Dockerfile.ml while the compose service is met-nonlinear-ts. The current compose contract does not let CD prove that the recreated long-running container image label equals the requested commit.",
runtimeVerification: "blocked",
runtimeVerificationBlockReason: "D601 direct artifact consumer is implemented, but this service's registered Dockerfile is the ML image contract while the long-running Compose service is met-nonlinear-ts. Publish a labeled artifact that matches the running service image contract before live deploy, or update the service contract to a server Dockerfile.",
targets: {
dev: {
targetImage: "met-nonlinear-ml:tf26",
targetCommitImage: (commit: string) => `met-nonlinear-ml:${commit}`,
deployRef: "deploy.json#environments.dev.services.met-nonlinear",
compose: {
serviceName: "met-nonlinear-ts",
containerName: "met-nonlinear-ts",
deployEnvPrefix: "UNIDESK_MET_NONLINEAR_DEPLOY",
workDir: "/home/ubuntu/met_nonlinear",
composeFile: "docker-compose.unidesk.yml",
projectHint: "met-nonlinear",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:3288/health",
requireHealthCommit: false,
},
},
prod: {
targetImage: "met-nonlinear-ml:tf26",
targetCommitImage: (commit: string) => `met-nonlinear-ml:${commit}`,
deployRef: "deploy.json#environments.prod.services.met-nonlinear",
compose: {
serviceName: "met-nonlinear-ts",
containerName: "met-nonlinear-ts",
deployEnvPrefix: "UNIDESK_MET_NONLINEAR_DEPLOY",
workDir: "/home/ubuntu/met_nonlinear",
composeFile: "docker-compose.unidesk.yml",
projectHint: "met-nonlinear",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:3288/health",
requireHealthCommit: false,
},
},
},
},
"oa-event-flow": {
serviceId: "oa-event-flow",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/oa-event-flow",
dockerfile: "src/components/microservices/oa-event-flow/Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "oa-event-flow",
targetCommitImage: (commit: string) => `oa-event-flow:${commit}`,
deployRef: "deploy.json#environments.dev.services.oa-event-flow",
compose: {
serviceName: "oa-event-flow",
containerName: "oa-event-flow-backend",
deployEnvPrefix: "UNIDESK_OA_EVENT_FLOW_DEPLOY",
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4255/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
requireHealthCommit: true,
},
},
prod: {
targetImage: "oa-event-flow",
targetCommitImage: (commit: string) => `oa-event-flow:${commit}`,
deployRef: "deploy.json#environments.prod.services.oa-event-flow",
compose: {
serviceName: "oa-event-flow",
containerName: "oa-event-flow-backend",
deployEnvPrefix: "UNIDESK_OA_EVENT_FLOW_DEPLOY",
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4255/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
requireHealthCommit: true,
},
},
},
},
"project-manager": {
serviceId: "project-manager",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/project-manager",
dockerfile: "src/components/microservices/project-manager/Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "project-manager",
targetCommitImage: (commit: string) => `project-manager:${commit}`,
deployRef: "deploy.json#environments.dev.services.project-manager",
compose: {
serviceName: "project-manager",
containerName: "project-manager-backend",
deployEnvPrefix: "UNIDESK_PROJECT_MANAGER_DEPLOY",
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4233/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
requireHealthCommit: true,
},
},
prod: {
targetImage: "project-manager",
targetCommitImage: (commit: string) => `project-manager:${commit}`,
deployRef: "deploy.json#environments.prod.services.project-manager",
compose: {
serviceName: "project-manager",
containerName: "project-manager-backend",
deployEnvPrefix: "UNIDESK_PROJECT_MANAGER_DEPLOY",
healthProbeCommand: "bun -e \"fetch('http://127.0.0.1:4233/health').then(async r=>{const text=await r.text(); console.log(text); process.exit(r.ok?0:1)}).catch(e=>{console.error(e); process.exit(1)})\"",
requireHealthCommit: true,
},
},
},
},
"pipeline": {
serviceId: "pipeline",
environment: "prod",
kind: "d601-compose",
sourceRepo: "https://github.com/pikasTech/pipeline",
registryRepository: "unidesk/pipeline",
dockerfile: "Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "pipeline-v2-control",
targetCommitImage: (commit: string) => `pipeline-v2-control:${commit}`,
deployRef: "deploy.json#environments.dev.services.pipeline",
compose: {
serviceName: "pipeline-control",
containerName: "pipeline-v2-control",
deployEnvPrefix: "UNIDESK_PIPELINE_DEPLOY",
workDir: "/home/ubuntu/pipeline",
composeFile: "docker-compose.yml",
projectHint: "pipeline",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:18082/health",
requireHealthCommit: false,
},
},
prod: {
targetImage: "pipeline-v2-control",
targetCommitImage: (commit: string) => `pipeline-v2-control:${commit}`,
deployRef: "deploy.json#environments.prod.services.pipeline",
compose: {
serviceName: "pipeline-control",
containerName: "pipeline-v2-control",
deployEnvPrefix: "UNIDESK_PIPELINE_DEPLOY",
workDir: "/home/ubuntu/pipeline",
composeFile: "docker-compose.yml",
projectHint: "pipeline",
healthProbeCommand: "curl -fsS --max-time 20 http://127.0.0.1:18082/health",
requireHealthCommit: false,
},
},
},
},
"todo-note": {
serviceId: "todo-note",
environment: "prod",
kind: "compose",
registryRepository: "unidesk/todo-note",
sourceRepo: "https://gitee.com/Lyon1998/todo_note",
dockerfile: "Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "todo-note",
targetCommitImage: (commit: string) => `todo-note:${commit}`,
deployRef: "deploy.json#environments.dev.services.todo-note",
compose: {
serviceName: "todo-note",
containerName: "todo-note-backend",
deployEnvPrefix: "UNIDESK_TODO_NOTE_DEPLOY",
healthProbeCommand: todoNoteHealthProbeCommand(),
requireHealthCommit: true,
syntheticHealthDeployProof: todoNoteSyntheticHealthDeployProof,
},
},
prod: {
targetImage: "todo-note",
targetCommitImage: (commit: string) => `todo-note:${commit}`,
deployRef: "deploy.json#environments.prod.services.todo-note",
compose: {
serviceName: "todo-note",
containerName: "todo-note-backend",
deployEnvPrefix: "UNIDESK_TODO_NOTE_DEPLOY",
healthProbeCommand: todoNoteHealthProbeCommand(),
requireHealthCommit: true,
syntheticHealthDeployProof: todoNoteSyntheticHealthDeployProof,
},
},
},
},
"dev:frontend": {
serviceId: "frontend",
environment: "dev",
kind: "d601-k3s",
registryRepository: "unidesk/frontend",
dockerfile: "src/components/frontend/Dockerfile",
prodLiveApply: "enabled",
targets: {
dev: {
targetImage: "unidesk-frontend:dev",
targetCommitImage: (commit: string) => `unidesk-frontend:${commit}`,
deployRef: "origin/master:deploy.json#environments.dev.services.frontend",
k3s: {
namespace: "unidesk-dev",
manifestRepoPath: "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml",
deploymentName: "frontend-dev",
serviceName: "frontend-dev",
servicePort: 8080,
containerName: "frontend",
healthPath: "/health",
applySelector: "app.kubernetes.io/name=frontend",
podLabelSelector: "app.kubernetes.io/name=frontend,app.kubernetes.io/component=web,unidesk.ai/environment=dev",
},
},
},
},
};
function isHelpArg(value: string | undefined): boolean {
return value === undefined || value === "help" || value === "--help" || value === "-h";
}
function requireValue(args: string[], index: number, option: string): string {
const value = args[index + 1];
if (value === undefined || value.length === 0) throw new Error(`${option} requires a non-empty value`);
return value;
}
function positiveInt(value: string, option: string): number {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`${option} must be a positive integer`);
return parsed;
}
function absolutePath(value: string, option: string): string {
if (!value.startsWith("/")) throw new Error(`${option} must be an absolute path`);
if (value.includes("\n") || value.includes("\0")) throw new Error(`${option} must not contain control characters`);
return value.replace(/\/+$/u, "");
}
function commitValue(value: string, option: string): string {
const normalized = value.toLowerCase();
if (!/^[0-9a-f]{40}$/u.test(normalized)) throw new Error(`${option} must be a full 40-character commit SHA`);
return normalized;
}
function environmentValue(value: string, option: string): ArtifactDeployEnvironment {
if (value === "prod" || value === "dev") return value;
throw new Error(`${option} must be one of: prod, dev`);
}
export function parseArtifactRegistryOptions(args: string[]): ArtifactRegistryOptions {
const options = { ...defaultOptions };
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === "--dry-run") {
options.dryRun = true;
} else if (arg === "--run-now") {
options.runNow = true;
} else if (arg === "--env" || arg === "--environment") {
const environment = requireValue(args, index, arg);
if (environment !== "dev" && environment !== "prod") throw new Error(`${arg} must be dev or prod`);
options.environment = environment;
index += 1;
} else if (arg === "--provider-id") {
options.providerId = requireValue(args, index, arg);
index += 1;
} else if (arg === "--host") {
options.host = requireValue(args, index, arg);
index += 1;
} else if (arg === "--port") {
options.port = positiveInt(requireValue(args, index, arg), arg);
index += 1;
} else if (arg === "--image") {
options.image = requireValue(args, index, arg);
index += 1;
} else if (arg === "--base-dir") {
options.baseDir = absolutePath(requireValue(args, index, arg), arg);
index += 1;
} else if (arg === "--storage-dir") {
options.storageDir = absolutePath(requireValue(args, index, arg), arg);
index += 1;
} else if (arg === "--timeout-ms") {
options.timeoutMs = positiveInt(requireValue(args, index, arg), arg);
index += 1;
} else if (arg === "--commit") {
options.commit = commitValue(requireValue(args, index, arg), arg);
index += 1;
} else if (arg === "--target-image") {
options.targetImage = requireValue(args, index, arg);
index += 1;
} else if (arg === "--service" || arg === "--service-id") {
options.serviceId = requireValue(args, index, arg);
index += 1;
} else if (arg === "--source-repo") {
options.sourceRepo = requireValue(args, index, arg);
options.sourceRepoExplicit = true;
index += 1;
} else if (arg === "--deploy-ref") {
options.deployRef = requireValue(args, index, arg);
index += 1;
} else if (arg === "--deploy-json-service") {
options.deployJsonService = parseDeployJsonServiceContractBase64(requireValue(args, index, arg));
index += 1;
} else if (arg === "--env" || arg === "--environment") {
options.environment = environmentValue(requireValue(args, index, arg), arg);
index += 1;
} else {
throw new Error(`unknown artifact-registry option: ${arg}`);
}
}
if (options.host !== "127.0.0.1") throw new Error("--host is first-stage restricted to 127.0.0.1");
if (options.port !== 5000) throw new Error("--port is first-stage restricted to 5000");
return options;
}
function sha256(text: string): string {
return createHash("sha256").update(text).digest("hex");
}
function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
function d601K3sGuardScript(): string {
return d601K3sGuardShellLines().join("\n");
}
function base64(value: string): string {
return Buffer.from(value, "utf8").toString("base64");
}
function safeName(value: string): string {
return value.replace(/[^A-Za-z0-9_.-]/gu, "-");
}
function rootExecPrelude(): string {
return [
"root_exec() {",
" if [ \"$(id -u)\" = \"0\" ]; then \"$@\"; return; fi",
" if sudo -n true >/dev/null 2>&1; then sudo -n \"$@\"; return; fi",
" if [ -x /mnt/c/Windows/System32/wsl.exe ]; then /mnt/c/Windows/System32/wsl.exe -u root -- \"$@\"; return; fi",
" echo 'artifact_registry_root_access=missing' >&2",
" return 1",
"}",
].join("\n");
}
function file(path: string, content: string, mode = "0644"): RenderedFile {
return { path, mode, sha256: sha256(content), content };
}
function registryConfig(): string {
return `version: 0.1
log:
fields:
service: unidesk-artifact-registry
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: false
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
`;
}
function composeFile(options: ArtifactRegistryOptions): string {
return `name: ${options.composeProject}
services:
${options.serviceName}:
image: ${options.image}
container_name: ${options.containerName}
restart: unless-stopped
ports:
- "${options.host}:${options.port}:5000"
volumes:
- "${options.baseDir}/config.yml:/etc/docker/registry/config.yml:ro"
- "${options.storageDir}:/var/lib/registry"
labels:
unidesk.ai/service-id: artifact-registry
unidesk.ai/managed-by: host-systemd-docker-compose
unidesk.ai/provider-id: ${options.providerId}
`;
}
function systemdUnit(options: ArtifactRegistryOptions): string {
return `[Unit]
Description=UniDesk D601 Artifact Registry (CNCF Distribution)
Documentation=https://github.com/pikasTech/unidesk/blob/master/docs/reference/artifact-registry.md
After=network-online.target
Wants=network-online.target
ConditionPathExists=/var/run/docker.sock
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=${options.baseDir}
ExecStartPre=/bin/mkdir -p ${options.baseDir} ${options.storageDir}
ExecStartPre=/bin/sh -lc 'docker info >/dev/null'
ExecStart=/bin/sh -lc 'docker compose -p ${options.composeProject} -f ${options.baseDir}/compose.yml up -d --remove-orphans'
ExecStop=/bin/sh -lc 'docker compose -p ${options.composeProject} -f ${options.baseDir}/compose.yml down'
TimeoutStartSec=120
TimeoutStopSec=120
[Install]
WantedBy=multi-user.target
`;
}
function renderBundle(options: ArtifactRegistryOptions): RenderedBundle {
const paths = {
unit: `/etc/systemd/system/${options.unitName}`,
compose: `${options.baseDir}/compose.yml`,
config: `${options.baseDir}/config.yml`,
storage: options.storageDir,
baseDir: options.baseDir,
};
return {
paths,
files: [
file(paths.config, registryConfig()),
file(paths.compose, composeFile(options)),
file(paths.unit, systemdUnit(options)),
],
};
}
function plan(options: ArtifactRegistryOptions): Record<string, unknown> {
const bundle = renderBundle(options);
return {
ok: true,
providerId: options.providerId,
mode: "d601-host-managed",
firstStage: true,
runtime: {
implementation: "CNCF Distribution Docker registry",
image: options.image,
endpoint: `http://${options.host}:${options.port}`,
publicExposure: "none",
orchestration: "systemd + Docker Compose on D601 host/WSL OS",
k3sManaged: false,
},
dependencies: [
{ name: "Docker Engine", scope: "D601 host", requiredFor: "running the registry container" },
{ name: "Docker Compose plugin", scope: "D601 host", requiredFor: "systemd ExecStart/ExecStop" },
{ name: "systemd", scope: "D601 host", requiredFor: "host-managed service lifecycle" },
{ name: "registry:2.8.3", scope: "D601 Docker cache / upstream pull", requiredFor: "CNCF Distribution runtime image" },
{ name: "provider-gateway Host SSH", scope: "UniDesk control bridge", requiredFor: "readonly status and health checks" },
{ name: "local filesystem storage", scope: bundle.paths.storage, requiredFor: "artifact persistence outside k3s" },
],
boundaries: [
"listen only on D601 host loopback 127.0.0.1:5000",
"do not expose a public port, NodePort, hostPort, or third-party registry",
"do not run inside k3s; keep the registry outside the native k3s failure domain",
"CI builds and publishes backend-core, frontend, and reviewed user-service artifacts on D601",
"CD only pulls, retags, recreates/imports artifacts, and verifies live commit",
"master server CD must not compile Rust or run docker compose build for artifact consumers",
"code-queue is dev-only for artifact consumer validation and has no prod artifact deploy target",
],
renderedPaths: bundle.paths,
artifactConsumerFlow: [
"D601 CI builds unidesk/<service-id>:<commit>",
"D601 CI pushes 127.0.0.1:5000/unidesk/<service-id>:<commit>",
"Compose consumers pull via a controlled provider-gateway SSH image stream",
"k3s consumers pull on D601 and import into native k3s containerd",
"CD retags/recreates or updates Deployment images and verifies live commit metadata",
],
frontendArtifactFlow: [
"D601 CI builds and pushes 127.0.0.1:5000/unidesk/frontend:<commit>",
"dev CD imports the image into D601 native k3s and verifies frontend-dev /health deploy.commit",
"prod CD pulls via provider-gateway image stream, recreates Compose frontend with no build, and verifies /health deploy.commit",
],
};
}
function statusScript(options: ArtifactRegistryOptions, bundle: RenderedBundle): string {
const hashes = Object.fromEntries(bundle.files.map((item) => [item.path, item.sha256]));
return `set -u
kv() { printf '%s=%s\\n' "$1" "$2"; }
bool_file() { [ -f "$1" ] && printf true || printf false; }
bool_dir() { [ -d "$1" ] && printf true || printf false; }
hash_file() { sha256sum "$1" 2>/dev/null | awk '{print $1}' || true; }
unit=${shellQuote(bundle.paths.unit)}
compose=${shellQuote(bundle.paths.compose)}
config=${shellQuote(bundle.paths.config)}
storage=${shellQuote(bundle.paths.storage)}
container=${shellQuote(options.containerName)}
expected_image=${shellQuote(options.image)}
port=${options.port}
kv readonly true
kv unit_exists "$(bool_file "$unit")"
kv compose_exists "$(bool_file "$compose")"
kv config_exists "$(bool_file "$config")"
kv storage_exists "$(bool_dir "$storage")"
if command -v systemctl >/dev/null 2>&1; then
kv systemctl_available true
kv unit_active "$(systemctl is-active "${options.unitName}" 2>/dev/null || true)"
kv unit_enabled "$(systemctl is-enabled "${options.unitName}" 2>/dev/null || true)"
else
kv systemctl_available false
kv unit_active unknown
kv unit_enabled unknown
fi
if command -v docker >/dev/null 2>&1; then
kv docker_available true
container_running="$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null || true)"
container_status="$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || true)"
container_image="$(docker inspect -f '{{.Config.Image}}' "$container" 2>/dev/null || true)"
container_restart_policy="$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$container" 2>/dev/null || true)"
kv container_running "$([ -n "$container_running" ] && printf '%s' "$container_running" || printf false)"
kv container_status "$container_status"
kv container_image "$container_image"
kv container_restart_policy "$container_restart_policy"
else
kv docker_available false
kv container_running false
kv container_status unknown
kv container_image ""
kv container_restart_policy unknown
fi
if command -v ss >/dev/null 2>&1; then
listeners="$(ss -ltnH 2>/dev/null | awk -v p=":$port" '$4 ~ p "$" {print $4}' || true)"
else
listeners=""
fi
listener_count="$(printf '%s\\n' "$listeners" | sed '/^$/d' | wc -l | tr -d ' ')"
bad_listener_count="$(printf '%s\\n' "$listeners" | sed '/^$/d' | grep -Ev "^(127[.]0[.]0[.]1|localhost):${options.port}$|^\\[::1\\]:${options.port}$|^::1:${options.port}$" | wc -l | tr -d ' ')"
loopback_only=false
if [ "\${listener_count:-0}" -gt 0 ] && [ "\${bad_listener_count:-0}" -eq 0 ]; then loopback_only=true; fi
kv listener_count "\${listener_count:-0}"
kv bad_listener_count "\${bad_listener_count:-0}"
kv loopback_only "$loopback_only"
if command -v curl >/dev/null 2>&1; then
kv curl_available true
kv v2_http_code "$(timeout 3 curl -fsS -o /dev/null -w '%{http_code}' http://127.0.0.1:${options.port}/v2/ 2>/dev/null || true)"
else
kv curl_available false
kv v2_http_code ""
fi
config_hash="$(hash_file "$config")"
compose_hash="$(hash_file "$compose")"
unit_hash="$(hash_file "$unit")"
kv config_hash_matches "$([ -n "$config_hash" ] && [ "$config_hash" = ${shellQuote(hashes[bundle.paths.config] ?? "")} ] && printf true || printf false)"
kv compose_hash_matches "$([ -n "$compose_hash" ] && [ "$compose_hash" = ${shellQuote(hashes[bundle.paths.compose] ?? "")} ] && printf true || printf false)"
kv unit_hash_matches "$([ -n "$unit_hash" ] && [ "$unit_hash" = ${shellQuote(hashes[bundle.paths.unit] ?? "")} ] && printf true || printf false)"
kv image_matches "$([ "\${container_image:-}" = "$expected_image" ] && printf true || printf false)"
kv config_hash "$config_hash"
kv compose_hash "$compose_hash"
kv unit_hash "$unit_hash"
`;
}
function parseKeyValueOutput(stdout: string): Record<string, string> {
const values: Record<string, string> = {};
for (const line of stdout.split(/\r?\n/u)) {
const match = /^([A-Za-z0-9_]+)=(.*)$/u.exec(line);
if (match !== null) values[match[1]] = match[2];
}
return values;
}
function asBool(value: string | undefined): boolean {
return value === "true";
}
function registryRecommendedAction(classification: ArtifactRegistryFailureClassification | null): string {
if (classification === null) return "none";
if (classification === "control-plane-missing") return "restore the artifact registry readonly control plane, or rerun through --main-server-ip <host> once frontend/backend-core dispatch is reachable";
if (classification === "local-docker-required") return "run the read-only check through --main-server-ip <host> or from a main-server CLI with backend-core available";
if (classification === "provider-ssh-command-missing") return "restore D601 provider-gateway host.ssh dispatch/capability before retrying artifact registry health";
if (classification === "ssh-helper-command-shape-incompatible") return "upgrade the CLI/control-plane host.ssh helper shape so it can run bash -lc readonly probes";
if (classification === "remote-command-timeout") return "rerun artifact-registry health and inspect D601 host.ssh latency if the timeout repeats";
if (classification === "registry-not-installed") return "install the D601 artifact registry through the controlled artifact-registry install path, then rerun health";
return "restore the D601 artifact registry service/container/API until registry health passes";
}
function readonlyRemoteCommandShape(action: "status" | "health", options: ArtifactRegistryOptions): string {
return `host.ssh provider=${options.providerId} mode=exec argv=bash -lc <artifact-registry-${action}-readonly-script> timeoutMs=${options.timeoutMs}`;
}
function classifyProviderSshCommandFailure(command: CommandResult): ArtifactRegistryFailureClassification {
const output = `${command.stderr}\n${command.stdout}`.toLowerCase();
if (command.timedOut || output.includes("timed out") || output.includes("timeout")) return "remote-command-timeout";
if (output.includes("no such container: unidesk-backend-core") || output.includes("no such container: unidesk-database") || output.includes("backend-core bridge unavailable")) {
return "local-docker-required";
}
if (output.includes("provider does not declare host.ssh") || output.includes("provider is offline") || output.includes("host.ssh capability")) {
return "provider-ssh-command-missing";
}
if (output.includes("remote frontend transport does not support") || output.includes("ssh helper") || output.includes("unsupported command") || output.includes("bad substitution") || output.includes("command is too long")) {
return "ssh-helper-command-shape-incompatible";
}
if (command.exitCode === 126 || command.exitCode === 127 || output.includes("command not found") || output.includes("exec format error")) {
return "ssh-helper-command-shape-incompatible";
}
return "provider-ssh-command-missing";
}
function providerSshCommandFailureScopes(classification: ArtifactRegistryFailureClassification): string[] {
const scopes = ["provider-ssh-command"];
if (classification === "control-plane-missing") return ["control-plane-missing", ...scopes];
if (classification === "remote-command-timeout") scopes.push("remote-command-timeout");
if (classification === "local-docker-required") scopes.push("local-docker-control-plane");
if (classification === "ssh-helper-command-shape-incompatible") scopes.push("ssh-helper-command-shape");
return scopes;
}
function registryHealthFailureClassification(checks: Record<string, boolean>, failedScopes: string[]): ArtifactRegistryFailureClassification | null {
if (failedScopes.length === 0) return null;
const installed = checks.unitExists === true
|| checks.composeExists === true
|| checks.configExists === true
|| checks.storageExists === true
|| checks.containerRunning === true;
return installed ? "registry-unhealthy" : "registry-not-installed";
}
function registryHealthDecision(checks: Record<string, boolean>, commandOk: boolean): Record<string, unknown> {
const scopeChecks: Array<[string, boolean]> = [
["systemd", checks.systemctlAvailable === true && checks.unitExists === true && checks.unitActive === true],
["docker", checks.dockerAvailable === true],
["registry-container", checks.containerRunning === true],
["loopback-listener", checks.loopbackOnly === true],
["registry-api", checks.v2Ok === true],
["storage", checks.storageExists === true],
["rendered-config", checks.configHashMatches === true && checks.composeHashMatches === true && checks.unitHashMatches === true],
["registry-image", checks.imageMatches === true],
];
const healthyScopes = scopeChecks.filter(([, ok]) => ok).map(([scope]) => scope);
const failedScopes = scopeChecks.filter(([, ok]) => !ok).map(([scope]) => scope);
const runtimeApiHealthy = checks.containerRunning === true && checks.loopbackOnly === true && checks.v2Ok === true;
const failureClassification = commandOk ? registryHealthFailureClassification(checks, failedScopes) : "provider-ssh-command-missing";
return {
decision: commandOk && failedScopes.length === 0 ? "healthy" : commandOk || runtimeApiHealthy ? "service-degraded" : "retryable-transient",
retryable: true,
failureClassification,
healthyScopes,
failedScopes,
recommendedAction: registryRecommendedAction(failureClassification),
runtimeApiHealthy,
};
}
export function artifactRegistryCommandTail(result: CommandResult): Record<string, unknown> {
return {
command: result.command.length > 7 ? [...result.command.slice(0, 7), "<readonly-script>"] : result.command,
exitCode: result.exitCode,
signal: result.signal,
timedOut: result.timedOut,
stdoutTail: result.stdout.slice(-4000),
stderrTail: result.stderr.slice(-4000),
};
}
function commandTail(result: CommandResult): Record<string, unknown> {
return artifactRegistryCommandTail(result);
}
function artifactConsumerSpec(serviceId: string, environment: ArtifactDeployEnvironment | null): ArtifactConsumerSpec | null {
const key = environment === null || environment === "prod" ? serviceId : `${environment}:${serviceId}`;
const explicit = artifactConsumerSpecs[key];
if (explicit !== undefined) return explicit;
const shared = artifactConsumerSpecs[serviceId];
return shared ?? null;
}
function supportedArtifactConsumers(): Array<{ environment: ArtifactDeployEnvironment; serviceId: SupportedArtifactConsumerService; kind: ArtifactConsumerSpec["kind"] }> {
return Object.values(artifactConsumerSpecs).flatMap((spec) => Object.keys(spec.targets).map((environment) => ({
environment: environment as ArtifactDeployEnvironment,
serviceId: spec.serviceId,
kind: spec.kind,
})));
}
function artifactConsumerTarget(spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment | null): ArtifactConsumerTarget | null {
return spec.targets[environment ?? "prod"] ?? null;
}
function unsupportedService(serviceId: string, options: ArtifactRegistryOptions): Record<string, unknown> {
return {
ok: false,
supported: false,
error: "unsupported",
serviceId,
environment: options.environment ?? "prod",
providerId: options.providerId,
reason: "No standardized D601 registry CD consumer is implemented for this service.",
supportedServices: supportedArtifactConsumerServices,
supportedConsumers: supportedArtifactConsumers(),
policy: "unsupported services must not silently fall back to maintenance-channel source builds or legacy direct deployment",
};
}
function unsupportedEnvironment(spec: ArtifactConsumerSpec, options: ArtifactRegistryOptions): Record<string, unknown> {
const environment = options.environment ?? "prod";
const codeQueueGuard = spec.serviceId === "code-queue" ? codeQueueSelfBootstrapGuard(environment) : undefined;
const reason = spec.serviceId === "code-queue" && environment === "prod"
? spec.prodLiveBlockReason ?? "Code Queue production artifact deploy, rollout and manifest mutation are intentionally unsupported in this phase."
: `No standardized ${environment} registry artifact consumer is implemented for ${spec.serviceId}.`;
return {
ok: false,
supported: false,
error: "unsupported-environment",
serviceId: spec.serviceId,
environment,
providerId: options.providerId,
reason,
supportedEnvironments: Object.keys(spec.targets),
requiresSupervisorApproval: spec.serviceId === "code-queue",
selfBootstrapGuard: codeQueueGuard,
affectedRuntime: spec.serviceId === "code-queue" ? codeQueueAffectedRuntime(environment, false) : undefined,
excludedTargets: spec.serviceId === "code-queue" ? codeQueueExcludedTargets(environment) : undefined,
policy: "artifact CD must not silently fall back to maintenance-channel source builds or legacy direct deployment",
};
}
function artifactConsumerLivePolicy(spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment): "enabled" | "supervisor-only" | "unsupported" {
return environment === "prod" ? spec.prodLiveApply : spec.devLiveApply ?? "enabled";
}
function artifactConsumerLiveBlockReason(spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment): string | null {
if (spec.runtimeVerificationBlockReason !== undefined) return spec.runtimeVerificationBlockReason;
return environment === "prod" ? spec.prodLiveBlockReason ?? null : spec.devLiveBlockReason ?? null;
}
function codeQueueExcludedTargets(environment: ArtifactDeployEnvironment): Record<string, unknown>[] {
const excluded: Record<string, unknown>[] = [
{
namespace: "unidesk",
deployments: ["code-queue", "code-queue-read", "code-queue-write", "d601-provider-egress-proxy", "d601-tcp-egress-gateway"],
reason: "production Code Queue execution-plane Deployments are outside this artifact consumer and must not be mutated by dry-run or self-bootstrap automation.",
},
{
operations: ["restart scheduler", "restart runner", "interrupt active tasks", "cancel active tasks"],
reason: "artifact CD planning must not alter scheduler/runner lifecycle or active task control state.",
},
];
if (environment === "dev") {
excluded.push({
namespace: "unidesk-dev",
nonDryRunMutation: "requires explicit supervisor or human authorization outside the running Code Queue task",
reason: "DEV is the only Code Queue artifact consumer target, but self-bootstrap automation may produce dry-run evidence only.",
});
}
return excluded;
}
function codeQueueAffectedRuntime(environment: ArtifactDeployEnvironment, targetPlanned: boolean): Record<string, unknown> {
return {
dryRunMutation: false,
plannedTarget: targetPlanned && environment === "dev"
? {
namespace: "unidesk-dev",
deployments: ["code-queue-scheduler-dev", "code-queue-read-dev", "code-queue-write-dev", "d601-dev-provider-egress-proxy"],
}
: null,
productionNamespaceAffected: false,
productionSchedulerRunnerAffected: false,
activeTaskControlAffected: false,
activeTaskInterruptCancelAffected: false,
};
}
function codeQueueSelfBootstrapGuard(environment: ArtifactDeployEnvironment): Record<string, unknown> {
return {
check: "code-queue-self-bootstrap-guard",
serviceId: "code-queue",
selfBootstrapBlocked: true,
requiresSupervisorApproval: true,
actorBoundary: "a running Code Queue task may publish contract evidence and dry-run plans only; it must not deploy Code Queue itself",
devApply: "requires explicit supervisor or human authorization outside the running Code Queue task",
prodApply: "unsupported until a separate supervisor-approved production Code Queue CD design exists",
allowedWithoutApproval: [
"CI artifact publication",
"deploy plan",
"deploy apply --dry-run",
"artifact-registry deploy-service --dry-run",
],
forbiddenActions: [
"self-deploy Code Queue from Code Queue",
"run a non-dry-run DEV apply from Code Queue",
"production namespace mutation",
"production manifest mutation",
"scheduler or runner restart",
"active task interrupt",
"active task cancel",
],
environment,
};
}
function codeQueueMgrSelfBootstrapGuard(environment: ArtifactDeployEnvironment, requiresSupervisorApproval: boolean): Record<string, unknown> {
return {
check: "code-queue-mgr-control-plane-guard",
serviceId: "code-queue-mgr",
selfBootstrapBlocked: true,
requiresSupervisorApproval,
actorBoundary: "dry-run is allowed, but a running Code Queue task must not replace the Code Queue control-plane sidecar as part of delivering Code Queue itself",
targetScope: "main-server Compose service code-queue-mgr / container code-queue-mgr-backend only",
prodApply: "requires explicit supervisor confirmation",
environment,
forbiddenActions: [
"mutate D601 Code Queue scheduler",
"mutate D601 Code Queue runner",
"restart active tasks",
"interrupt active tasks",
"cancel active tasks",
],
};
}
function authBrokerCredentialMountGuard(environment: ArtifactDeployEnvironment, requiresSupervisorApproval: boolean): Record<string, unknown> {
return {
check: "auth-broker-credential-mount-guard",
serviceId: "auth-broker",
requiresSupervisorApproval,
actorBoundary: "dry-run and contract evidence are allowed, but live broker startup needs explicit review of credential reference mounting and private exposure",
targetScope: "main-server Compose profile auth-broker / container auth-broker-backend only",
composeProfileRequired: "auth-broker",
publicPortAllowed: false,
registryCredentialsProxied: false,
deployCredentialsProxied: false,
environment,
forbiddenActions: [
"write real GitHub token into config.json, deploy.json, or docker-compose.yml",
"publish auth-broker on a public port",
"restart backend-core, provider-gateway, or Code Queue as part of broker dry-run registration",
"grant registry, deploy, database, k3s, provider token, or host SSH permissions",
],
};
}
function artifactConsumerSelfBootstrapGuard(
spec: ArtifactConsumerSpec,
environment: ArtifactDeployEnvironment,
requiresSupervisorApproval: boolean,
): Record<string, unknown> | undefined {
if (spec.serviceId === "code-queue") return codeQueueSelfBootstrapGuard(environment);
if (spec.serviceId === "code-queue-mgr") return codeQueueMgrSelfBootstrapGuard(environment, requiresSupervisorApproval);
if (spec.serviceId === "auth-broker") return authBrokerCredentialMountGuard(environment, requiresSupervisorApproval);
return undefined;
}
function artifactConsumerLiveBlock(spec: ArtifactConsumerSpec, options: ArtifactRegistryOptions): Record<string, unknown> | null {
const environment = options.environment ?? "prod";
const livePolicy = artifactConsumerLivePolicy(spec, environment);
const liveReason = artifactConsumerLiveBlockReason(spec, environment);
if (spec.runtimeVerification === "blocked") {
return {
ok: false,
supported: false,
liveApplyAllowed: false,
error: "runtime-verification-blocked",
serviceId: spec.serviceId,
environment,
providerId: options.providerId,
reason: spec.runtimeVerificationBlockReason ?? `${spec.serviceId} does not yet satisfy strict runtime deploy commit verification.`,
requiredBeforeLiveApply: [
"runtime Compose env injects deploy commit/requestedCommit metadata",
"service health reports deploy.commit and deploy.requestedCommit for strict verification",
],
policy: "artifact CD must not accept a healthy old service or silently fall back to legacy rebuild paths",
};
}
if (livePolicy === "enabled") return null;
if (livePolicy === "supervisor-only") {
return {
ok: false,
supported: true,
liveApplyAllowed: false,
error: "supervisor-confirmation-required",
serviceId: spec.serviceId,
environment,
providerId: options.providerId,
reason: liveReason ?? `${spec.serviceId} ${environment} artifact apply requires supervisor confirmation.`,
requiresSupervisorApproval: true,
selfBootstrapGuard: artifactConsumerSelfBootstrapGuard(spec, environment, true),
affectedRuntime: spec.serviceId === "code-queue" ? codeQueueAffectedRuntime(environment, true) : undefined,
excludedTargets: spec.serviceId === "code-queue" ? codeQueueExcludedTargets(environment) : undefined,
dryRunCommandShape: `bun scripts/cli.ts artifact-registry deploy-service --env ${environment} --service ${spec.serviceId} --commit <full-sha> --dry-run`,
policy: "worker automation must not perform live apply for this supervisor-gated artifact consumer",
};
}
return {
ok: false,
supported: false,
liveApplyAllowed: false,
error: "artifact-consumer-blocked",
serviceId: spec.serviceId,
environment,
providerId: options.providerId,
reason: liveReason ?? `${spec.serviceId} does not yet satisfy the artifact consumer runtime verification contract.`,
requiresSupervisorApproval: spec.serviceId === "code-queue",
selfBootstrapGuard: spec.serviceId === "code-queue" ? codeQueueSelfBootstrapGuard(environment) : undefined,
affectedRuntime: spec.serviceId === "code-queue" ? codeQueueAffectedRuntime(environment, false) : undefined,
excludedTargets: spec.serviceId === "code-queue" ? codeQueueExcludedTargets(environment) : undefined,
requiredBeforeLiveApply: [
"CI can publish a commit-pinned image with matching service id, source repo, source commit, and Dockerfile labels",
"runtime Compose env injects deploy commit/requestedCommit metadata",
"service health reports deploy.commit and deploy.requestedCommit for strict verification",
],
policy: "do not silently fall back to server rebuild, dirty worktrees, mutable tags, or source builds on the runtime target",
};
}
function artifactImageRef(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string): string {
return `127.0.0.1:${options.port}/${(options.dryRun ? options.deployJsonService?.artifact?.repository : undefined) ?? spec.registryRepository}:${commit}`;
}
function sourceRepoFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
return options.sourceRepoExplicit ? options.sourceRepo : spec.sourceRepo ?? options.sourceRepo;
}
function deployRefFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
const target = artifactConsumerTarget(spec, options.environment);
return options.deployRef ?? (options.dryRun ? options.deployJsonService?.consumer?.targetRef : undefined) ?? target?.deployRef ?? `deploy.json#environments.${options.environment ?? "prod"}.services.${spec.serviceId}`;
}
function deployJsonServiceForOptions(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, environment: ArtifactDeployEnvironment): DeployJsonServiceContract | null {
if (!options.dryRun) return null;
if (options.deployJsonService !== null) return options.deployJsonService;
if (environment === "dev" && (spec.serviceId === "decision-center" || spec.serviceId === "mdtodo")) {
return readDeployJsonServiceContractFromFile(environment, spec.serviceId);
}
return null;
}
function targetImageFor(options: ArtifactRegistryOptions, target: ArtifactConsumerTarget): string {
return (options.dryRun ? options.deployJsonService?.consumer?.target.stableImage : undefined) ?? target.targetImage;
}
function targetCommitImageFor(options: ArtifactRegistryOptions, target: ArtifactConsumerTarget, commit: string): string {
const stableImage = options.dryRun ? options.deployJsonService?.consumer?.target.stableImage : undefined;
return stableImage === undefined ? target.targetCommitImage(commit) : deployJsonCommitImage(stableImage, commit);
}
function registryRepositoryFor(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec): string {
return (options.dryRun ? options.deployJsonService?.artifact?.repository : undefined) ?? spec.registryRepository;
}
function artifactRegistryDeployJsonMirrors(
options: ArtifactRegistryOptions,
spec: ArtifactConsumerSpec,
target: ArtifactConsumerTarget,
): DeployJsonExecutorMirror[] {
const mirrors: DeployJsonExecutorMirror[] = [
{
surface: "artifact-registry-executor",
artifact: {
kind: "source-build",
repository: spec.registryRepository,
tag: "commitId",
},
consumer: {
kind: spec.kind === "d601-k3s" ? "d601-k3s-managed" : spec.kind,
noRuntimeSourceBuild: true,
target: target.k3s === undefined ? undefined : {
namespace: target.k3s.namespace,
deployment: target.k3s.deploymentName,
service: target.k3s.serviceName,
containerName: target.k3s.containerName,
stableImage: target.targetImage,
manifestRepoPath: target.k3s.manifestRepoPath,
},
},
runtime: target.k3s === undefined ? undefined : {
containerPort: target.k3s.servicePort,
servicePort: target.k3s.servicePort,
healthPath: target.k3s.healthPath,
health: options.deployJsonService?.runtime?.health === undefined ? undefined : {
deployMetadataRequired: true,
},
},
},
];
if (options.deployJsonService !== null) {
const manifestMirror = k3sManifestExecutorMirror(options.deployJsonService);
if (manifestMirror !== null) mirrors.push(manifestMirror);
}
return mirrors;
}
function runRemoteScript(options: ArtifactRegistryOptions, script: string, timeoutMs = options.timeoutMs, runtime: ArtifactRegistryCommandRuntime = {}): CommandResult {
const command = [process.execPath, "scripts/cli.ts", "ssh", options.providerId, "argv", "bash", "-lc", script];
if (runtime.runRemoteScriptForTest !== undefined) return runtime.runRemoteScriptForTest(options, script, timeoutMs);
return runCommand(command, repoRoot, { timeoutMs });
}
function readonlyCommandFailureResult(
options: ArtifactRegistryOptions,
command: CommandResult,
action: "status" | "health",
): Record<string, unknown> {
const bundle = renderBundle(options);
const failureClassification = classifyProviderSshCommandFailure(command);
return {
ok: false,
readonly: true,
installed: false,
healthy: false,
decision: failureClassification === "local-docker-required" ? "infra-blocked" : "retryable-transient",
retryable: failureClassification !== "ssh-helper-command-shape-incompatible",
failureClassification,
recommendedAction: registryRecommendedAction(failureClassification),
remoteCommandShape: readonlyRemoteCommandShape(action, options),
healthyScopes: [],
failedScopes: providerSshCommandFailureScopes(failureClassification),
runtimeApiHealthy: false,
checks: {},
expected: {
endpoint: `http://${options.host}:${options.port}`,
image: options.image,
paths: bundle.paths,
},
command: artifactRegistryCommandTail(command),
};
}
function remoteFrontendHostFromEnv(env: NodeJS.ProcessEnv = process.env): string | null {
for (const key of ["UNIDESK_MAIN_SERVER_IP", "UNIDESK_MAIN_SERVER_HOST", "CODE_QUEUE_DEV_CONTAINER_MASTER_HOST"]) {
const value = env[key]?.trim() ?? "";
if (value.length === 0) continue;
if (value === "localhost" || value === "127.0.0.1" || value === "::1") continue;
return value.replace(/\/+$/u, "");
}
if (env.CODE_QUEUE_SERVICE_ROLE || env.CODE_QUEUE_INSTANCE_ID || env.KUBERNETES_SERVICE_HOST) {
const publicHost = readConfig().network.publicHost.trim();
if (publicHost.length > 0 && publicHost !== "localhost" && publicHost !== "127.0.0.1" && publicHost !== "::1") return publicHost;
}
return null;
}
function controlPlaneMissingResult(
options: ArtifactRegistryOptions,
action: "status" | "health",
localResult: Record<string, unknown>,
remoteResult: Record<string, unknown> | null,
remoteCommand: CommandResult | null,
remoteHost: string | null,
): Record<string, unknown> {
const bundle = renderBundle(options);
return {
ok: false,
readonly: true,
installed: false,
healthy: false,
decision: "infra-blocked",
retryable: true,
runnerDisposition: "infra-blocked",
failureClassification: "control-plane-missing",
recommendedAction: registryRecommendedAction("control-plane-missing"),
remoteCommandShape: readonlyRemoteCommandShape(action, options),
healthyScopes: [],
failedScopes: providerSshCommandFailureScopes("control-plane-missing"),
runtimeApiHealthy: false,
checks: {},
expected: {
endpoint: `http://${options.host}:${options.port}`,
image: options.image,
paths: bundle.paths,
},
controlPlane: {
preferred: "remote-frontend",
remoteFallbackAttempted: remoteHost !== null,
remoteFallbackUsed: false,
remoteHost,
localBackendCoreMissing: true,
classification: "control-plane-missing",
retryCommand: remoteHost === null
? `bun scripts/cli.ts --main-server-ip <host> artifact-registry ${action} --provider-id ${options.providerId}`
: `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --provider-id ${options.providerId}`,
},
localObservation: localResult,
remoteObservation: remoteResult,
remoteCommand: remoteCommand === null ? null : artifactRegistryCommandTail(remoteCommand),
};
}
function parsedCliData(stdout: string): Record<string, unknown> | null {
if (stdout.trim().length === 0) return null;
try {
const parsed = JSON.parse(stdout) as unknown;
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
const record = parsed as Record<string, unknown>;
const data = record.data;
return typeof data === "object" && data !== null && !Array.isArray(data) ? data as Record<string, unknown> : record;
} catch {
return null;
}
}
function unwrapRemoteArtifactRegistryResult(data: Record<string, unknown>): Record<string, unknown> | null {
const directResult = data.result;
if (typeof directResult === "object" && directResult !== null && !Array.isArray(directResult)) return directResult as Record<string, unknown>;
const nested = data.data;
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) {
const nestedRecord = nested as Record<string, unknown>;
const nestedResult = nestedRecord.result;
if (typeof nestedResult === "object" && nestedResult !== null && !Array.isArray(nestedResult)) return nestedResult as Record<string, unknown>;
}
return null;
}
function annotateRemoteReadonlyResult(
result: Record<string, unknown>,
localResult: Record<string, unknown>,
remoteHost: string,
command: CommandResult,
): Record<string, unknown> {
return {
...result,
controlPlane: {
preferred: "remote-frontend",
remoteFallbackAttempted: true,
remoteFallbackUsed: true,
remoteHost,
localBackendCoreMissing: true,
classification: null,
},
localObservation: localResult,
remoteCommand: artifactRegistryCommandTail(command),
};
}
function annotateRemoteFirstReadonlyResult(
result: Record<string, unknown>,
remoteHost: string,
command: CommandResult,
): Record<string, unknown> {
return {
...result,
controlPlane: {
preferred: "remote-frontend",
remoteFallbackAttempted: false,
remoteFallbackUsed: true,
remoteFirst: true,
remoteHost,
localBackendCoreMissing: null,
classification: null,
},
remoteCommand: artifactRegistryCommandTail(command),
};
}
export function artifactRegistryReadonlyAutoRemotePlan(
action: "status" | "health",
options: ArtifactRegistryOptions,
env: NodeJS.ProcessEnv = process.env,
): Record<string, unknown> {
const remoteHost = remoteFrontendHostFromEnv(env);
return {
enabled: remoteHost !== null,
action,
providerId: options.providerId,
host: remoteHost,
transport: "frontend",
command: remoteHost === null ? null : `bun scripts/cli.ts --main-server-ip ${remoteHost} artifact-registry ${action} --provider-id ${options.providerId}`,
failureClassification: remoteHost === null ? "control-plane-missing" : null,
};
}
function runReadonlyStatusWithRemoteFallback(options: ArtifactRegistryOptions, healthMode: boolean, runtime: ArtifactRegistryCommandRuntime = {}): Record<string, unknown> {
const action = healthMode ? "health" : "status";
const remoteHost = remoteFrontendHostFromEnv(runtime.env ?? process.env);
const shouldPreferRemote = remoteHost !== null && (
Boolean((runtime.env ?? process.env).CODE_QUEUE_SERVICE_ROLE)
|| Boolean((runtime.env ?? process.env).CODE_QUEUE_INSTANCE_ID)
|| Boolean((runtime.env ?? process.env).KUBERNETES_SERVICE_HOST)
|| Boolean((runtime.env ?? process.env).UNIDESK_ARTIFACT_REGISTRY_REMOTE_FIRST)
);
if (shouldPreferRemote) {
const remoteResult = runReadonlyStatusViaRemoteFrontend(options, action, remoteHost, null, runtime);
if (remoteResult !== null) return remoteResult;
}
const localResult = runReadonlyStatus(options, healthMode, runtime);
if (localResult.failureClassification !== "local-docker-required") return localResult;
if (remoteHost === null) return controlPlaneMissingResult(options, action, localResult, null, null, remoteHost);
const remoteResult = runReadonlyStatusViaRemoteFrontend(options, action, remoteHost, localResult, runtime);
if (remoteResult !== null) return remoteResult;
return controlPlaneMissingResult(options, action, localResult, null, null, remoteHost);
}
function runReadonlyStatusViaRemoteFrontend(
options: ArtifactRegistryOptions,
action: "status" | "health",
remoteHost: string,
localResult: Record<string, unknown> | null,
runtime: ArtifactRegistryCommandRuntime,
): Record<string, unknown> | null {
const remoteCommandArgs = [
process.execPath,
rootPath("scripts", "cli.ts"),
"--main-server-ip",
remoteHost,
"artifact-registry",
action,
"--provider-id",
options.providerId,
"--timeout-ms",
String(options.timeoutMs),
];
const remoteTimeoutMs = Math.max(options.timeoutMs + 15_000, 45_000);
const remoteCommand = runtime.runCliForTest === undefined
? runCommand(remoteCommandArgs, repoRoot, { timeoutMs: remoteTimeoutMs })
: runtime.runCliForTest(remoteCommandArgs, remoteTimeoutMs);
const parsed = parsedCliData(remoteCommand.stdout);
const remoteResult = parsed === null ? null : unwrapRemoteArtifactRegistryResult(parsed);
if (remoteCommand.exitCode === 0 && !remoteCommand.timedOut && remoteResult !== null) {
return localResult === null
? annotateRemoteFirstReadonlyResult(remoteResult, remoteHost, remoteCommand)
: annotateRemoteReadonlyResult(remoteResult, localResult, remoteHost, remoteCommand);
}
if (localResult !== null) return controlPlaneMissingResult(options, action, localResult, remoteResult, remoteCommand, remoteHost);
return null;
}
function statusFromValues(options: ArtifactRegistryOptions, values: Record<string, string>, command: CommandResult, healthMode: boolean): Record<string, unknown> {
const commandOk = command.exitCode === 0 && !command.timedOut;
const bundle = renderBundle(options);
const hashes = Object.fromEntries(bundle.files.map((item) => [item.path, item.sha256]));
const checks = {
systemctlAvailable: asBool(values.systemctl_available),
dockerAvailable: asBool(values.docker_available),
curlAvailable: asBool(values.curl_available),
unitExists: asBool(values.unit_exists),
unitActive: values.unit_active === "active",
unitEnabled: values.unit_enabled === "enabled",
composeExists: asBool(values.compose_exists),
configExists: asBool(values.config_exists),
storageExists: asBool(values.storage_exists),
containerRunning: asBool(values.container_running),
loopbackOnly: asBool(values.loopback_only),
v2Ok: values.v2_http_code === "200",
imageMatches: asBool(values.image_matches),
configHashMatches: asBool(values.config_hash_matches),
composeHashMatches: asBool(values.compose_hash_matches),
unitHashMatches: asBool(values.unit_hash_matches),
};
const installed = checks.unitExists || checks.composeExists || checks.configExists || checks.storageExists || checks.containerRunning;
const healthy = commandOk
&& checks.systemctlAvailable
&& checks.dockerAvailable
&& checks.unitExists
&& checks.unitActive
&& checks.composeExists
&& checks.configExists
&& checks.storageExists
&& checks.containerRunning
&& checks.loopbackOnly
&& checks.v2Ok
&& checks.imageMatches
&& checks.configHashMatches
&& checks.composeHashMatches
&& checks.unitHashMatches;
const decision = registryHealthDecision(checks, commandOk);
return {
ok: healthMode ? healthy : commandOk,
readonly: true,
installed,
healthy,
...decision,
remoteCommandShape: readonlyRemoteCommandShape(healthMode ? "health" : "status", options),
checks,
observed: {
unit: { path: bundle.paths.unit, active: values.unit_active, enabled: values.unit_enabled },
compose: { path: bundle.paths.compose, sha256: values.compose_hash ?? null },
config: { path: bundle.paths.config, sha256: values.config_hash ?? null },
storage: { path: bundle.paths.storage },
container: {
name: options.containerName,
running: values.container_running,
status: values.container_status,
image: values.container_image,
restartPolicy: values.container_restart_policy,
},
listener: {
count: Number(values.listener_count ?? 0),
badCount: Number(values.bad_listener_count ?? 0),
loopbackOnly: checks.loopbackOnly,
},
registryApi: { v2HttpCode: values.v2_http_code },
},
expected: {
unitHash: hashes[bundle.paths.unit] ?? "",
composeHash: hashes[bundle.paths.compose] ?? "",
configHash: hashes[bundle.paths.config] ?? "",
image: options.image,
endpoint: `http://${options.host}:${options.port}`,
},
command: commandTail(command),
};
}
function runReadonlyStatus(options: ArtifactRegistryOptions, healthMode: boolean, runtime: ArtifactRegistryCommandRuntime = {}): Record<string, unknown> {
const bundle = renderBundle(options);
const script = statusScript(options, bundle);
const result = runRemoteScript(options, script, options.timeoutMs, runtime);
if (result.exitCode !== 0 || result.timedOut) {
return readonlyCommandFailureResult(options, result, healthMode ? "health" : "status");
}
return statusFromValues(options, parseKeyValueOutput(result.stdout), result, healthMode);
}
export function buildArtifactRegistryReadonlyProbe(action: "status" | "health", options: ArtifactRegistryOptions): ArtifactRegistryReadonlyProbe {
const bundle = renderBundle(options);
const healthMode = action === "health";
return {
action,
providerId: options.providerId,
script: statusScript(options, bundle),
timeoutMs: options.timeoutMs,
healthMode,
options,
remoteCommandShape: readonlyRemoteCommandShape(action, options),
};
}
export function artifactRegistryReadonlyResultFromCommand(
probe: ArtifactRegistryReadonlyProbe,
command: CommandResult,
): Record<string, unknown> {
if (command.exitCode !== 0 || command.timedOut) {
return readonlyCommandFailureResult(probe.options, command, probe.action);
}
return statusFromValues(probe.options, parseKeyValueOutput(command.stdout), command, probe.healthMode);
}
function remoteWriteFileCommand(item: RenderedFile): string {
const encoded = Buffer.from(item.content, "utf8").toString("base64");
const rootOwned = item.path.startsWith("/etc/");
return [
`target=${shellQuote(item.path)}`,
"tmp=$(mktemp /tmp/unidesk-artifact-registry.XXXXXX)",
"trap 'rm -f \"$tmp\"' EXIT",
`printf %s ${shellQuote(encoded)} | base64 -d > "$tmp"`,
`if [ ${rootOwned ? "1" : "0"} = 1 ]; then`,
" root_exec mkdir -p \"$(dirname \"$target\")\"",
` root_exec install -m ${shellQuote(item.mode)} "$tmp" "$target"`,
"else",
" mkdir -p \"$(dirname \"$target\")\"",
` install -m ${shellQuote(item.mode)} "$tmp" "$target"`,
"fi",
`echo ${shellQuote(`artifact_registry_file_written path=${item.path} sha256=${item.sha256}`)}`,
].join("\n");
}
function install(options: ArtifactRegistryOptions): Record<string, unknown> {
const bundle = renderBundle(options);
const script = [
"set -euo pipefail",
"command -v docker >/dev/null",
"docker compose version >/dev/null",
"command -v systemctl >/dev/null",
rootExecPrelude(),
`mkdir -p ${shellQuote(bundle.paths.baseDir)} ${shellQuote(bundle.paths.storage)}`,
...bundle.files.map(remoteWriteFileCommand),
"root_exec systemctl daemon-reload",
`root_exec systemctl enable --now ${shellQuote(options.unitName)}`,
"sleep 2",
`curl -fsS http://${options.host}:${options.port}/v2/ >/dev/null`,
].join("\n");
const command = runRemoteScript(options, script, Math.max(options.timeoutMs, 120_000));
const status = runReadonlyStatus(options, true);
return {
ok: command.exitCode === 0 && !command.timedOut && status.ok === true,
dryRun: false,
mutation: true,
providerId: options.providerId,
render: {
paths: bundle.paths,
files: bundle.files.map((item) => ({ path: item.path, mode: item.mode, sha256: item.sha256 })),
},
installCommand: commandTail(command),
health: status,
};
}
function installDryRun(options: ArtifactRegistryOptions): Record<string, unknown> {
const bundle = renderBundle(options);
return {
ok: true,
dryRun: true,
readonly: true,
mutation: false,
providerId: options.providerId,
plan: plan(options),
render: bundle,
intendedRemoteActions: [
`mkdir -p ${options.baseDir} ${options.storageDir}`,
`write ${bundle.paths.config}`,
`write ${bundle.paths.compose}`,
`write ${bundle.paths.unit}`,
"systemctl daemon-reload",
`systemctl enable --now ${options.unitName}`,
`curl -fsS http://${options.host}:${options.port}/v2/`,
],
note: "Dry run only; no D601 runtime files or services were changed.",
};
}
function composeLockScript(innerScript: string): string {
const lockDir = rootPath(".state", "locks");
const lockPath = rootPath(".state", "locks", "server-compose.lock");
return [
"set -euo pipefail",
`mkdir -p ${shellQuote(lockDir)}`,
`echo ${shellQuote(`compose_lock_wait ${lockPath}`)}`,
`flock ${shellQuote(lockPath)} bash -lc ${shellQuote(innerScript)}`,
].join("; ");
}
function findEnvValueLength(raw: string, key: string): number {
for (const line of raw.split(/\r?\n/u)) {
if (!line.trim() || line.trimStart().startsWith("#")) continue;
const index = line.indexOf("=");
if (index <= 0) continue;
if (line.slice(0, index) !== key) continue;
let value = line.slice(index + 1);
if (value.startsWith("\"") && value.endsWith("\"")) {
try {
value = JSON.parse(value) as string;
} catch {
value = value.slice(1, -1);
}
}
return value.length;
}
return 0;
}
export function runtimeSecretPresenceFromEnvText(envText: string, requirements: RuntimeSecretRequirement[]): RuntimeSecretPresence[] {
return requirements.map((requirement) => {
const length = findEnvValueLength(envText, requirement.sourceEnvName);
return {
sourceEnvName: requirement.sourceEnvName,
containerEnvName: requirement.containerEnvName,
present: length > 0,
length,
};
});
}
export function baiduNetdiskAuthHealthGateStatus(health: unknown): { ok: boolean; requiredFields: string[]; failedFields: string[] } {
const record = typeof health === "object" && health !== null && !Array.isArray(health) ? health as Record<string, unknown> : {};
const auth = typeof record.auth === "object" && record.auth !== null && !Array.isArray(record.auth) ? record.auth as Record<string, unknown> : {};
const failedFields = baiduNetdiskAuthHealthGate.requiredFields.filter((field) => auth[field] !== true);
return {
ok: failedFields.length === 0,
requiredFields: baiduNetdiskAuthHealthGate.requiredFields,
failedFields,
};
}
function composeArtifactRuntime(config: UniDeskConfig, target: ArtifactConsumerTarget): ComposeArtifactRuntime {
if (target.compose === undefined) throw new Error("compose artifact target missing compose config");
const compose = target.compose;
const workDir = compose.workDir ?? config.providerGateway.upgrade.hostProjectRoot;
const composeFile = compose.composeFile ?? config.providerGateway.upgrade.composeFile;
const composeEnvFile = config.providerGateway.upgrade.composeEnvFile;
const envFile = join(workDir, composeEnvFile);
const project = compose.projectHint ?? (config.providerGateway.upgrade.composeProject || config.docker.projectName);
const command = ["docker", "compose", "--env-file", envFile, "-f", join(workDir, composeFile), "-p", project];
return { workDir, composeFile, composeEnvFile, envFile, project, command };
}
function runtimeSecretSourceForTarget(config: UniDeskConfig, target: ArtifactConsumerTarget): RuntimeSecretSource {
const runtime = composeArtifactRuntime(config, target);
return {
kind: "compose-env-file",
path: runtime.envFile,
exists: existsSync(runtime.envFile),
configSource: "config.providerGateway.upgrade.hostProjectRoot + config.providerGateway.upgrade.composeEnvFile",
workDir: runtime.workDir,
composeEnvFile: runtime.composeEnvFile,
composeService: target.compose?.serviceName ?? null,
containerName: target.compose?.containerName ?? null,
valuesPrinted: false,
};
}
function runtimeSecretRecommendedAction(missing: string[]): string {
if (missing.length === 0) return "none";
return "Restore the missing source env keys in the canonical Compose env file without printing values, then rerun deploy apply --env <env> --service <service> --dry-run before any live apply.";
}
function runtimeSecretRecommendedActionForService(contract: RuntimeSecretContract, environment: ArtifactDeployEnvironment, serviceId: string): RuntimeSecretContract {
if (contract.requiredSecretsPresent) return { ...contract, recommendedAction: "none" };
return {
...contract,
recommendedAction: `Restore ${contract.missingSecretKeys.join(", ")} in the canonical Compose env file without printing values, then rerun deploy apply --env ${environment} --service ${serviceId} --dry-run before any live apply.`,
};
}
function runtimeSecretContractFromPresence(
source: RuntimeSecretSource,
requirements: RuntimeSecretRequirement[],
presence: RuntimeSecretPresence[],
): RuntimeSecretContract {
const missingSecretKeys = presence.filter((item) => !item.present).map((item) => item.sourceEnvName);
const requiredSecretsPresent = requirements.length === 0 || (source.exists && missingSecretKeys.length === 0);
return {
check: "runtime-secret-presence",
secretSource: source,
requiredSecretsPresent,
missingSecretKeys,
recommendedAction: runtimeSecretRecommendedAction(missingSecretKeys),
valuesPrinted: false,
requirements: presence.map((item) => ({
sourceEnvName: item.sourceEnvName,
containerEnvName: item.containerEnvName,
present: item.present,
valuePrinted: false,
})),
dryRunDisposition: requirements.length === 0
? "not-required"
: requiredSecretsPresent
? "ready-for-live-apply"
: "secret-source-blocked",
};
}
export function runtimeSecretContractFromEnvText(
envText: string,
requirements: RuntimeSecretRequirement[],
sourceOverrides: Partial<Omit<RuntimeSecretSource, "kind" | "valuesPrinted">> = {},
): RuntimeSecretContract {
const source: RuntimeSecretSource = {
kind: "compose-env-file",
path: sourceOverrides.path ?? "<env-text>",
exists: sourceOverrides.exists ?? true,
configSource: sourceOverrides.configSource ?? "config.providerGateway.upgrade.hostProjectRoot + config.providerGateway.upgrade.composeEnvFile",
workDir: sourceOverrides.workDir ?? "",
composeEnvFile: sourceOverrides.composeEnvFile ?? "",
composeService: sourceOverrides.composeService ?? null,
containerName: sourceOverrides.containerName ?? null,
valuesPrinted: false,
};
return runtimeSecretContractFromPresence(source, requirements, runtimeSecretPresenceFromEnvText(envText, requirements));
}
export function runtimeSecretContractForComposeTarget(
config: UniDeskConfig,
target: ArtifactConsumerTarget,
requirements: RuntimeSecretRequirement[] | undefined,
): RuntimeSecretContract | undefined {
if (requirements === undefined) return undefined;
const source = runtimeSecretSourceForTarget(config, target);
const envText = source.exists ? readFileSync(source.path, "utf8") : "";
return runtimeSecretContractFromPresence(source, requirements, runtimeSecretPresenceFromEnvText(envText, requirements));
}
function composeArtifactSecretPreflight(envFile: string, requirements: RuntimeSecretRequirement[] | undefined): { ok: boolean; envFile: string; requirements: RuntimeSecretPresence[]; missing: RuntimeSecretPresence[]; valuesLogged: false } {
const requirementList = requirements ?? [];
const envText = existsSync(envFile) ? readFileSync(envFile, "utf8") : "";
const presence = runtimeSecretPresenceFromEnvText(envText, requirementList);
const missing = presence.filter((item) => !item.present);
return {
ok: requirementList.length === 0 || (existsSync(envFile) && missing.length === 0),
envFile,
requirements: presence,
missing,
valuesLogged: false,
};
}
function authHealthGateScript(gate: AuthHealthGate | undefined, healthJsonShellArg: string): string[] {
if (gate === undefined) return [];
return [
`auth_health_gate_fields=${shellQuote(gate.requiredFields.join(","))}`,
`python3 - ${healthJsonShellArg} "$auth_health_gate_fields" <<'PY'`,
"import json, sys",
"path, fields_text = sys.argv[1:]",
"fields = [item for item in fields_text.split(',') if item]",
"body = json.load(open(path, encoding='utf-8'))",
"auth = body.get('auth') if isinstance(body, dict) else None",
"auth = auth if isinstance(auth, dict) else {}",
"failed = [field for field in fields if auth.get(field) is not True]",
"if failed:",
" print('baidu_netdisk_auth_health_gate_failed=' + ','.join(failed))",
" raise SystemExit(1)",
"print('baidu_netdisk_auth_health_gate=ok')",
"PY",
];
}
function upsertEnvFileValues(path: string, values: Record<string, string>): void {
const existing = readFileSync(path, "utf8");
const seen = new Set<string>();
const lines = existing.split(/\n/u).filter((line, index, array) => index < array.length - 1 || line.length > 0).map((line) => {
const match = /^([A-Za-z0-9_]+)=/u.exec(line);
if (match === null || values[match[1]] === undefined) return line;
seen.add(match[1]);
return `${match[1]}=${values[match[1]]}`;
});
for (const [key, value] of Object.entries(values)) {
if (!seen.has(key)) lines.push(`${key}=${value}`);
}
writeFileSync(path, `${lines.join("\n")}\n`, "utf8");
}
function pullArtifactFromD601(options: ArtifactRegistryOptions, sourceImage: string): CommandResult {
const remoteScript = [
"set -euo pipefail",
`image=${shellQuote(sourceImage)}`,
"export DOCKER_CONFIG=$(mktemp -d /tmp/unidesk-artifact-docker-config.XXXXXX)",
"trap 'rm -rf \"$DOCKER_CONFIG\"' EXIT",
"printf '{}\\n' > \"$DOCKER_CONFIG/config.json\"",
"docker pull -q \"$image\" >/dev/null",
"docker image inspect \"$image\" --format 'remote_source={{ index .Config.Labels \"unidesk.ai/source-commit\" }} remote_service={{ index .Config.Labels \"unidesk.ai/service-id\" }} remote_dockerfile={{ index .Config.Labels \"unidesk.ai/dockerfile\" }}' >&2",
"docker save \"$image\" | gzip -1",
].join("\n");
const sshCommand = [
process.execPath,
rootPath("scripts", "cli.ts"),
"ssh",
options.providerId,
"argv",
"bash",
"-lc",
remoteScript,
].map(shellQuote).join(" ");
const pipeline = [
"set -euo pipefail",
`${sshCommand} | gzip -dc | docker load`,
].join("\n");
return runCommand(["bash", "-lc", pipeline], repoRoot, { timeoutMs: Math.max(options.timeoutMs, 900_000) });
}
function registryArtifactProbeScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string): string {
const sourceImage = artifactImageRef(options, spec, commit);
return [
"set -euo pipefail",
`registry_image=${shellQuote(sourceImage)}`,
`manifest_url=${shellQuote(`http://127.0.0.1:${options.port}/v2/${spec.registryRepository}/manifests/${commit}`)}`,
"headers=$(mktemp /tmp/unidesk-artifact-manifest.XXXXXX.headers)",
"trap 'rm -f \"$headers\"' EXIT",
"curl -fsSI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' -D \"$headers\" -o /dev/null \"$manifest_url\"",
"manifest_digest=$(awk 'BEGIN{IGNORECASE=1} /^Docker-Content-Digest:/ {gsub(/\\r/, \"\", $2); print $2; exit}' \"$headers\")",
"printf 'registry_image=%s\\nmanifest_url=%s\\nmanifest_digest=%s\\n' \"$registry_image\" \"$manifest_url\" \"$manifest_digest\"",
].join("\n");
}
function registryArtifactMissingMessage(spec: ArtifactConsumerSpec): string {
return `${spec.serviceId} image artifact is missing from D601 registry; run CI artifact publication first`;
}
function verifyLocalArtifactLabels(
localLoadedImage: string,
spec: ArtifactConsumerSpec,
commit: string,
sourceRepo: string,
): Record<string, unknown> | null {
const inspectPulled = runCommand(["docker", "image", "inspect", localLoadedImage, "--format", "{{json .Config.Labels}}"], repoRoot);
const labelCommit = runCommand(["docker", "image", "inspect", localLoadedImage, "--format", "{{ index .Config.Labels \"unidesk.ai/source-commit\" }}"], repoRoot);
const labelService = runCommand(["docker", "image", "inspect", localLoadedImage, "--format", "{{ index .Config.Labels \"unidesk.ai/service-id\" }}"], repoRoot);
const labelRepo = runCommand(["docker", "image", "inspect", localLoadedImage, "--format", "{{ index .Config.Labels \"unidesk.ai/source-repo\" }}"], repoRoot);
const labelDockerfile = runCommand(["docker", "image", "inspect", localLoadedImage, "--format", "{{ index .Config.Labels \"unidesk.ai/dockerfile\" }}"], repoRoot);
const observed = {
commit: labelCommit.stdout.trim(),
serviceId: labelService.stdout.trim(),
sourceRepo: labelRepo.stdout.trim(),
dockerfile: labelDockerfile.stdout.trim(),
labels: inspectPulled.stdout.trim(),
};
const ok = observed.commit === commit
&& observed.serviceId === spec.serviceId
&& observed.sourceRepo === sourceRepo
&& observed.dockerfile === spec.dockerfile;
if (ok) return null;
return {
ok: false,
step: "image-label-verify",
expected: { commit, serviceId: spec.serviceId, sourceRepo, dockerfile: spec.dockerfile },
observed,
};
}
function d601DevFrontendAuthPatchScript(config: UniDeskConfig): string {
const secretData = {
AUTH_USERNAME: base64(config.auth.username),
AUTH_PASSWORD: base64(config.auth.password),
SESSION_SECRET: base64(config.auth.sessionSecret),
};
const configData = {
SESSION_TTL_SECONDS: String(config.auth.sessionTtlSeconds),
};
return [
d601K3sGuardScript(),
`secret_patch=${shellQuote(JSON.stringify({ data: secretData }))}`,
`config_patch=${shellQuote(JSON.stringify({ data: configData }))}`,
"kubectl -n unidesk-dev patch secret unidesk-dev-runtime-secrets --type merge -p \"$secret_patch\"",
"kubectl -n unidesk-dev patch configmap unidesk-dev-runtime-config --type merge -p \"$config_patch\"",
"echo artifact_cd_dev_frontend_auth_synced=ok",
].join("\n");
}
function composeArtifactEnvValues(spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, options: ArtifactRegistryOptions, commit: string): Record<string, string> {
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const prefix = target.compose.deployEnvPrefix;
return {
[`${prefix}_SERVICE_ID`]: spec.serviceId,
[`${prefix}_REF`]: deployRefFor(options, spec),
[`${prefix}_REPO`]: sourceRepoFor(options, spec),
[`${prefix}_COMMIT`]: commit,
[`${prefix}_REQUESTED_COMMIT`]: commit,
};
}
function syntheticComposeHealthDeployScript(compose: NonNullable<ArtifactConsumerTarget["compose"]>, commit: string, sourceRepo: string): string[] {
if (compose.syntheticHealthDeployProof === undefined) return [];
const prefix = compose.deployEnvPrefix;
const envKeys = [
"UNIDESK_DEPLOY_SERVICE_ID",
"UNIDESK_DEPLOY_REF",
"UNIDESK_DEPLOY_REPO",
"UNIDESK_DEPLOY_COMMIT",
"UNIDESK_DEPLOY_REQUESTED_COMMIT",
`${prefix}_SERVICE_ID`,
`${prefix}_REF`,
`${prefix}_REPO`,
`${prefix}_COMMIT`,
`${prefix}_REQUESTED_COMMIT`,
];
const envKeysJson = JSON.stringify(envKeys);
return [
"runtime_metadata_json=$(docker inspect \"$cid\" --format '{{json .Config.Env}}')",
"container_label_commit=$(docker inspect -f '{{ index .Config.Labels \"unidesk.ai/deploy-commit\" }}' \"$cid\")",
"container_label_requested_commit=$(docker inspect -f '{{ index .Config.Labels \"unidesk.ai/deploy-requested-commit\" }}' \"$cid\")",
"container_label_repo=$(docker inspect -f '{{ index .Config.Labels \"unidesk.ai/deploy-repo\" }}' \"$cid\")",
"container_label_ref=$(docker inspect -f '{{ index .Config.Labels \"unidesk.ai/deploy-ref\" }}' \"$cid\")",
`python3 - "$health_json" "$runtime_metadata_json" ${shellQuote(envKeysJson)} ${shellQuote(prefix)} ${shellQuote(commit)} ${shellQuote(sourceRepo)} "$actual_commit" "$actual_service" "$actual_repo" "$container_label_commit" "$container_label_requested_commit" "$container_label_repo" "$container_label_ref" <<'PY'`,
"import json, sys",
"path, env_json, keys_json, prefix, expected_commit, expected_repo, image_commit, image_service, image_repo, label_commit, label_requested_commit, label_repo, label_ref = sys.argv[1:]",
"body = json.load(open(path, encoding='utf-8'))",
"env_items = json.loads(env_json)",
"allow = set(json.loads(keys_json))",
"env = {}",
"for item in env_items:",
" if not isinstance(item, str) or '=' not in item:",
" continue",
" key, value = item.split('=', 1)",
" if key in allow:",
" env[key] = value",
"deploy = body.get('deploy') if isinstance(body, dict) and isinstance(body.get('deploy'), dict) else {}",
"synthetic = {",
" 'serviceId': deploy.get('serviceId') or env.get('UNIDESK_DEPLOY_SERVICE_ID') or env.get(prefix + '_SERVICE_ID') or image_service,",
" 'ref': deploy.get('ref') or env.get('UNIDESK_DEPLOY_REF') or env.get(prefix + '_REF') or label_ref,",
" 'repo': deploy.get('repo') or env.get('UNIDESK_DEPLOY_REPO') or env.get(prefix + '_REPO') or label_repo or image_repo,",
" 'commit': deploy.get('commit') or env.get('UNIDESK_DEPLOY_COMMIT') or env.get(prefix + '_COMMIT') or label_commit or image_commit,",
" 'requestedCommit': deploy.get('requestedCommit') or env.get('UNIDESK_DEPLOY_REQUESTED_COMMIT') or env.get(prefix + '_REQUESTED_COMMIT') or label_requested_commit,",
"}",
"body['deploy'] = synthetic",
"body['deployProof'] = {",
" 'kind': 'compose-container-runtime-metadata',",
" 'sources': ['service-health', 'container-env', 'container-labels', 'image-labels'],",
" 'sourceDirectoryUsed': False,",
" 'envKeysUsed': sorted([key for key in env if key.startswith('UNIDESK_DEPLOY_') or key.startswith(prefix + '_')]),",
"}",
"if synthetic.get('commit') != expected_commit or synthetic.get('requestedCommit') != expected_commit:",
" raise SystemExit('synthetic deploy commit mismatch: ' + json.dumps(synthetic, sort_keys=True))",
"if synthetic.get('repo') != expected_repo:",
" raise SystemExit('synthetic deploy repo mismatch: ' + json.dumps(synthetic, sort_keys=True))",
"if synthetic.get('serviceId') != image_service:",
" raise SystemExit('synthetic deploy service mismatch: ' + json.dumps(synthetic, sort_keys=True))",
"with open(path, 'w', encoding='utf-8') as handle:",
" json.dump(body, handle, separators=(',', ':'))",
" handle.write('\\n')",
"print('synthetic_health_deploy_proof=compose-container-runtime-metadata sourceDirectoryUsed=false')",
"PY",
];
}
async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
const commit = options.commit;
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const config = readConfig();
const runtime = composeArtifactRuntime(config, target);
const secretPreflight = composeArtifactSecretPreflight(runtime.envFile, target.compose.requiredRuntimeSecrets);
const runtimeSecretsRaw = runtimeSecretContractForComposeTarget(config, target, target.compose.requiredRuntimeSecrets);
const runtimeSecrets = runtimeSecretsRaw === undefined
? undefined
: runtimeSecretRecommendedActionForService(runtimeSecretsRaw, options.environment ?? "prod", spec.serviceId);
if (!secretPreflight.ok) {
return {
ok: false,
supported: true,
serviceId: spec.serviceId,
step: "runtime-secret-preflight",
error: "runtime-secret-presence-missing",
environment: options.environment ?? "prod",
envFile: runtime.envFile,
requirements: secretPreflight.requirements,
missing: secretPreflight.missing,
secretSource: runtimeSecrets?.secretSource,
requiredSecretsPresent: runtimeSecrets?.requiredSecretsPresent ?? false,
missingSecretKeys: runtimeSecrets?.missingSecretKeys ?? secretPreflight.missing.map((item) => item.sourceEnvName),
recommendedAction: runtimeSecrets?.recommendedAction ?? "Restore the required runtime secrets in the canonical Compose env file, then rerun artifact deploy.",
runtimeSecrets,
valuesLogged: false,
recoveryHint: target.compose.authHealthGate?.recoveryHint ?? "Restore the required runtime secrets in the canonical Compose env file, then rerun artifact deploy.",
};
}
const health = runReadonlyStatus(options, true);
if (health.ok !== true) {
return { ok: false, serviceId: spec.serviceId, error: "D601 artifact registry is not healthy", health };
}
const sourceImage = artifactImageRef(options, spec, commit);
const sourceRepo = sourceRepoFor(options, spec);
const composeImage = options.targetImage ?? target.targetImage;
const commitImage = `${composeImage}:${commit}`;
const registryProbe = runRemoteScript(options, registryArtifactProbeScript(options, spec, commit), Math.max(options.timeoutMs, 120_000));
if (registryProbe.exitCode !== 0 || registryProbe.timedOut) {
return {
ok: false,
serviceId: spec.serviceId,
step: "registry-artifact-check",
error: registryArtifactMissingMessage(spec),
sourceImage,
registryProbe: commandTail(registryProbe),
};
}
const pull = pullArtifactFromD601(options, sourceImage);
if (pull.exitCode !== 0 || pull.timedOut) {
return {
ok: false,
serviceId: spec.serviceId,
step: "docker-load",
sourceImage,
registryProbe: commandTail(registryProbe),
pull: commandTail(pull),
};
}
const localLoadedImage = sourceImage;
const labelFailure = verifyLocalArtifactLabels(localLoadedImage, spec, commit, sourceRepo);
if (labelFailure !== null) return { ...labelFailure, registryProbe: commandTail(registryProbe) };
const tag = runCommand(["docker", "tag", localLoadedImage, composeImage], repoRoot);
if (tag.exitCode !== 0 || tag.timedOut) {
return { ok: false, step: "docker-tag", targetImage: composeImage, tag: commandTail(tag), registryProbe: commandTail(registryProbe) };
}
const tagCommit = runCommand(["docker", "tag", localLoadedImage, commitImage], repoRoot);
if (tagCommit.exitCode !== 0 || tagCommit.timedOut) {
return { ok: false, step: "docker-tag-commit", targetImage: commitImage, tag: commandTail(tagCommit), registryProbe: commandTail(registryProbe) };
}
upsertEnvFileValues(runtime.envFile, {
...composeArtifactEnvValues(spec, target, options, commit),
});
const compose = runtime.command;
const composeProject = runtime.project;
const serviceName = target.compose.serviceName;
const containerName = target.compose.containerName;
const serviceLogPrefix = spec.serviceId.replace(/-/gu, "_");
const upScript = [
"set -euo pipefail",
`echo ${shellQuote(`${serviceLogPrefix}_artifact_cd source=${sourceImage} target=${composeImage}`)}`,
`${compose.map(shellQuote).join(" ")} up -d --no-build --no-deps --force-recreate ${shellQuote(serviceName)}`,
"ready=0",
"for attempt in $(seq 1 60); do",
` cid=$(docker ps -q --filter label=com.docker.compose.project=${shellQuote(composeProject)} --filter label=com.docker.compose.service=${shellQuote(serviceName)} --filter label=com.docker.compose.oneoff=False | head -1 || true)`,
" if [ -n \"$cid\" ]; then",
" health=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \"$cid\" 2>/dev/null || true)",
` echo "${serviceLogPrefix}_container_probe attempt=$attempt cid=$cid health=$health"`,
" if [ \"$health\" = \"healthy\" ] || [ \"$health\" = \"running\" ]; then ready=1; break; fi",
" else",
` echo "${serviceLogPrefix}_container_probe attempt=$attempt cid=missing"`,
" fi",
" sleep 1",
"done",
"test \"$ready\" = \"1\"",
`cid=$(docker ps -q --filter label=com.docker.compose.project=${shellQuote(composeProject)} --filter label=com.docker.compose.service=${shellQuote(serviceName)} --filter label=com.docker.compose.oneoff=False | head -1)`,
"image_id=$(docker inspect -f '{{.Image}}' \"$cid\")",
"actual_commit=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}' \"$image_id\")",
"actual_service=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/service-id\" }}' \"$image_id\")",
"actual_repo=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/source-repo\" }}' \"$image_id\")",
`test "$actual_commit" = ${shellQuote(commit)}`,
`test "$actual_service" = ${shellQuote(spec.serviceId)}`,
`test "$actual_repo" = ${shellQuote(sourceRepo)}`,
`health_json=/tmp/unidesk-${safeName(spec.serviceId)}-health.json`,
`docker exec "$cid" sh -lc ${shellQuote(target.compose.healthProbeCommand)} > "$health_json"`,
...syntheticComposeHealthDeployScript(target.compose, commit, sourceRepo),
"cat \"$health_json\"",
...authHealthGateScript(target.compose.authHealthGate, "\"$health_json\""),
...(target.compose.requireHealthCommit ? [
"health_commit=$(python3 -c 'import json,sys; print(((json.load(open(sys.argv[1])).get(\"deploy\") or {}).get(\"commit\") or \"\"))' \"$health_json\")",
"health_requested_commit=$(python3 -c 'import json,sys; print(((json.load(open(sys.argv[1])).get(\"deploy\") or {}).get(\"requestedCommit\") or \"\"))' \"$health_json\")",
`test "$health_commit" = ${shellQuote(commit)}`,
`test "$health_requested_commit" = ${shellQuote(commit)}`,
] : []),
].join("\n");
const deploy = runCommand(["bash", "-lc", composeLockScript(upScript)], repoRoot, { timeoutMs: Math.max(options.timeoutMs, 300_000) });
if (deploy.exitCode !== 0 || deploy.timedOut) {
return {
ok: false,
serviceId: spec.serviceId,
step: "compose-recreate",
sourceImage,
targetImage: composeImage,
registryProbe: commandTail(registryProbe),
runtimeSecretPreflight: secretPreflight,
deploy: commandTail(deploy),
recoveryHint: target.compose.authHealthGate?.recoveryHint ?? undefined,
};
}
const running = runCommand(["docker", "inspect", containerName, "--format", "image={{.Config.Image}} imageId={{.Image}} status={{.State.Status}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}"], repoRoot);
return {
ok: true,
supported: true,
serviceId: spec.serviceId,
commit,
providerId: options.providerId,
sourceImage,
registryProbe: commandTail(registryProbe),
localLoadedImage,
targetImage: composeImage,
targetCommitImage: commitImage,
pull: commandTail(pull),
deploy: commandTail(deploy),
running: running.stdout.trim(),
validation: {
liveCommit: commit,
liveRequestedCommit: target.compose.requireHealthCommit ? commit : "not-required",
imageLabelCommit: commit,
serviceHealthCommit: target.compose.requireHealthCommit ? commit : "not-required",
serviceHealthRequestedCommit: target.compose.requireHealthCommit ? commit : "not-required",
runtimeProof: target.compose.syntheticHealthDeployProof === undefined ? "native-health" : target.compose.syntheticHealthDeployProof,
requiredRuntimeSecrets: secretPreflight.requirements,
runtimeSecrets,
authHealthGate: target.compose.authHealthGate === undefined ? "not-required" : {
requiredFields: target.compose.authHealthGate.requiredFields,
passed: true,
},
healthyOldVersionAccepted: false,
},
rollback: {
previousImageHint: `Use docker image ls and docker inspect logs for the previous ${spec.serviceId} image id; Compose named volumes were not changed.`,
commandShape: `${compose.map(shellQuote).join(" ")} up -d --no-build --no-deps --force-recreate ${serviceName}`,
},
};
}
async function deployBackendCoreNow(options: ArtifactRegistryOptions): Promise<Record<string, unknown>> {
if (options.commit === null) throw new Error("artifact-registry deploy-backend-core requires --commit <full-sha>");
const spec = artifactConsumerSpecs["backend-core"];
const target = artifactConsumerTarget(spec, options.environment);
if (target === null) return unsupportedEnvironment(spec, options);
return deployComposeArtifactNow(options, spec, target);
}
function d601ComposeArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const compose = target.compose;
const sourceImage = artifactImageRef(options, spec, commit);
const sourceRepo = sourceRepoFor(options, spec);
const commitImage = target.targetCommitImage(commit);
const envValues = composeArtifactEnvValues(spec, target, options, commit);
const labels = {
"unidesk.ai/deploy-service-id": spec.serviceId,
"unidesk.ai/deploy-ref": deployRefFor(options, spec),
"unidesk.ai/deploy-repo": sourceRepo,
"unidesk.ai/deploy-commit": commit,
"unidesk.ai/deploy-requested-commit": commit,
"unidesk.ai/image-source": sourceImage,
"unidesk.ai/deploy-environment": options.environment ?? "prod",
};
const override = {
services: {
[compose.serviceName]: {
image: "${UNIDESK_ARTIFACT_STABLE_IMAGE}",
labels,
environment: {
UNIDESK_DEPLOY_SERVICE_ID: spec.serviceId,
UNIDESK_DEPLOY_REF: deployRefFor(options, spec),
UNIDESK_DEPLOY_REPO: sourceRepo,
UNIDESK_DEPLOY_COMMIT: commit,
UNIDESK_DEPLOY_REQUESTED_COMMIT: commit,
...envValues,
},
},
},
};
return [
"set -euo pipefail",
`registry_image=${shellQuote(sourceImage)}`,
`stable_image=${shellQuote(target.targetImage)}`,
`commit_image=${shellQuote(commitImage)}`,
`service_id=${shellQuote(spec.serviceId)}`,
`source_repo=${shellQuote(sourceRepo)}`,
`deploy_ref=${shellQuote(deployRefFor(options, spec))}`,
`commit=${shellQuote(commit)}`,
`dockerfile=${shellQuote(spec.dockerfile)}`,
`work_dir=${shellQuote(compose.workDir ?? "/home/ubuntu")}`,
`compose_file=${shellQuote(compose.composeFile ?? "docker-compose.yml")}`,
`compose_service=${shellQuote(compose.serviceName)}`,
`container=${shellQuote(compose.containerName)}`,
`project_hint=${shellQuote(compose.projectHint ?? "")}`,
`health_probe_b64=${shellQuote(base64(compose.healthProbeCommand))}`,
`override_b64=${shellQuote(base64(JSON.stringify(override, null, 2)))}`,
"command -v docker >/dev/null",
"docker compose version >/dev/null",
`curl -fsSI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' ${shellQuote(`http://127.0.0.1:${options.port}/v2/${spec.registryRepository}/manifests/${commit}`)} >/dev/null`,
"docker pull -q \"$registry_image\" >/dev/null",
"label_commit=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}')",
"label_service=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/service-id\" }}')",
"label_repo=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/source-repo\" }}')",
"label_dockerfile=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/dockerfile\" }}')",
"test \"$label_commit\" = \"$commit\"",
"test \"$label_service\" = \"$service_id\"",
"test \"$label_repo\" = \"$source_repo\"",
"test \"$label_dockerfile\" = \"$dockerfile\"",
"test -d \"$work_dir\"",
"test -f \"$work_dir/$compose_file\"",
"docker tag \"$registry_image\" \"$stable_image\"",
"docker tag \"$registry_image\" \"$commit_image\"",
"override=\"$work_dir/.unidesk-artifact-consumer.override.yml\"",
"printf '%s' \"$override_b64\" | base64 -d > \"$override\"",
"export UNIDESK_ARTIFACT_STABLE_IMAGE=\"$stable_image\"",
"project=$(docker inspect \"$container\" --format '{{ index .Config.Labels \"com.docker.compose.project\" }}' 2>/dev/null || true)",
"if [ -z \"$project\" ]; then project=\"$project_hint\"; fi",
"if [ -z \"$project\" ]; then project=$(basename \"$work_dir\"); fi",
"cd \"$work_dir\"",
"docker compose -p \"$project\" -f \"$compose_file\" -f \"$override\" up -d --no-build --no-deps --force-recreate \"$compose_service\"",
"ready=0",
"for attempt in $(seq 1 90); do",
" cid=$(docker ps -q --filter label=com.docker.compose.project=\"$project\" --filter label=com.docker.compose.service=\"$compose_service\" --filter label=com.docker.compose.oneoff=False | head -1 || true)",
" if [ -n \"$cid\" ]; then",
" health=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \"$cid\" 2>/dev/null || true)",
" echo \"artifact_cd_container_probe attempt=$attempt cid=$cid health=$health\"",
" if [ \"$health\" = \"healthy\" ] || [ \"$health\" = \"running\" ]; then ready=1; break; fi",
" else",
" echo \"artifact_cd_container_probe attempt=$attempt cid=missing\"",
" fi",
" sleep 2",
"done",
"test \"$ready\" = \"1\"",
"cid=$(docker ps -q --filter label=com.docker.compose.project=\"$project\" --filter label=com.docker.compose.service=\"$compose_service\" --filter label=com.docker.compose.oneoff=False | head -1)",
"image_id=$(docker inspect -f '{{.Image}}' \"$cid\")",
"actual_commit=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}' \"$image_id\")",
"actual_service=$(docker image inspect -f '{{ index .Config.Labels \"unidesk.ai/service-id\" }}' \"$image_id\")",
"container_commit=$(docker inspect -f '{{ index .Config.Labels \"unidesk.ai/deploy-commit\" }}' \"$cid\")",
"test \"$actual_commit\" = \"$commit\"",
"test \"$actual_service\" = \"$service_id\"",
"test \"$container_commit\" = \"$commit\"",
"health_probe=$(printf '%s' \"$health_probe_b64\" | base64 -d)",
"docker exec \"$cid\" sh -lc \"$health_probe\" >/tmp/unidesk-artifact-health.out",
"cat /tmp/unidesk-artifact-health.out",
...authHealthGateScript(compose.authHealthGate, shellQuote("/tmp/unidesk-artifact-health.out")),
"printf 'artifact_cd_service=%s\\nartifact_cd_source_repo=%s\\nartifact_cd_deploy_ref=%s\\nartifact_cd_source_image=%s\\nartifact_cd_stable_image=%s\\nartifact_cd_runtime_image=%s\\nartifact_cd_commit=%s\\nartifact_cd_container=%s\\nartifact_cd_container_id=%s\\nartifact_cd_image_label_commit=%s\\nartifact_cd_container_label_commit=%s\\n' \"$service_id\" \"$source_repo\" \"$deploy_ref\" \"$registry_image\" \"$stable_image\" \"$commit_image\" \"$commit\" \"$container\" \"$cid\" \"$actual_commit\" \"$container_commit\"",
].join("\n");
}
async function deployD601ComposeArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
const environment = options.environment ?? "prod";
const commit = options.commit;
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const health = runReadonlyStatus(options, true);
if (health.ok !== true) return { ok: false, serviceId: spec.serviceId, error: "D601 artifact registry is not healthy", health };
const sourceImage = artifactImageRef(options, spec, commit);
const registryProbe = runRemoteScript(options, registryArtifactProbeScript(options, spec, commit), Math.max(options.timeoutMs, 120_000));
if (registryProbe.exitCode !== 0 || registryProbe.timedOut) {
return {
ok: false,
supported: true,
serviceId: spec.serviceId,
step: "registry-artifact-check",
error: registryArtifactMissingMessage(spec),
sourceImage,
registryProbe: commandTail(registryProbe),
};
}
const deploy = runRemoteScript(options, d601ComposeArtifactDeployScript(options, spec, target, commit), Math.max(options.timeoutMs, 420_000));
if (deploy.exitCode !== 0 || deploy.timedOut) {
return {
ok: false,
supported: true,
serviceId: spec.serviceId,
step: "d601-compose-artifact-deploy",
sourceImage,
registryProbe: commandTail(registryProbe),
deploy: commandTail(deploy),
rollback: rollbackInfo(spec, target, environment, commit),
};
}
return {
ok: true,
supported: true,
serviceId: spec.serviceId,
environment,
commit,
providerId: options.providerId,
sourceRepo: sourceRepoFor(options, spec),
deployRef: deployRefFor(options, spec),
sourceImage,
targetImage: target.targetImage,
targetCommitImage: target.targetCommitImage(commit),
composeService: target.compose.serviceName,
containerName: target.compose.containerName,
registryProbe: commandTail(registryProbe),
deploy: commandTail(deploy),
validation: {
liveCommit: commit,
liveRequestedCommit: commit,
imageLabelCommit: commit,
containerDeployLabelCommit: commit,
serviceHealthCommit: target.compose.requireHealthCommit ? commit : "not-required",
serviceHealthRequestedCommit: target.compose.requireHealthCommit ? commit : "not-required",
healthyOldVersionAccepted: false,
},
rollback: rollbackInfo(spec, target, environment, commit),
};
}
function deployBackendCoreJob(args: string[], options: ArtifactRegistryOptions): Record<string, unknown> {
if (options.commit === null) throw new Error("artifact-registry deploy-backend-core requires --commit <full-sha>");
const spec = artifactConsumerSpecs["backend-core"];
const target = artifactConsumerTarget(spec, options.environment);
if (target === null) return unsupportedEnvironment(spec, options);
const runArgs = args.includes("--run-now") ? args : [...args, "--run-now"];
const command = [process.execPath, rootPath("scripts", "cli.ts"), "artifact-registry", ...runArgs];
const job = startJob("artifact_registry_backend_core_cd", command, `Pull and deploy backend-core artifact ${options.commit} from D601 registry`);
return {
ok: true,
mode: "async-job",
job,
statusCommand: `bun scripts/cli.ts job status ${job.id}`,
tailCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`,
note: "Backend-core CD continues in the background: D601 registry health, provider-gateway SSH image stream, docker load, retag, no-build recreate, live commit verification.",
};
}
function legacyDeployBackendCoreResult(options: ArtifactRegistryOptions): Record<string, unknown> {
return {
ok: false,
supported: false,
deprecated: true,
action: "deploy-backend-core",
replacement: "bun scripts/cli.ts deploy apply --env prod --service backend-core --commit <full-sha>",
serviceId: "backend-core",
environment: options.environment ?? "prod",
commit: options.commit,
policy: "backend-core production CD must enter through deploy apply --env prod so the standard artifact-consumer guardrails run before any mutation; the legacy artifact-registry deploy-backend-core entry is retained only as a documented compatibility name.",
};
}
function dryRunArtifactConsumerPlan(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): Record<string, unknown> {
const environment = options.environment ?? "prod";
const deployJsonService = deployJsonServiceForOptions(options, spec, environment);
const effectiveOptions = deployJsonService === options.deployJsonService ? options : { ...options, deployJsonService };
const drifts = deployJsonService !== null && hasDeployJsonExecutorContract(deployJsonService)
? compareDeployJsonExecutorMirrors(deployJsonService, environment, artifactRegistryDeployJsonMirrors(effectiveOptions, spec, target))
: [];
if (deployJsonService !== null && drifts.length > 0) return deployJsonDriftResult(deployJsonService, environment, drifts);
const verificationBlocked = spec.runtimeVerification === "blocked";
const livePolicy = artifactConsumerLivePolicy(spec, environment);
const liveReason = artifactConsumerLiveBlockReason(spec, environment);
const requiresSupervisorApproval = livePolicy === "supervisor-only" || spec.serviceId === "code-queue";
const sourceImage = artifactImageRef(effectiveOptions, spec, commit);
const registryEndpoint = `http://127.0.0.1:${effectiveOptions.port}`;
const config = readConfig();
const contractTarget = deployJsonService?.consumer?.target;
const contractRuntime = deployJsonService?.runtime;
const k3sDeployments = target.k3s === undefined
? []
: [
{ name: contractTarget?.deployment ?? target.k3s.deploymentName, containerName: contractTarget?.containerName ?? target.k3s.containerName },
...(contractTarget === undefined ? (target.k3s.extraDeployments ?? []) : []).map((deployment) => typeof deployment === "string"
? { name: deployment, containerName: target.k3s!.containerName }
: { name: deployment.name, containerName: deployment.containerName ?? target.k3s!.containerName }),
];
const common = {
ok: !verificationBlocked,
supported: !verificationBlocked,
dryRun: true,
mutation: false,
error: verificationBlocked ? "runtime-verification-blocked" : undefined,
environment,
providerId: effectiveOptions.providerId,
serviceId: spec.serviceId,
commit,
sourceRepo: sourceRepoFor(effectiveOptions, spec),
deployRef: deployRefFor(effectiveOptions, spec),
sourceImage,
source: {
repo: sourceRepoFor(effectiveOptions, spec),
commit,
dockerfile: spec.dockerfile,
},
registry: {
endpoint: registryEndpoint,
repository: registryRepositoryFor(effectiveOptions, spec),
tag: commit,
imageRef: sourceImage,
digest: null,
digestHeader: "Docker-Content-Digest",
digestSource: "planned registry manifest HEAD; live apply must read this digest before mutation",
},
build: {
willCompile: false,
willRunCargoBuild: false,
willRunDockerBuild: false,
willRunDockerComposeBuild: false,
producerBoundary: spec.serviceId === "backend-core" ? "ci publish-backend-core" : "ci publish-user-service",
},
requiredLabels: {
"unidesk.ai/service-id": spec.serviceId,
"unidesk.ai/source-repo": sourceRepoFor(effectiveOptions, spec),
"unidesk.ai/source-commit": commit,
"unidesk.ai/dockerfile": spec.dockerfile,
},
registryProbe: {
method: "HEAD",
url: `${registryEndpoint}/v2/${registryRepositoryFor(effectiveOptions, spec)}/manifests/${commit}`,
digestHeader: "Docker-Content-Digest",
},
boundary: `${environment} CD is artifact-consumer only: verify commit-pinned registry image, pull/import, deploy, then verify live commit/image/health; it never builds source on the runtime target`,
liveApply: {
policy: livePolicy,
allowed: !verificationBlocked && livePolicy === "enabled",
requiresSupervisorApproval,
reason: liveReason,
},
requiresSupervisorApproval,
selfBootstrapGuard: artifactConsumerSelfBootstrapGuard(spec, environment, requiresSupervisorApproval),
affectedRuntime: spec.serviceId === "code-queue" ? codeQueueAffectedRuntime(environment, true) : undefined,
sourceOfTruth: deployJsonService !== null && hasDeployJsonExecutorContract(deployJsonService) ? deployJsonSourceOfTruth(deployJsonService, environment) : undefined,
driftCheck: deployJsonService !== null && hasDeployJsonExecutorContract(deployJsonService) ? {
ok: true,
mirrors: ["artifact-registry-executor", ...(deployJsonService.consumer?.kind === "d601-k3s-managed" ? ["k8s-manifest"] : [])],
} : undefined,
};
if (spec.kind === "compose" || spec.kind === "d601-compose") {
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const runtimeSecretsRaw = runtimeSecretContractForComposeTarget(config, target, target.compose.requiredRuntimeSecrets);
const runtimeSecrets = runtimeSecretsRaw === undefined ? undefined : runtimeSecretRecommendedActionForService(runtimeSecretsRaw, environment, spec.serviceId);
return {
...common,
target: {
kind: spec.kind,
runtimeHost: spec.kind === "d601-compose" ? "D601" : "main-server",
workDir: target.compose.workDir,
composeFile: target.compose.composeFile,
composeService: target.compose.serviceName,
containerName: target.compose.containerName,
targetImage: options.targetImage ?? target.targetImage,
runtimeImage: target.targetCommitImage(commit),
deployEnvPrefix: target.compose.deployEnvPrefix,
deployCommandShape: `docker compose up -d --no-build --no-deps --force-recreate ${target.compose.serviceName}`,
},
excludedTargets: spec.serviceId === "code-queue-mgr"
? [
{
serviceId: "code-queue",
reason: "code-queue-mgr is the main-server control-plane sidecar only; scheduler/runner live in the separate Code Queue execution plane and are not part of this artifact consumer.",
},
{
services: ["code-queue-scheduler", "code-queue-runner"],
reason: "dry-run does not mutate D601 Code Queue scheduler, runner, tasks, interrupts, or cancellations.",
},
]
: spec.serviceId === "code-queue"
? codeQueueExcludedTargets(environment)
: undefined,
validation: [
"D601 registry /v2 manifest exists for the commit tag",
spec.kind === "d601-compose"
? "D601-pulled image labels match service id, source repo, source commit, and Dockerfile"
: "loaded image labels match service id, source repo, source commit, and Dockerfile",
spec.kind === "d601-compose"
? "running D601 Compose container is recreated with a no-build override that points the service image at the artifact"
: "running Compose container image label matches the requested commit",
...(spec.kind === "d601-compose" ? ["running Compose container image label matches the requested commit"] : []),
verificationBlocked
? `blocked: ${spec.runtimeVerificationBlockReason}`
: target.compose.requireHealthCommit && target.compose.syntheticHealthDeployProof !== undefined
? `${spec.serviceId} runtime health proof synthesizes deploy.commit/deploy.requestedCommit from Compose container runtime metadata and verifies it against image/container labels, not source directory guesses`
: target.compose.requireHealthCommit
? `${spec.serviceId} runtime health probe succeeds and reports deploy.commit/deploy.requestedCommit matching the artifact commit`
: `${spec.serviceId} /health succeeds for the recreated container`,
...(target.compose.requiredRuntimeSecrets === undefined ? [] : [
`${spec.serviceId} live apply checks required runtime secret presence in the canonical Compose env file without reading or printing values`,
]),
...(target.compose.authHealthGate === undefined ? [] : [
`${spec.serviceId} live apply gates success on /health.auth fields: ${target.compose.authHealthGate.requiredFields.join(", ")}`,
]),
],
runtimeSecrets,
authHealthGate: target.compose.authHealthGate === undefined ? undefined : {
path: "/health",
requiredAuthFields: target.compose.authHealthGate.requiredFields,
dryRunDisposition: "pending-live-check",
},
runtimeProof: target.compose.syntheticHealthDeployProof === undefined ? {
kind: "native-health-deploy-metadata",
sourceDirectoryUsed: false,
} : {
...target.compose.syntheticHealthDeployProof,
sourceDirectoryUsed: false,
sources: ["service-health", "container-env", "container-labels", "image-labels"],
requiredEnvKeys: [
"UNIDESK_DEPLOY_SERVICE_ID",
"UNIDESK_DEPLOY_REF",
"UNIDESK_DEPLOY_REPO",
"UNIDESK_DEPLOY_COMMIT",
"UNIDESK_DEPLOY_REQUESTED_COMMIT",
`${target.compose.deployEnvPrefix}_SERVICE_ID`,
`${target.compose.deployEnvPrefix}_REF`,
`${target.compose.deployEnvPrefix}_REPO`,
`${target.compose.deployEnvPrefix}_COMMIT`,
`${target.compose.deployEnvPrefix}_REQUESTED_COMMIT`,
],
},
rollback: rollbackInfo(spec, target, environment, commit),
};
}
return {
...common,
target: {
kind: "d601-k3s",
namespace: contractTarget?.namespace ?? target.k3s?.namespace,
deployment: contractTarget?.deployment ?? target.k3s?.deploymentName,
deployments: k3sDeployments,
service: contractTarget?.service ?? target.k3s?.serviceName,
stableImage: targetImageFor(effectiveOptions, target),
runtimeImage: targetCommitImageFor(effectiveOptions, target, commit),
manifestRepoPath: contractTarget?.manifestRepoPath ?? target.k3s?.manifestRepoPath,
deployCommandShape: "kubectl set image + set env + annotate + rollout status",
},
runtime: contractRuntime === undefined ? undefined : {
sourceOfTruth: "deploy.json",
containerPort: contractRuntime.containerPort,
healthPath: contractRuntime.healthPath,
memory: contractRuntime.memory,
health: contractRuntime.health,
},
excludedTargets: spec.serviceId === "code-queue" ? codeQueueExcludedTargets(environment) : undefined,
validation: [
"D601 registry /v2 manifest exists for the commit tag before mutation",
"D601 Docker-pulled image labels match service id, source repo, source commit, and Dockerfile",
"native k3s containerd has the commit image and stable runtime image tag",
"Deployment annotation and pod image id label match the requested commit",
"service health via Kubernetes API service proxy returns the same deploy.commit and deploy.requestedCommit",
...(environment === "dev" && spec.serviceId === "frontend" ? ["dev frontend auth/session config is synced from main-server config before rollout"] : []),
],
rollback: rollbackInfo(spec, target, environment, commit),
};
}
function rollbackInfo(spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, environment: ArtifactDeployEnvironment, commit: string): Record<string, unknown> {
if (spec.kind === "compose" || spec.kind === "d601-compose") {
const compose = target.compose;
return {
type: spec.kind === "d601-compose" ? "d601-compose-retag-recreate" : "compose-retag-recreate",
composeService: compose?.serviceName,
containerName: compose?.containerName,
workDir: compose?.workDir,
composeFile: compose?.composeFile,
previousImageHint: `Use docker image ls / docker inspect to find the previous labeled ${spec.serviceId} image id; Compose volumes are unchanged.`,
commandShape: `bun scripts/cli.ts deploy apply --env ${environment} --service ${spec.serviceId}${environment === "prod" ? " --commit <previous-full-sha>" : ""}`,
};
}
return {
type: "d601-k3s-previous-commit",
serviceId: spec.serviceId,
environment,
currentCommit: commit,
discovery: `KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n ${target.k3s?.namespace} rollout history deployment/${target.k3s?.deploymentName} && KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl -n ${target.k3s?.namespace} get deployment ${target.k3s?.deploymentName} -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-previous-commit}'`,
commandShape: `bun scripts/cli.ts deploy apply --env ${environment} --service ${spec.serviceId} --commit <previous-full-sha>`,
note: "Rollback is exposed as the same artifact consumer pointed at a previous commit-pinned image that still exists in D601 registry.",
};
}
function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
const environment = options.environment ?? "prod";
if (target.k3s === undefined) throw new Error(`${spec.serviceId} missing k3s artifact consumer config for ${environment}`);
const manifestText = readFileSync(rootPath(target.k3s.manifestRepoPath), "utf8");
const manifestBase64 = Buffer.from(manifestText, "utf8").toString("base64");
const sourceImage = artifactImageRef(options, spec, commit);
const sourceRepo = sourceRepoFor(options, spec);
const commitImage = target.targetCommitImage(commit);
const k3s = target.k3s;
const deploymentSpecs = [
{ name: k3s.deploymentName, containerName: k3s.containerName },
...(k3s.extraDeployments ?? []).map((deployment) => typeof deployment === "string"
? { name: deployment, containerName: k3s.containerName }
: { name: deployment.name, containerName: deployment.containerName ?? k3s.containerName }),
];
const deploymentSpecsBase64 = Buffer.from(JSON.stringify(deploymentSpecs), "utf8").toString("base64");
const labels = [
`unidesk.ai/deploy-service-id=${spec.serviceId}`,
`unidesk.ai/deploy-ref=${deployRefFor(options, spec)}`,
`unidesk.ai/deploy-repo=${sourceRepo}`,
`unidesk.ai/deploy-commit=${commit}`,
`unidesk.ai/deploy-requested-commit=${commit}`,
`unidesk.ai/image-source=${sourceImage}`,
`unidesk.ai/deploy-environment=${environment}`,
];
return [
"set -euo pipefail",
rootExecPrelude(),
`registry_image=${shellQuote(sourceImage)}`,
`stable_image=${shellQuote(target.targetImage)}`,
`commit_image=${shellQuote(commitImage)}`,
`environment=${shellQuote(environment)}`,
`service_id=${shellQuote(spec.serviceId)}`,
`source_repo=${shellQuote(sourceRepo)}`,
`deploy_ref=${shellQuote(deployRefFor(options, spec))}`,
`commit=${shellQuote(commit)}`,
`dockerfile=${shellQuote(spec.dockerfile)}`,
`namespace=${shellQuote(k3s.namespace)}`,
`deployment=${shellQuote(k3s.deploymentName)}`,
`container_name=${shellQuote(k3s.containerName)}`,
`deployment_specs_b64=${shellQuote(deploymentSpecsBase64)}`,
`service_name=${shellQuote(k3s.serviceName)}`,
`service_port=${shellQuote(String(k3s.servicePort))}`,
`health_path=${shellQuote(k3s.healthPath)}`,
`apply_selector=${shellQuote(k3s.applySelector ?? "")}`,
`pod_selector=${shellQuote(k3s.podLabelSelector ?? `app.kubernetes.io/name=${k3s.deploymentName}`)}`,
"archive=",
"manifest=",
"deployment_specs=",
"health_tmp=",
`manifest_repo_path=${shellQuote(k3s.manifestRepoPath)}`,
`manifest_b64=${shellQuote(manifestBase64)}`,
"export DOCKER_CONFIG=$(mktemp -d /tmp/unidesk-artifact-docker-config.XXXXXX)",
"printf '{}\\n' > \"$DOCKER_CONFIG/config.json\"",
"cleanup_artifact_cd() {",
" [ -n \"${archive:-}\" ] && rm -f \"$archive\"",
" [ -n \"${manifest:-}\" ] && rm -f \"$manifest\"",
" [ -n \"${deployment_specs:-}\" ] && rm -f \"$deployment_specs\"",
" [ -n \"${health_tmp:-}\" ] && rm -f \"$health_tmp\"",
" [ -n \"${DOCKER_CONFIG:-}\" ] && rm -rf \"$DOCKER_CONFIG\"",
"}",
"trap cleanup_artifact_cd EXIT",
"command -v docker >/dev/null",
"command -v kubectl >/dev/null",
"command -v ctr >/dev/null",
"test -S /run/k3s/containerd/containerd.sock",
d601K3sGuardScript(),
`curl -fsSI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' ${shellQuote(`http://127.0.0.1:${options.port}/v2/${spec.registryRepository}/manifests/${commit}`)} >/dev/null`,
"docker pull -q \"$registry_image\" >/dev/null",
"label_commit=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}')",
"label_service=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/service-id\" }}')",
"label_repo=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/source-repo\" }}')",
"label_dockerfile=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/dockerfile\" }}')",
"test \"$label_commit\" = \"$commit\"",
"test \"$label_service\" = \"$service_id\"",
"test \"$label_repo\" = \"$source_repo\"",
"test \"$label_dockerfile\" = \"$dockerfile\"",
"docker tag \"$registry_image\" \"$stable_image\"",
"docker tag \"$registry_image\" \"$commit_image\"",
"archive=$(mktemp /tmp/unidesk-artifact-k3s-image.XXXXXX.tar)",
"manifest=$(mktemp /tmp/unidesk-artifact-k3s-manifest.XXXXXX.yaml)",
"docker save \"$commit_image\" \"$stable_image\" -o \"$archive\"",
"root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import \"$archive\" >/dev/null",
"containerd_image_ref() {",
" ref=\"$1\"",
" if root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images inspect \"$ref\" >/dev/null 2>&1; then printf '%s\\n' \"$ref\"; return; fi",
" suffix=\"${ref#*/}\"",
" actual_ref=$(root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images ls -q | grep -F \"/$suffix\" | head -1 || true)",
" test -n \"$actual_ref\"",
" printf '%s\\n' \"$actual_ref\"",
"}",
"stable_containerd_ref=$(containerd_image_ref \"$stable_image\")",
"commit_containerd_ref=$(containerd_image_ref \"$commit_image\")",
"printf '%s' \"$manifest_b64\" | base64 -d > \"$manifest\"",
"deployment_specs=$(mktemp /tmp/unidesk-artifact-k3s-deployments.XXXXXX.json)",
"printf '%s' \"$deployment_specs_b64\" | base64 -d > \"$deployment_specs\"",
"python3 - \"$deployment_specs\" \"$manifest\" <<'PY'",
"import json, sys",
"specs = json.load(open(sys.argv[1], encoding='utf-8'))",
"manifest = open(sys.argv[2], encoding='utf-8').read()",
"missing = [item['name'] for item in specs if f\"name: {item['name']}\" not in manifest]",
"if missing:",
" raise SystemExit('manifest missing deployment(s): ' + ','.join(missing))",
"PY",
"if [ -n \"$apply_selector\" ]; then kubectl apply -f \"$manifest\" -l \"$apply_selector\"; else kubectl apply -f \"$manifest\"; fi",
...(environment === "dev" && spec.serviceId === "frontend" ? [d601DevFrontendAuthPatchScript(readConfig())] : []),
"previous_commit=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-commit}' 2>/dev/null || true)",
"python3 - \"$deployment_specs\" <<'PY' | while IFS=$'\\t' read -r rollout_deployment rollout_container; do",
"import json, sys",
"for item in json.load(open(sys.argv[1], encoding='utf-8')):",
" print(f\"{item['name']}\\t{item['containerName']}\")",
"PY",
" kubectl -n \"$namespace\" set image \"deployment/$rollout_deployment\" \"$rollout_container=$commit_image\"",
" kubectl -n \"$namespace\" set env \"deployment/$rollout_deployment\" \"UNIDESK_DEPLOY_SERVICE_ID=$service_id\" \"UNIDESK_DEPLOY_REF=$deploy_ref\" \"UNIDESK_DEPLOY_REPO=$source_repo\" \"UNIDESK_DEPLOY_COMMIT=$commit\" \"UNIDESK_DEPLOY_REQUESTED_COMMIT=$commit\" \"CODE_QUEUE_DEPLOY_COMMIT=$commit\" \"CODE_QUEUE_DEPLOY_REQUESTED_COMMIT=$commit\"",
` kubectl -n "$namespace" annotate "deployment/$rollout_deployment" ${labels.map(shellQuote).join(" ")} --overwrite`,
" if [ -n \"$previous_commit\" ] && [ \"$previous_commit\" != \"$commit\" ]; then kubectl -n \"$namespace\" annotate \"deployment/$rollout_deployment\" \"unidesk.ai/deploy-previous-commit=$previous_commit\" --overwrite; fi",
"done",
"python3 - \"$deployment_specs\" <<'PY' | while IFS= read -r rollout_deployment; do",
"import json, sys",
"for item in json.load(open(sys.argv[1], encoding='utf-8')):",
" print(item['name'])",
"PY",
" kubectl -n \"$namespace\" rollout status \"deployment/$rollout_deployment\" --timeout=180s",
"done",
"deployment_commit=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-commit}')",
"deployment_requested_commit=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-requested-commit}')",
"test \"$deployment_commit\" = \"$commit\"",
"test \"$deployment_requested_commit\" = \"$commit\"",
"deployment_image=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.spec.template.spec.containers[?(@.name==\"'\"$container_name\"'\")].image}')",
"test \"$deployment_image\" = \"$commit_image\"",
"containerd_config_label() {",
" ref=\"$1\"",
" key=\"$2\"",
" actual_ref=$(containerd_image_ref \"$ref\")",
" target_digest=$(root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images inspect \"$actual_ref\" | python3 -c 'import re,sys; m=re.search(r\"@(sha256:[0-9a-f]+)\", sys.stdin.read()); print(m.group(1) if m else \"\")')",
" test -n \"$target_digest\"",
" digest=\"$target_digest\"",
" config_digest=",
" while [ -n \"$digest\" ]; do",
" content_file=$(mktemp /tmp/unidesk-artifact-content.XXXXXX.json)",
" root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io content get \"$digest\" > \"$content_file\"",
" next=$(python3 - \"$content_file\" <<'PY'",
"import json, sys",
"obj = json.load(open(sys.argv[1], encoding='utf-8'))",
"manifests = obj.get('manifests')",
"if isinstance(manifests, list):",
" for item in manifests:",
" platform = item.get('platform') or {}",
" if platform.get('architecture') in ('amd64', '') or not platform:",
" print('manifest:' + str(item.get('digest') or ''))",
" raise SystemExit(0)",
" print('')",
" raise SystemExit(0)",
"config = obj.get('config') or {}",
"print('config:' + str(config.get('digest') or ''))",
"PY",
" )",
" rm -f \"$content_file\"",
" case \"$next\" in",
" manifest:*) digest=\"${next#manifest:}\" ;;",
" config:*) config_digest=\"${next#config:}\"; break ;;",
" *) digest= ;;",
" esac",
" done",
" test -n \"$config_digest\"",
" config_file=$(mktemp /tmp/unidesk-artifact-config.XXXXXX.json)",
" root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io content get \"$config_digest\" > \"$config_file\"",
" python3 - \"$config_file\" \"$key\" <<'PY'",
"import json, sys",
"cfg = json.load(open(sys.argv[1], encoding='utf-8'))",
"key = sys.argv[2]",
"print(str(((cfg.get('config') or {}).get('Labels') or {}).get(key) or ''))",
"PY",
" rm -f \"$config_file\"",
"}",
"image_label_commit=$(containerd_config_label \"$commit_image\" 'unidesk.ai/source-commit' 2>/tmp/unidesk-artifact-containerd-label.err || true)",
"image_label_service=$(containerd_config_label \"$commit_image\" 'unidesk.ai/service-id' 2>>/tmp/unidesk-artifact-containerd-label.err || true)",
"if [ \"$image_label_commit\" != \"$commit\" ] || [ \"$image_label_service\" != \"$service_id\" ]; then",
" echo \"artifact_cd_containerd_label_fallback=docker-label imageLabelCommit=${image_label_commit:-missing} imageLabelService=${image_label_service:-missing}\"",
" image_label_commit=\"$label_commit\"",
" image_label_service=\"$label_service\"",
"fi",
"test \"$image_label_commit\" = \"$commit\"",
"test \"$image_label_service\" = \"$service_id\"",
"pod=$(kubectl -n \"$namespace\" get pod -l \"$pod_selector\" -o jsonpath='{.items[0].metadata.name}')",
"test -n \"$pod\"",
"pod_image=$(kubectl -n \"$namespace\" get pod \"$pod\" -o jsonpath='{.spec.containers[?(@.name==\"'\"$container_name\"'\")].image}')",
"test \"$pod_image\" = \"$commit_image\"",
"pod_image_id=$(kubectl -n \"$namespace\" get pod \"$pod\" -o jsonpath='{.status.containerStatuses[?(@.name==\"'\"$container_name\"'\")].imageID}')",
"if [ -z \"$pod_image_id\" ]; then pod_image_id=$(kubectl -n \"$namespace\" get pod \"$pod\" -o jsonpath='{.status.containerStatuses[0].imageID}'); fi",
"case \"$health_path\" in /*) ;; *) health_path=\"/$health_path\" ;; esac",
"proxy_path=\"/api/v1/namespaces/$namespace/services/http:$service_name:$service_port/proxy$health_path\"",
"health_tmp=$(mktemp /tmp/unidesk-artifact-health.XXXXXX.json)",
"for attempt in $(seq 1 60); do",
" if kubectl get --raw \"$proxy_path\" > \"$health_tmp\" 2>/tmp/unidesk-artifact-health.err; then",
" health_commit=$(python3 -c 'import json,sys; print(((json.load(open(sys.argv[1])).get(\"deploy\") or {}).get(\"commit\") or \"\"))' \"$health_tmp\" 2>/dev/null || true)",
" health_requested_commit=$(python3 -c 'import json,sys; print(((json.load(open(sys.argv[1])).get(\"deploy\") or {}).get(\"requestedCommit\") or \"\"))' \"$health_tmp\" 2>/dev/null || true)",
" health_ok=$(python3 -c 'import json,sys; print(\"true\" if json.load(open(sys.argv[1])).get(\"ok\") is True else \"false\")' \"$health_tmp\" 2>/dev/null || true)",
" echo \"artifact_cd_health_probe attempt=$attempt ok=$health_ok commit=$health_commit requestedCommit=$health_requested_commit\"",
" if [ \"$health_ok\" = \"true\" ] && [ \"$health_commit\" = \"$commit\" ] && [ \"$health_requested_commit\" = \"$commit\" ]; then break; fi",
" else",
" echo \"artifact_cd_health_probe attempt=$attempt request=failed\"",
" fi",
" if [ \"$attempt\" = \"60\" ]; then echo artifact_cd_health_failed >&2; cat \"$health_tmp\" >&2 || true; exit 1; fi",
" sleep 2",
"done",
"printf 'artifact_cd_service=%s\\nartifact_cd_environment=%s\\nartifact_cd_manifest_repo_path=%s\\nartifact_cd_source_image=%s\\nartifact_cd_stable_image=%s\\nartifact_cd_runtime_image=%s\\nartifact_cd_commit=%s\\nartifact_cd_requested_commit=%s\\nartifact_cd_previous_commit=%s\\nartifact_cd_pod=%s\\nartifact_cd_pod_image=%s\\nartifact_cd_pod_image_id=%s\\nartifact_cd_image_label_commit=%s\\nartifact_cd_health_commit=%s\\nartifact_cd_health_requested_commit=%s\\n' \"$service_id\" \"$environment\" \"$manifest_repo_path\" \"$registry_image\" \"$stable_image\" \"$commit_image\" \"$commit\" \"$deployment_requested_commit\" \"$previous_commit\" \"$pod\" \"$pod_image\" \"$pod_image_id\" \"$image_label_commit\" \"$health_commit\" \"$health_requested_commit\"",
].join("\n");
}
async function deployD601K3sArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
const environment = options.environment ?? "prod";
const commit = options.commit;
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
const health = runReadonlyStatus(options, true);
if (health.ok !== true) return { ok: false, serviceId: spec.serviceId, error: "D601 artifact registry is not healthy", health };
const sourceImage = artifactImageRef(options, spec, commit);
const registryProbe = runRemoteScript(options, registryArtifactProbeScript(options, spec, commit), Math.max(options.timeoutMs, 120_000));
if (registryProbe.exitCode !== 0 || registryProbe.timedOut) {
return {
ok: false,
supported: true,
serviceId: spec.serviceId,
step: "registry-artifact-check",
error: registryArtifactMissingMessage(spec),
sourceImage,
registryProbe: commandTail(registryProbe),
};
}
const deployScript = d601K3sArtifactDeployScript(options, spec, target, commit);
const deploy = runRemoteScript(options, deployScript, Math.max(options.timeoutMs, 420_000));
if (deploy.exitCode !== 0 || deploy.timedOut) {
return {
ok: false,
supported: true,
serviceId: spec.serviceId,
step: "d601-k3s-artifact-deploy",
sourceImage,
registryProbe: commandTail(registryProbe),
deploy: commandTail(deploy),
rollback: rollbackInfo(spec, target, environment, commit),
};
}
return {
ok: true,
supported: true,
serviceId: spec.serviceId,
environment,
commit,
providerId: options.providerId,
sourceRepo: sourceRepoFor(options, spec),
deployRef: deployRefFor(options, spec),
sourceImage,
stableImage: target.targetImage,
runtimeImage: target.targetCommitImage(commit),
manifestRepoPath: target.k3s?.manifestRepoPath,
registryProbe: commandTail(registryProbe),
deploy: commandTail(deploy),
validation: {
liveCommit: commit,
liveRequestedCommit: commit,
imageLabelCommit: commit,
serviceHealthCommit: commit,
serviceHealthRequestedCommit: commit,
healthyOldVersionAccepted: false,
},
rollback: rollbackInfo(spec, target, environment, commit),
};
}
async function deployServiceNow(options: ArtifactRegistryOptions): Promise<Record<string, unknown>> {
if (options.serviceId === null) throw new Error("artifact-registry deploy-service requires --service <id>");
if (options.commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
const spec = artifactConsumerSpec(options.serviceId, options.environment);
if (spec === null) return unsupportedService(options.serviceId, options);
const target = artifactConsumerTarget(spec, options.environment);
if (target === null) return unsupportedEnvironment(spec, options);
if (options.dryRun) return dryRunArtifactConsumerPlan(options, spec, target, options.commit);
const liveBlock = artifactConsumerLiveBlock(spec, options);
if (liveBlock !== null) return liveBlock;
if (spec.kind === "compose") return deployComposeArtifactNow(options, spec, target);
if (spec.kind === "d601-compose") return deployD601ComposeArtifactNow(options, spec, target);
return deployD601K3sArtifactNow(options, spec, target);
}
function deployServiceJob(args: string[], options: ArtifactRegistryOptions): Record<string, unknown> {
if (options.serviceId === null) throw new Error("artifact-registry deploy-service requires --service <id>");
if (options.commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
const spec = artifactConsumerSpec(options.serviceId, options.environment);
if (spec === null) return unsupportedService(options.serviceId, options);
const target = artifactConsumerTarget(spec, options.environment);
if (target === null) return unsupportedEnvironment(spec, options);
if (options.dryRun) return dryRunArtifactConsumerPlan(options, spec, target, options.commit);
const liveBlock = artifactConsumerLiveBlock(spec, options);
if (liveBlock !== null) return liveBlock;
const runArgs = args.includes("--run-now") ? args : [...args, "--run-now"];
const command = [process.execPath, rootPath("scripts", "cli.ts"), "artifact-registry", ...runArgs];
const job = startJob("artifact_registry_service_cd", command, `Pull and deploy ${options.environment ?? "prod"} ${options.serviceId} artifact ${options.commit} from D601 registry`);
return {
ok: true,
mode: "async-job",
serviceId: options.serviceId,
environment: options.environment ?? "prod",
job,
statusCommand: `bun scripts/cli.ts job status ${job.id}`,
tailCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`,
note: "User-service CD continues in the background: D601 registry health, commit-pinned artifact check, pull/import, rollout, image-label and live health commit verification.",
rollback: rollbackInfo(spec, target, options.environment ?? "prod", options.commit),
};
}
function serviceIdFromDeployBackendCoreArgs(action: ArtifactRegistryAction, options: ArtifactRegistryOptions): string | null {
return action === "deploy-backend-core" ? "backend-core" : options.serviceId;
}
function localHelp(): Record<string, unknown> {
return {
ok: true,
command: "artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service",
output: "json",
usage: [
"bun scripts/cli.ts artifact-registry plan [--provider-id D601]",
"bun scripts/cli.ts artifact-registry render [--provider-id D601]",
"bun scripts/cli.ts artifact-registry status [--provider-id D601]",
"bun scripts/cli.ts artifact-registry health [--provider-id D601]",
"bun scripts/cli.ts artifact-registry install [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --service baidu-netdisk --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env prod --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --service frontend --env dev --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service decision-center --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service project-manager --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service oa-event-flow --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service code-queue-mgr --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service todo-note --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service findjob --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service findjob --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service pipeline --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service pipeline --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service met-nonlinear --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service met-nonlinear --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service k3sctl-adapter --commit <full-sha> --dry-run [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service mdtodo --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service mdtodo --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service claudeqq --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env prod --service claudeqq --commit <full-sha> [--dry-run] [--run-now] [--provider-id D601]",
"bun scripts/cli.ts artifact-registry deploy-service --env dev --service code-queue --commit <full-sha> --dry-run [--provider-id D601]",
],
firstStage: "install now writes the rendered systemd/Compose/config files and starts the registry",
artifactConsumers: {
supportedServices: supportedArtifactConsumerServices,
supportedConsumers: supportedArtifactConsumers(),
unsupportedPolicy: "return structured unsupported; never fall back to legacy maintenance-channel source builds",
prodCommands: [
"bun scripts/cli.ts deploy apply --env prod --service backend-core",
"bun scripts/cli.ts deploy apply --env prod --service baidu-netdisk",
"bun scripts/cli.ts deploy apply --env prod --service frontend",
"bun scripts/cli.ts deploy apply --env prod --service decision-center",
"bun scripts/cli.ts deploy apply --env prod --service project-manager",
"bun scripts/cli.ts deploy apply --env prod --service oa-event-flow",
"bun scripts/cli.ts deploy apply --env prod --service code-queue-mgr --dry-run",
"bun scripts/cli.ts deploy apply --env prod --service todo-note",
"bun scripts/cli.ts deploy apply --env prod --service findjob",
"bun scripts/cli.ts deploy apply --env prod --service pipeline",
"bun scripts/cli.ts deploy apply --env prod --service met-nonlinear --dry-run",
"bun scripts/cli.ts deploy apply --env prod --service k3sctl-adapter --dry-run",
"bun scripts/cli.ts deploy apply --env prod --service mdtodo",
"bun scripts/cli.ts deploy apply --env prod --service claudeqq",
],
devCommands: [
"bun scripts/cli.ts deploy apply --env dev --service frontend",
"bun scripts/cli.ts deploy apply --env dev --service decision-center",
"bun scripts/cli.ts deploy apply --env dev --service project-manager --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service oa-event-flow --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service code-queue-mgr --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service todo-note",
"bun scripts/cli.ts deploy apply --env dev --service findjob --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service pipeline --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service met-nonlinear --dry-run",
"bun scripts/cli.ts deploy apply --env dev --service mdtodo",
"bun scripts/cli.ts deploy apply --env dev --service claudeqq",
"bun scripts/cli.ts deploy apply --env dev --service code-queue --dry-run",
],
devOnlyConsumers: ["code-queue"],
rollbackShape: "rerun the same artifact consumer with a previous commit-pinned image",
},
legacyEntrypoints: {
"deploy-backend-core": {
deprecated: true,
enabled: !legacyDeployBackendCoreDisabled,
replacement: "bun scripts/cli.ts deploy apply --env prod --service backend-core --commit <full-sha>",
},
},
defaults: defaultOptions,
};
}
export async function runArtifactRegistryCommand(args: string[], runtime: ArtifactRegistryCommandRuntime = {}): Promise<unknown> {
const action = args[0];
if (isHelpArg(action)) return localHelp();
if (action !== "plan" && action !== "render" && action !== "status" && action !== "health" && action !== "install" && action !== "deploy-backend-core" && action !== "deploy-service") {
throw new Error("artifact-registry usage: plan|render|status|health|install|deploy-backend-core|deploy-service");
}
const typedAction = action as ArtifactRegistryAction;
const options = parseArtifactRegistryOptions(args.slice(1));
if (action === "plan") return plan(options);
if (action === "render") return { ok: true, providerId: options.providerId, render: renderBundle(options) };
if (action === "status") return runReadonlyStatusWithRemoteFallback(options, false, runtime);
if (action === "health") return runReadonlyStatusWithRemoteFallback(options, true, runtime);
if (action === "install") {
return options.dryRun ? installDryRun(options) : install(options);
}
if (action === "deploy-backend-core") {
if (options.commit === null) throw new Error("artifact-registry deploy-backend-core requires --commit <full-sha>");
if (legacyDeployBackendCoreDisabled) return legacyDeployBackendCoreResult(options);
return options.runNow ? await deployBackendCoreNow(options) : deployBackendCoreJob(args, options);
}
if (action === "deploy-service") {
const serviceId = serviceIdFromDeployBackendCoreArgs(typedAction, options);
return options.runNow || options.dryRun ? await deployServiceNow({ ...options, serviceId }) : deployServiceJob(args, { ...options, serviceId });
}
throw new Error("unreachable artifact-registry action");
}