// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. install module for scripts/src/artifact-registry.ts. // Moved mechanically from scripts/src/artifact-registry.ts:2117-2231 for #903. import { createHash } from "node:crypto"; import { existsSync, readFileSync, rmSync, 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"; import { composeRuntimeEnvValue } from "../runtime-env"; import type { ArtifactRegistryOptions, RenderedFile, RuntimeSecretPresence, RuntimeSecretRequirement } from "./types"; import { renderBundle, rootExecPrelude, shellQuote } from "./bundle"; import { commandTail } from "./consumer"; import { runReadonlyStatus } from "./readonly"; import { runRemoteScript } from "./remote"; import { plan } from "./status"; export 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"); } export function install(options: ArtifactRegistryOptions): Record { 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, }; } export function installDryRun(options: ArtifactRegistryOptions): Record { 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.", }; } export 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("; "); } export 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, }; }); }