Files
pikasTech-HWLAB/scripts/g14-gitops-render.mjs
T
2026-05-31 11:45:10 +08:00

4244 lines
187 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const defaultOutDir = "deploy/gitops/g14";
const defaultRegistryPrefix = process.env.HWLAB_G14_REGISTRY_PREFIX || "127.0.0.1:5000/hwlab";
const defaultSourceRepo = process.env.HWLAB_G14_GIT_URL || "git@github.com:pikasTech/HWLAB.git";
const defaultGitReadUrl = process.env.HWLAB_G14_GIT_READ_URL || null;
const defaultV02GitReadUrl = process.env.HWLAB_V02_GIT_READ_URL || "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
const defaultV02GitWriteUrl = process.env.HWLAB_V02_GIT_WRITE_URL || "http://git-mirror-write.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
const defaultBranch = process.env.HWLAB_G14_BRANCH || "G14";
const defaultGitopsBranch = process.env.HWLAB_G14_GITOPS_BRANCH || "G14-gitops";
const defaultCatalogPath = process.env.HWLAB_ARTIFACT_CATALOG_PATH || "deploy/artifact-catalog.dev.json";
const defaultRuntimeEndpoint = process.env.HWLAB_G14_RUNTIME_ENDPOINT || "http://74.48.78.17:17667";
const defaultWebEndpoint = process.env.HWLAB_G14_WEB_ENDPOINT || "http://74.48.78.17:17666";
const defaultProdRuntimeEndpoint = process.env.HWLAB_G14_PROD_RUNTIME_ENDPOINT || "http://74.48.78.17:18667";
const defaultProdWebEndpoint = process.env.HWLAB_G14_PROD_WEB_ENDPOINT || "http://74.48.78.17:18666";
const defaultV02RuntimeEndpoint = process.env.HWLAB_V02_RUNTIME_ENDPOINT || "http://74.48.78.17:19667";
const defaultV02WebEndpoint = process.env.HWLAB_V02_WEB_ENDPOINT || "http://74.48.78.17:19666";
const defaultProxyUrl = process.env.HWLAB_G14_PROXY_URL || "http://127.0.0.1:10808";
const defaultAllProxyUrl = process.env.HWLAB_G14_ALL_PROXY_URL || "socks5h://127.0.0.1:10808";
const defaultNoProxy = process.env.HWLAB_G14_NO_PROXY || "hyueapi.com,.hyueapi.com,127.0.0.1,localhost,::1,10.0.0.0/8,10.42.0.0/16,10.43.0.0/16,192.168.0.0/16,.svc,.cluster.local,kubernetes.default.svc,registry.hwlab-ci.svc,hwlab-registry.hwlab-ci.svc,git-mirror-http,git-mirror-http.devops-infra,git-mirror-http.devops-infra.svc,git-mirror-http.devops-infra.svc.cluster.local,git-mirror-write,git-mirror-write.devops-infra,git-mirror-write.devops-infra.svc,git-mirror-write.devops-infra.svc.cluster.local";
const ciToolsRunnerImage = process.env.HWLAB_G14_CI_TOOLS_IMAGE || "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1";
const buildkitRunnerImage = process.env.HWLAB_G14_BUILDKIT_IMAGE || "moby/buildkit:rootless";
const defaultDevBaseImage = process.env.HWLAB_G14_DEV_BASE_IMAGE || "127.0.0.1:5000/hwlab/hwlab-node20-base:20-bookworm-slim";
const defaultServiceIds = Object.freeze([
"hwlab-cloud-api",
"hwlab-cloud-web",
"hwlab-agent-mgr",
"hwlab-agent-worker",
"hwlab-device-pod",
"hwlab-gateway",
"hwlab-edge-proxy",
"hwlab-agent-skills"
]);
const v02RuntimeServiceIds = Object.freeze([
"hwlab-cloud-api",
"hwlab-cloud-web",
"hwlab-agent-mgr",
"hwlab-agent-worker",
"hwlab-device-pod",
"hwlab-gateway",
"hwlab-edge-proxy",
"hwlab-agent-skills"
]);
const v02RemovedServiceIds = new Set([
"hwlab-router",
"hwlab-tunnel-client",
"hwlab-gateway-simu",
"hwlab-box-simu",
"hwlab-patch-panel"
]);
const v02RemovedServiceEnvNames = new Set([
"HWLAB_M3_GATEWAY_SIMU_1_URL",
"HWLAB_M3_GATEWAY_SIMU_2_URL",
"HWLAB_M3_PATCH_PANEL_URL",
"HWLAB_GATEWAY_SIMU_URL",
"HWLAB_BOX_SIMU_URL",
"HWLAB_PATCH_PANEL_URL",
"HWLAB_ROUTER_URL",
"HWLAB_TUNNEL_CLIENT_URL"
]);
const moonBridgeSourceRef = process.env.HWLAB_G14_MOONBRIDGE_REF || "1b99888d3dae889b79ee602cb875c7907f7e76f2";
const moonBridgeImage = process.env.HWLAB_G14_MOONBRIDGE_IMAGE || `${defaultRegistryPrefix}/moonbridge:${moonBridgeSourceRef.slice(0, 12)}`;
const deepSeekProfileModel = process.env.HWLAB_G14_DEEPSEEK_PROFILE_MODEL || "deepseek-chat";
const codexApiProfileModel = process.env.HWLAB_G14_CODEX_API_PROFILE_MODEL || "gpt-5.5";
const codexApiProfileBaseUrl = process.env.HWLAB_G14_CODEX_API_PROFILE_BASE_URL || "http://127.0.0.1:49280/responses";
const codexApiForwarderPort = process.env.HWLAB_G14_CODEX_API_FORWARDER_PORT || "49280";
const codexApiForwarderUpstreamBaseUrl = process.env.HWLAB_G14_CODEX_API_UPSTREAM_BASE_URL || "https://hyueapi.com";
const sourceCommitPattern = /^[a-f0-9]{7,40}$/u;
const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout";
const primitiveValidationTasks = Object.freeze([
{
name: "repo-reports-guard",
commands: ["node scripts/repo-reports-guard.mjs"]
},
{
name: "g14-contract-check",
commands: [
"node --check scripts/g14-gitops-render.mjs",
"render_check_revision=$(git rev-parse HEAD)",
"render_check_dir=$(mktemp -d)",
"node scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --source-revision \"$render_check_revision\"",
"node scripts/g14-gitops-render.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --image-tag-mode \"$(params.image-tag-mode)\" --source-branch \"$(params.source-branch)\" --gitops-branch \"$(params.gitops-branch)\" --out \"$render_check_dir\" --check --source-revision \"$render_check_revision\"",
`node --input-type=module <<'NODE'
import { readdirSync, readFileSync, statSync } from "node:fs";
import path from "node:path";
const root = process.cwd();
const ciFileName = "CI" + ".json";
const forbiddenCiFragments = [
"ci" + "-json",
"HWLAB_G14_TEKTON" + "_SINGLE_IMAGE_PUBLISH",
"single" + "-dind",
"docker" + ":29",
"DOCKER" + "_HOST",
"Docker" + "-in-Docker",
"D" + "IND",
"docker" + " push",
"--build" + "-backend",
"HWLAB_ARTIFACT" + "_BUILD_BACKEND",
"--legacy" + "-source-images",
"HWLAB_G14_USE_DEPLOY_IMAGES" + "=0"
];
const scanTargets = [
"scripts/g14-gitops-render.mjs",
"scripts/artifact-publish.mjs",
"scripts/g14-artifact-publish.mjs",
"scripts/src/g14-ci-plan-lib.mjs",
"deploy/gitops/g14/tekton"
];
const skipDirs = new Set([".git", "node_modules", ".worktree"]);
function walkFiles(relativePath) {
const absolutePath = path.join(root, relativePath);
let stat;
try {
stat = statSync(absolutePath);
} catch {
return [];
}
if (stat.isFile()) return [relativePath];
if (!stat.isDirectory()) return [];
const files = [];
for (const entry of readdirSync(absolutePath, { withFileTypes: true })) {
if (entry.isDirectory() && skipDirs.has(entry.name)) continue;
files.push(...walkFiles(path.join(relativePath, entry.name)));
}
return files;
}
const ciFiles = walkFiles(".").filter((filePath) => path.basename(filePath) === ciFileName);
const violations = [];
for (const filePath of scanTargets.flatMap(walkFiles)) {
const text = readFileSync(path.join(root, filePath), "utf8");
for (const fragment of forbiddenCiFragments) {
if (text.includes(fragment)) violations.push({ filePath, fragment });
}
}
if (ciFiles.length || violations.length) {
console.error(JSON.stringify({ status: "failed", ciFiles, violations }, null, 2));
process.exit(1);
}
NODE`,
"node --check scripts/artifact-publish.mjs",
"node --check scripts/g14-artifact-publish.mjs"
]
},
{
name: "codex-api-forwarder-check",
commands: [
"node scripts/run-bun.mjs test cmd/hwlab-codex-api-responses-forwarder/main.test.ts"
]
}
]);
function proxyEnv() {
return [
{ name: "HTTP_PROXY", value: defaultProxyUrl },
{ name: "HTTPS_PROXY", value: defaultProxyUrl },
{ name: "ALL_PROXY", value: defaultAllProxyUrl },
{ name: "NO_PROXY", value: defaultNoProxy },
{ name: "http_proxy", value: defaultProxyUrl },
{ name: "https_proxy", value: defaultProxyUrl },
{ name: "all_proxy", value: defaultAllProxyUrl },
{ name: "no_proxy", value: defaultNoProxy }
];
}
function parseArgs(argv) {
const args = {
lane: process.env.HWLAB_GITOPS_LANE || "g14",
outDir: defaultOutDir,
catalogPath: defaultCatalogPath,
imageTagMode: process.env.HWLAB_ARTIFACT_IMAGE_TAG_MODE || "short",
registryPrefix: defaultRegistryPrefix,
sourceRevision: null,
sourceBranch: defaultBranch,
gitopsBranch: defaultGitopsBranch,
sourceRepo: defaultSourceRepo,
gitReadUrl: defaultGitReadUrl,
gitWriteUrl: null,
runtimeEndpoint: defaultRuntimeEndpoint,
webEndpoint: defaultWebEndpoint,
prodRuntimeEndpoint: defaultProdRuntimeEndpoint,
prodWebEndpoint: defaultProdWebEndpoint,
useDeployImages: true,
check: false,
write: true,
help: false
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--lane") args.lane = readOption(argv, ++index, arg);
else if (arg === "--out") args.outDir = readOption(argv, ++index, arg);
else if (arg === "--catalog-path") args.catalogPath = readOption(argv, ++index, arg);
else if (arg === "--image-tag-mode") args.imageTagMode = readOption(argv, ++index, arg);
else if (arg === "--registry-prefix") args.registryPrefix = readOption(argv, ++index, arg).replace(/\/+$/u, "");
else if (arg === "--source-revision") args.sourceRevision = readOption(argv, ++index, arg);
else if (arg === "--source-branch") args.sourceBranch = readOption(argv, ++index, arg);
else if (arg === "--gitops-branch") args.gitopsBranch = readOption(argv, ++index, arg);
else if (arg === "--source-repo") args.sourceRepo = readOption(argv, ++index, arg);
else if (arg === "--git-read-url") args.gitReadUrl = readOption(argv, ++index, arg);
else if (arg === "--git-write-url") args.gitWriteUrl = readOption(argv, ++index, arg);
else if (arg === "--runtime-endpoint") args.runtimeEndpoint = readOption(argv, ++index, arg);
else if (arg === "--web-endpoint") args.webEndpoint = readOption(argv, ++index, arg);
else if (arg === "--prod-runtime-endpoint") args.prodRuntimeEndpoint = readOption(argv, ++index, arg);
else if (arg === "--prod-web-endpoint") args.prodWebEndpoint = readOption(argv, ++index, arg);
else if (arg === "--use-deploy-images") args.useDeployImages = true;
else if (arg === "--check") {
args.check = true;
args.write = false;
} else if (arg === "--no-write") args.write = false;
else if (arg === "--help" || arg === "-h") args.help = true;
else throw new Error(`unknown argument ${arg}`);
}
if (args.lane === "v02") {
if (args.sourceBranch === defaultBranch) args.sourceBranch = "v0.2";
if (args.gitopsBranch === defaultGitopsBranch) args.gitopsBranch = "v0.2-gitops";
if (args.catalogPath === defaultCatalogPath) args.catalogPath = "deploy/artifact-catalog.v02.json";
if (args.runtimeEndpoint === defaultRuntimeEndpoint) args.runtimeEndpoint = defaultV02RuntimeEndpoint;
if (args.webEndpoint === defaultWebEndpoint) args.webEndpoint = defaultV02WebEndpoint;
if (args.imageTagMode === "short") args.imageTagMode = "full";
if (!args.gitReadUrl) args.gitReadUrl = defaultV02GitReadUrl;
if (!args.gitWriteUrl) args.gitWriteUrl = defaultV02GitWriteUrl;
}
if (!args.gitReadUrl) args.gitReadUrl = args.sourceRepo;
if (!args.gitWriteUrl) args.gitWriteUrl = args.sourceRepo;
assert.ok(["g14", "v02"].includes(args.lane), `unknown lane ${args.lane}`);
assert.ok(["short", "full"].includes(args.imageTagMode), `unknown image tag mode ${args.imageTagMode}`);
if (args.lane === "v02") {
assert.equal(args.sourceBranch, "v0.2", "v02 source branch must be v0.2");
assert.equal(args.gitopsBranch, "v0.2-gitops", "v02 GitOps branch must be v0.2-gitops");
}
return args;
}
function readOption(argv, index, name) {
const value = argv[index];
if (!value || value.startsWith("--")) throw new Error(`${name} requires a value`);
return value;
}
function usage() {
return [
"usage: node scripts/g14-gitops-render.mjs [options]",
"",
"Render G14-only Kubernetes-native CI/CD manifests from built-in Tekton CI primitives, deploy/deploy.json, and deploy/k8s/*.",
"",
"options:",
" --lane LANE g14 or v02; default: g14",
` --out DIR default: ${defaultOutDir}`,
` --catalog-path PATH default: ${defaultCatalogPath}`,
" --image-tag-mode MODE short or full; v02 uses full source commit IDs",
` --source-repo URL default: ${defaultSourceRepo}`,
" --git-read-url URL read-only source URL; v02 defaults to devops-infra git mirror",
" --git-write-url URL GitOps write URL; v02 defaults to devops-infra git mirror write relay",
` --source-branch BRANCH default: ${defaultBranch}`,
` --gitops-branch BRANCH default: ${defaultGitopsBranch}`,
" --source-revision SHA source commit used for image tags and runtime annotations; default: git rev-parse HEAD",
` --registry-prefix PREFIX default: ${defaultRegistryPrefix}`,
` --runtime-endpoint URL default: ${defaultRuntimeEndpoint}`,
` --web-endpoint URL default: ${defaultWebEndpoint}`,
` --prod-runtime-endpoint URL default: ${defaultProdRuntimeEndpoint}`,
` --prod-web-endpoint URL default: ${defaultProdWebEndpoint}`,
" --use-deploy-images default and only supported mode: render workload images from deploy/artifact-catalog.dev.json per service",
" --check verify generated files are current without writing",
" --no-write plan and print generated file list without writing"
].join("\n");
}
function generatedPath(filePath) {
return path.isAbsolute(filePath) ? filePath : path.join(repoRoot, filePath);
}
async function readJson(relativePath) {
return JSON.parse(await readFile(path.join(repoRoot, relativePath), "utf8"));
}
async function readJsonIfPresent(relativePath, fallback = null) {
try {
return await readJson(relativePath);
} catch (error) {
if (error && error.code === "ENOENT") return fallback;
throw error;
}
}
async function gitValue(args) {
const result = await execFileAsync("git", args, { cwd: repoRoot, timeout: 10000, maxBuffer: 1024 * 1024 });
return result.stdout.trim();
}
async function resolveSourceRevision(value) {
const revision = value || await gitValue(["rev-parse", "HEAD"]);
const full = sourceCommitPattern.test(revision) && revision.length === 40
? revision
: await gitValue(["rev-parse", revision]);
assert.match(full, /^[a-f0-9]{40}$/u, "source revision must resolve to a 40-char git commit");
return { full, short: full.slice(0, 7) };
}
function jsonManifest(value) {
return `${JSON.stringify(value, null, 2)}\n`;
}
function textFile(value) {
return value.endsWith("\n") ? value : `${value}\n`;
}
function cloneJson(value) {
return JSON.parse(JSON.stringify(value));
}
function ensureObject(value, name) {
assert.ok(value && typeof value === "object" && !Array.isArray(value), `${name} must be an object`);
return value;
}
function asItems(list, name) {
assert.equal(list?.kind, "List", `${name} must be a Kubernetes List`);
assert.ok(Array.isArray(list.items), `${name}.items must be an array`);
return list.items;
}
function serviceIdForWorkload(item, container) {
return (
item?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
item?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
container?.name
);
}
function upsertEnv(envList, name, value) {
if (!Array.isArray(envList)) return;
const existing = envList.find((entry) => entry?.name === name);
if (existing) {
delete existing.valueFrom;
existing.value = value;
} else {
envList.push({ name, value });
}
}
function upsertEnvEntry(envList, entry) {
if (!Array.isArray(envList) || !entry?.name) return;
const existing = envList.find((item) => item?.name === entry.name);
if (existing) {
delete existing.value;
delete existing.valueFrom;
Object.assign(existing, cloneJson(entry));
} else {
envList.push(cloneJson(entry));
}
}
function syncCodeAgentWorkspaceInitContainer(initContainers, { image, runtimeCommit, runtimeImageTag }) {
const initContainer = Array.isArray(initContainers)
? initContainers.find((entry) => entry?.name === "hwlab-code-agent-workspace-init")
: null;
if (!initContainer) return;
initContainer.image = image;
initContainer.imagePullPolicy = "IfNotPresent";
initContainer.env ??= [];
upsertEnv(initContainer.env, "HWLAB_WORKSPACE_SOURCE_COMMIT", runtimeCommit);
upsertEnv(initContainer.env, "HWLAB_IMAGE", image);
upsertEnv(initContainer.env, "HWLAB_IMAGE_TAG", runtimeImageTag);
}
function annotate(metadata, annotations) {
metadata.annotations = { ...(metadata.annotations ?? {}), ...annotations };
}
function label(metadata, labels) {
metadata.labels = { ...(metadata.labels ?? {}), ...labels };
}
function imageFor(registryPrefix, serviceId, imageTag) {
return `${registryPrefix}/${serviceId}:${imageTag}`;
}
function imageTagFromReference(image) {
const value = String(image ?? "");
const slashIndex = value.lastIndexOf("/");
const colonIndex = value.lastIndexOf(":");
if (colonIndex <= slashIndex) return null;
return value.slice(colonIndex + 1).split("@", 1)[0] || null;
}
function repositoryFromImage(image) {
const value = String(image ?? "").split("@", 1)[0];
const slashIndex = value.lastIndexOf("/");
const colonIndex = value.lastIndexOf(":");
return colonIndex > slashIndex ? value.slice(0, colonIndex) : value;
}
function digestPinnedImage(image, digest) {
if (!/^sha256:[a-f0-9]{64}$/u.test(String(digest ?? ""))) return image;
const repository = repositoryFromImage(image);
return repository ? `${repository}@${digest}` : image;
}
function catalogServiceById(catalog, serviceId) {
return (catalog?.services ?? []).find((service) => service?.serviceId === serviceId) ?? null;
}
function v02EnvReuseEnabled(catalog, serviceId) {
const service = catalogServiceById(catalog, serviceId);
return service?.runtimeMode === v02EnvReuseRuntimeMode || service?.envReuse === true;
}
function bootShForService(deployService, serviceId) {
const value = deployService?.bootSh || deployService?.bootScript || `deploy/runtime/boot/${serviceId}.sh`;
const text = String(value).replaceAll("\\", "/").replace(/^\.\//u, "");
assert.ok(text && !text.startsWith("/") && !text.split("/").includes(".."), `${serviceId} boot script must be repo-relative`);
return text;
}
function bootMetadataForService({ args, catalog, deployService, serviceId, source }) {
const catalogService = catalogServiceById(catalog, serviceId);
const environmentImage = catalogService?.environmentImage ?? imageFor(args.registryPrefix, `${serviceId}-env`, `env-${String(catalogService?.environmentInputHash ?? source.full).slice(0, 12)}`);
const environmentDigest = catalogService?.environmentDigest ?? catalogService?.digest ?? null;
return {
runtimeMode: v02EnvReuseRuntimeMode,
environmentImage,
environmentDigest,
environmentInputHash: catalogService?.environmentInputHash ?? null,
codeInputHash: catalogService?.codeInputHash ?? null,
bootRepo: catalogService?.bootRepo ?? args.sourceRepo,
bootCommit: catalogService?.bootCommit ?? source.full,
bootSh: catalogService?.bootSh ?? bootShForService(deployService, serviceId)
};
}
function runtimeArtifactForService({ catalog, serviceId, source, registryPrefix, useDeployImages = false, digestPin = false }) {
const catalogService = catalogServiceById(catalog, serviceId);
if (catalogService?.runtimeMode === v02EnvReuseRuntimeMode || catalogService?.envReuse === true) {
const environmentImage = catalogService.environmentImage ?? imageFor(registryPrefix, `${serviceId}-env`, `env-${String(catalogService.environmentInputHash ?? source.full).slice(0, 12)}`);
const runtimeImage = digestPin && useDeployImages ? digestPinnedImage(environmentImage, catalogService.environmentDigest ?? catalogService.digest) : environmentImage;
return {
image: runtimeImage,
imageTag: imageTagFromReference(environmentImage) ?? source.imageTag ?? source.short,
commit: catalogService.bootCommit ?? source.full,
runtimeMode: v02EnvReuseRuntimeMode,
environmentDigest: catalogService.environmentDigest ?? catalogService.digest ?? null
};
}
const fallbackImageTag = source.imageTag ?? source.short;
const image = useDeployImages && catalogService?.image
? catalogService.image
: imageFor(registryPrefix, serviceId, fallbackImageTag);
const runtimeImage = digestPin && useDeployImages ? digestPinnedImage(image, catalogService?.digest) : image;
const imageTag = useDeployImages && catalogService?.imageTag
? catalogService.imageTag
: imageTagFromReference(image) ?? fallbackImageTag;
const commit = useDeployImages
? catalogService?.sourceCommitId ?? catalogService?.commitId ?? imageTag ?? source.full
: source.full;
return { image: runtimeImage, imageTag, commit };
}
function runtimeImageForService(options) {
return runtimeArtifactForService(options).image;
}
function runtimeCommitForService(options) {
return runtimeArtifactForService(options).commit;
}
function runtimeImageTagForService(options) {
return runtimeArtifactForService(options).imageTag;
}
function artifactEnvironment(args) {
return args.lane === "v02" ? "v02" : "dev";
}
function artifactEndpoint(args) {
return args.lane === "v02" ? defaultV02RuntimeEndpoint : defaultRuntimeEndpoint;
}
function serviceIdsForLane(lane) {
return lane === "v02" ? v02RuntimeServiceIds : defaultServiceIds;
}
function serviceIdsForProfile(profile) {
return profile === "v02" ? v02RuntimeServiceIds : defaultServiceIds;
}
function servicesParamForLane(lane) {
return serviceIdsForLane(lane).join(",");
}
function isV02RemovedServiceId(serviceId) {
return v02RemovedServiceIds.has(serviceId);
}
function artifactCatalogSkeleton({ args, source }) {
const environment = artifactEnvironment(args);
const namespace = args.lane === "v02" ? "hwlab-v02" : "hwlab-dev";
const tag = imageTagForSource(args, source);
return {
catalogVersion: "v1",
kind: "hwlab-artifact-catalog",
environment,
profile: environment,
namespace,
endpoint: artifactEndpoint(args),
commitId: tag,
artifactState: "contract-skeleton",
publish: {
registryPrefix: args.registryPrefix,
sourceCommitId: source.full,
imageTag: tag,
publishedAt: null
},
allowedProfiles: [environment],
forbiddenProfiles: args.lane === "v02" ? ["dev", "prod"] : ["prod"],
services: serviceIdsForLane(args.lane).map((serviceId) => artifactCatalogSkeletonService({ args, source, serviceId, environment, namespace, tag }))
};
}
function artifactCatalogSkeletonService({ args, source, serviceId, environment, namespace, tag }) {
const base = {
serviceId,
profile: environment,
namespace,
commitId: tag,
sourceCommitId: source.full,
image: imageFor(args.registryPrefix, serviceId, tag),
imageTag: tag,
digest: null,
buildBackend: "contract-skeleton"
};
if (args.lane !== "v02" || serviceId !== "hwlab-device-pod") return base;
const environmentImage = imageFor(args.registryPrefix, `${serviceId}-env`, `env-${String(source.full).slice(0, 12)}`);
return {
...base,
image: environmentImage,
imageTag: imageTagFromReference(environmentImage),
runtimeMode: v02EnvReuseRuntimeMode,
envReuse: true,
environmentImage,
environmentDigest: null,
environmentInputHash: null,
bootRepo: args.sourceRepo,
bootCommit: source.full,
bootSh: `deploy/runtime/boot/${serviceId}.sh`,
codeInputHash: null,
bootEnvEvidence: {
env: {
HWLAB_BOOT_REPO: args.sourceRepo,
HWLAB_BOOT_COMMIT: source.full,
HWLAB_BOOT_SH: `deploy/runtime/boot/${serviceId}.sh`
}
}
};
}
function namespaceNameForProfile(profile) {
if (profile === "v02") return "hwlab-v02";
return profile === "prod" ? "hwlab-prod" : "hwlab-dev";
}
function runtimePathForProfile(profile) {
if (profile === "v02") return "runtime-v02";
return profile === "prod" ? "runtime-prod" : "runtime-dev";
}
function runtimeLabelForProfile(profile) {
if (profile === "v02") return "v02";
return profile === "prod" ? "prod" : "dev";
}
function gitopsTargetForProfile(profile) {
return profile === "v02" ? "v02" : "g14";
}
function profileEndpoint(args, profile) {
if (profile === "v02") return { runtimeEndpoint: args.runtimeEndpoint, webEndpoint: args.webEndpoint };
return profile === "prod"
? { runtimeEndpoint: args.prodRuntimeEndpoint, webEndpoint: args.prodWebEndpoint }
: { runtimeEndpoint: args.runtimeEndpoint, webEndpoint: args.webEndpoint };
}
function laneRuntimeProfiles(args) {
return args.lane === "v02" ? ["v02"] : ["dev", "prod"];
}
function laneNames(args) {
if (args.lane === "v02") {
return {
gitopsTarget: "v02",
pipeline: "hwlab-v02-ci-image-publish",
poller: "hwlab-v02-branch-poller",
reconciler: "hwlab-v02-control-plane-reconciler",
serviceAccount: "hwlab-v02-tekton-runner",
pipelineRunPrefix: "hwlab-v02-ci-poll-",
runtimeNamespace: "hwlab-v02",
runtimePath: "deploy/gitops/g14/runtime-v02",
argoApplications: ["argocd/hwlab-g14-v02"]
};
}
return {
gitopsTarget: "g14",
pipeline: "hwlab-g14-ci-image-publish",
poller: "hwlab-g14-branch-poller",
reconciler: "hwlab-g14-control-plane-reconciler",
serviceAccount: "hwlab-tekton-runner",
pipelineRunPrefix: "hwlab-g14-ci-poll-",
runtimeNamespace: "hwlab-dev",
runtimePath: "deploy/gitops/g14/runtime-dev",
argoApplications: ["argocd/hwlab-g14-dev", "argocd/hwlab-g14-prod"]
};
}
function argoApplicationName(profile) {
return profile === "v02" ? "hwlab-g14-v02" : `hwlab-g14-${profile}`;
}
function imageTagForSource(args, source) {
return args.imageTagMode === "full" ? source.full : source.short;
}
function deepSeekBaseUrl(namespace) {
return `http://hwlab-deepseek-proxy.${namespace}.svc.cluster.local:4000/v1/responses`;
}
function codeAgentNoProxy(namespace) {
return [
`${namespace}.svc.cluster.local`,
".svc",
".cluster.local",
"hyueapi.com",
".hyueapi.com",
"hyui.com",
".hyui.com",
"127.0.0.1",
"localhost",
"::1",
"10.0.0.0/8",
"10.42.0.0/16",
"10.43.0.0/16",
"api.minimaxi.com",
".minimaxi.com"
].join(",");
}
function namespaceScopedHost(value, namespace) {
if (typeof value !== "string") return value;
return value.replace(/\.hwlab-dev\.svc\.cluster\.local/gu, `.${namespace}.svc.cluster.local`);
}
function secretNameForProfile(secretName, profile) {
if (profile !== "v02") return secretName;
if (secretName === "hwlab-cloud-api-dev-db") return "hwlab-cloud-api-v02-db";
if (secretName === "hwlab-code-agent-provider") return "hwlab-v02-code-agent-provider";
if (secretName === "hwlab-code-agent-codex-auth") return "hwlab-v02-code-agent-codex-auth";
if (secretName === "hwlab-bootstrap-admin") return "hwlab-v02-bootstrap-admin";
return secretName;
}
function rewritePodSecretRefs(podSpec, profile) {
if (!podSpec || profile !== "v02") return;
for (const volume of podSpec.volumes ?? []) {
if (volume?.secret?.secretName) volume.secret.secretName = secretNameForProfile(volume.secret.secretName, profile);
}
}
function deployEnvEntry(name, value, namespace, profile = "dev") {
if (typeof value === "string" && value.startsWith("secretRef:")) {
const ref = value.slice("secretRef:".length);
const slashIndex = ref.indexOf("/");
if (slashIndex > 0 && slashIndex < ref.length - 1) {
return {
name,
valueFrom: {
secretKeyRef: {
name: secretNameForProfile(ref.slice(0, slashIndex), profile),
key: ref.slice(slashIndex + 1),
optional: true
}
}
};
}
}
return { name, value: namespaceScopedHost(value, namespace) };
}
function applyDeployEnv(envList, deployService, namespace, profile = "dev") {
if (!deployService?.env || typeof deployService.env !== "object" || Array.isArray(deployService.env)) return;
for (const [name, value] of Object.entries(deployService.env)) {
upsertEnvEntry(envList, deployEnvEntry(name, value, namespace, profile));
}
}
function removeV02LegacySimulatorEnv(envList) {
if (!Array.isArray(envList)) return envList;
return envList.filter((entry) => !v02RemovedServiceEnvNames.has(entry?.name));
}
function deployServicesForProfile(deploy, profile) {
const allowed = new Set(serviceIdsForProfile(profile));
const services = new Map((deploy.services ?? [])
.filter((service) => allowed.has(service.serviceId))
.map((service) => [service.serviceId, cloneJson(service)]));
const laneServices = profile === "v02" && Array.isArray(deploy.lanes?.v02?.services)
? deploy.lanes.v02.services
: [];
for (const override of laneServices) {
const existing = services.get(override.serviceId) ?? { serviceId: override.serviceId };
services.set(override.serviceId, {
...existing,
...cloneJson(override),
env: { ...(existing.env ?? {}), ...(override.env ?? {}) }
});
}
return services;
}
function transformWorkloads({ workloads, deploy, catalog, source, registryPrefix, runtimeEndpoint, webEndpoint, profile = "dev", useDeployImages = false }) {
const result = cloneJson(workloads);
const deployServices = deployServicesForProfile(deploy, profile);
const namespace = namespaceNameForProfile(profile);
const profileLabel = runtimeLabelForProfile(profile);
const gitopsTarget = gitopsTargetForProfile(profile);
const digestPin = profile === "v02";
const stableRuntimeLabels = {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/environment": profileLabel,
"hwlab.pikastech.local/profile": profileLabel
};
result.items = asItems(result, "workloads").filter((item) => !(profile === "v02" && isV02RemovedServiceId(serviceIdForWorkload(item, null))));
for (const item of asItems(result, "workloads")) {
item.metadata.namespace = namespace;
label(item.metadata, {
...stableRuntimeLabels,
"hwlab.pikastech.local/gitops-target": gitopsTarget,
"hwlab.pikastech.local/source-commit": source.full,
"unidesk.ai/g14-staging": profile === "dev" ? "true" : "false"
});
annotate(item.metadata, {
"hwlab.pikastech.local/gitops-note": profile === "v02" ? "HWLAB v0.2 GitOps target." : profile === "prod" ? "G14 PROD GitOps target." : "G14 DEV GitOps target.",
"hwlab.pikastech.local/source-commit": source.full
});
if (item.kind === "Job") {
annotate(item.metadata, {
"argocd.argoproj.io/sync-options": "Force=true,Replace=true"
});
if (item.spec?.suspend === true) {
annotate(item.metadata, {
"argocd.argoproj.io/ignore-healthcheck": "true"
});
}
}
const podTemplate = item?.spec?.template;
if (!podTemplate?.spec?.containers) continue;
const templateServiceId = serviceIdForWorkload(item, podTemplate.spec.containers[0]);
const templateArtifact = runtimeArtifactForService({ catalog, serviceId: templateServiceId, source, registryPrefix, useDeployImages, digestPin });
const templateSourceCommit = profile === "v02" ? (templateArtifact.commit ?? source.full) : source.full;
const templateArtifactCommit = templateArtifact.commit;
label(podTemplate.metadata ??= {}, {
...stableRuntimeLabels,
"hwlab.pikastech.local/gitops-target": gitopsTarget,
"hwlab.pikastech.local/source-commit": templateSourceCommit,
"hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit
});
if ((item.kind === "Deployment" || item.kind === "StatefulSet") && item.spec?.selector?.matchLabels) {
item.spec.selector.matchLabels = {
...item.spec.selector.matchLabels,
...stableRuntimeLabels
};
}
annotate(podTemplate.metadata, {
"hwlab.pikastech.local/source-commit": templateSourceCommit,
"hwlab.pikastech.local/artifact-source-commit": templateArtifactCommit,
"hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs"
});
for (const container of podTemplate.spec.containers) {
const serviceId = serviceIdForWorkload(item, container);
const deployService = deployServices.get(serviceId);
if (!deployService) continue;
const artifact = runtimeArtifactForService({ catalog, serviceId, source, registryPrefix, useDeployImages, digestPin });
const envReuse = profile === "v02" && v02EnvReuseEnabled(catalog, serviceId);
const bootMetadata = envReuse ? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.v02?.sourceRepo || defaultSourceRepo, registryPrefix }, catalog, deployService, serviceId, source }) : null;
const image = artifact.image;
const runtimeCommit = artifact.commit;
const runtimeImageTag = artifact.imageTag;
container.image = image;
container.imagePullPolicy = "IfNotPresent";
container.env ??= [];
for (const entry of container.env) {
if (Object.hasOwn(entry, "value")) entry.value = namespaceScopedHost(entry.value, namespace);
}
applyDeployEnv(container.env, deployService, namespace, profile);
if (profile === "v02") container.env = removeV02LegacySimulatorEnv(container.env);
upsertEnv(container.env, "HWLAB_ENVIRONMENT", profileLabel);
upsertEnv(container.env, "HWLAB_COMMIT_ID", runtimeCommit);
upsertEnv(container.env, "HWLAB_IMAGE", image);
upsertEnv(container.env, "HWLAB_IMAGE_TAG", runtimeImageTag);
if (serviceId === "hwlab-agent-skills") {
upsertEnv(container.env, "HWLAB_SKILLS_COMMIT_ID", runtimeCommit);
}
upsertEnv(container.env, "HWLAB_GITOPS_TARGET", gitopsTarget);
upsertEnv(container.env, "HWLAB_GITOPS_PROFILE", profileLabel);
upsertEnv(container.env, "HWLAB_GITOPS_SOURCE_COMMIT", profile === "v02" ? runtimeCommit : source.full);
if (bootMetadata) {
upsertEnv(container.env, "HWLAB_RUNTIME_MODE", bootMetadata.runtimeMode);
upsertEnv(container.env, "HWLAB_BOOT_REPO", bootMetadata.bootRepo);
upsertEnv(container.env, "HWLAB_BOOT_COMMIT", bootMetadata.bootCommit);
upsertEnv(container.env, "HWLAB_BOOT_SH", bootMetadata.bootSh);
upsertEnv(container.env, "HWLAB_BOOT_READ_URL", defaultV02GitReadUrl);
upsertEnv(container.env, "HWLAB_ENVIRONMENT_IMAGE", bootMetadata.environmentImage ?? "");
upsertEnv(container.env, "HWLAB_ENVIRONMENT_DIGEST", bootMetadata.environmentDigest ?? "");
upsertEnv(container.env, "HWLAB_ENVIRONMENT_INPUT_HASH", bootMetadata.environmentInputHash ?? "");
upsertEnv(container.env, "HWLAB_CODE_INPUT_HASH", bootMetadata.codeInputHash ?? "");
upsertEnv(container.env, "HWLAB_IMAGE_DIGEST", bootMetadata.environmentDigest ?? "unknown");
annotate(podTemplate.metadata, {
"hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode,
"hwlab.pikastech.local/boot-repo": bootMetadata.bootRepo,
"hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit,
"hwlab.pikastech.local/boot-sh": bootMetadata.bootSh,
"hwlab.pikastech.local/environment-digest": bootMetadata.environmentDigest ?? "not_published",
"hwlab.pikastech.local/environment-input-hash": bootMetadata.environmentInputHash ?? "unknown",
"hwlab.pikastech.local/code-input-hash": bootMetadata.codeInputHash ?? "unknown"
});
label(podTemplate.metadata, {
"hwlab.pikastech.local/runtime-mode": bootMetadata.runtimeMode,
"hwlab.pikastech.local/boot-commit": bootMetadata.bootCommit
});
}
if (serviceId === "hwlab-cloud-api" || serviceId === "hwlab-edge-proxy") {
upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", runtimeEndpoint);
}
if (serviceId === "hwlab-cloud-api") {
syncCodeAgentWorkspaceInitContainer(podTemplate.spec.initContainers, { image, runtimeCommit, runtimeImageTag });
if (profile === "v02") {
upsertEnv(container.env, "HWLAB_M3_IO_CONTROL_ENABLED", "false");
upsertEnv(container.env, "HWLAB_ACCESS_CONTROL_REQUIRED", "1");
upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_ID", "usr_v02_admin");
upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_USERNAME", "admin");
upsertEnv(container.env, "HWLAB_BOOTSTRAP_ADMIN_DISPLAY_NAME", "HWLAB v0.2 Admin");
upsertEnvEntry(container.env, deployEnvEntry("HWLAB_BOOTSTRAP_ADMIN_PASSWORD_HASH", "secretRef:hwlab-v02-bootstrap-admin/password-hash", namespace, profile));
}
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE", "deepseek");
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_MODEL", deepSeekProfileModel);
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL", deepSeekBaseUrl(namespace));
upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_MODEL", codexApiProfileModel);
upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_BASE_URL", codexApiProfileBaseUrl);
upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL", codexApiForwarderUpstreamBaseUrl);
upsertEnv(container.env, "HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT", codexApiForwarderPort);
upsertEnv(container.env, "HWLAB_PREINSTALLED_SKILLS_DIR", "/app/skills");
upsertEnv(container.env, "HWLAB_USER_SKILLS_DIR", "/data/user-skills");
upsertEnv(container.env, "HWLAB_CODE_AGENT_SKILLS_DIRS", "/app/skills:/data/user-skills");
upsertEnv(container.env, "NO_PROXY", codeAgentNoProxy(namespace));
upsertEnv(container.env, "no_proxy", codeAgentNoProxy(namespace));
}
if (serviceId === "hwlab-cloud-web") {
upsertEnv(container.env, "HWLAB_API_BASE_URL", `http://hwlab-cloud-api.${namespace}.svc.cluster.local:6667`);
upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", webEndpoint);
}
}
rewritePodSecretRefs(podTemplate.spec, profile);
}
return result;
}
function transformHealthContract(value, namespace, labels, annotations, runtimeEndpoint, webEndpoint, profile = "dev") {
const result = transformListNamespace(value, namespace, labels, annotations);
if (result.metadata?.name === "hwlab-dev-health-contract") {
result.metadata.name = `hwlab-${profile}-health-contract`;
}
if (result.metadata?.labels && Object.hasOwn(result.metadata.labels, "app.kubernetes.io/name")) {
result.metadata.labels["app.kubernetes.io/name"] = `hwlab-${profile}-health-contract`;
}
if (result.data && typeof result.data === "object") {
if (Object.hasOwn(result.data, "endpoint")) result.data.endpoint = runtimeEndpoint;
if (Object.hasOwn(result.data, "cloud-web")) {
result.data["cloud-web"] = `GET /health/live on ${webEndpoint}; consumes cloud-api only`;
}
if (Object.hasOwn(result.data, "cloud-api")) {
result.data["cloud-api"] = `GET /health/live through ${runtimeEndpoint}`;
}
if (Object.hasOwn(result.data, "tunnel-client")) {
result.data["tunnel-client"] = "disabled for G14 GitOps; no FRP or production traffic tunnel is required";
}
}
return result;
}
function transformListNamespace(value, namespace, labels, annotations) {
const result = cloneJson(value);
if (result.kind === "List") {
for (const item of result.items ?? []) {
item.metadata ??= {};
item.metadata.namespace = namespace;
label(item.metadata, labels);
annotate(item.metadata, annotations);
}
} else {
result.metadata ??= {};
result.metadata.namespace = namespace;
label(result.metadata, labels);
annotate(result.metadata, annotations);
}
return result;
}
function transformServices({ services, namespace, labels, annotations, profile = "dev" }) {
const result = transformListNamespace(services, namespace, labels, annotations);
if (result.kind === "List") {
result.items = (result.items ?? []).filter((item) => !(profile === "v02" && isV02RemovedServiceId(serviceIdForWorkload(item, null))));
}
return result;
}
function shellSingleQuote(value) {
return `'${String(value).replace(/'/g, `'"'"'`)}'`;
}
function ciTimingShellFunction() {
return `ci_now_ms() {
node -e 'console.log(Date.now())'
}
ci_timing_emit() {
stage="$1"
status="$2"
started_ms="$3"
finished_ms="$(ci_now_ms)"
duration_ms="$((finished_ms - started_ms))"
service_id="\${HWLAB_TIMING_SERVICE_ID:-}"
node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:"g14-cicd-timing",schemaVersion:"v1",stage,status,durationMs:Number(durationMs),pipelineRun:process.env.HWLAB_TEKTON_PIPELINERUN||null,taskRun:process.env.HWLAB_TEKTON_TASKRUN||null,task:process.env.HWLAB_TEKTON_TASK||null,revision:process.env.HWLAB_SOURCE_REVISION||null,serviceId:serviceId||null,source:"scripts/g14-gitops-render.mjs",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' "$stage" "$status" "$duration_ms" "$service_id"
}
`;
}
function dependencyProxyProbeScript(phase, targetUrl) {
return `HWLAB_PROXY_PROBE_PHASE=${shellSingleQuote(phase)} HWLAB_PROXY_PROBE_URL=${shellSingleQuote(targetUrl)} node <<'NODE' || echo '{"event":"dependency-proxy-probe-nonblocking","phase":"${phase}","ok":false,"reason":"probe-failed-but-continuing"}'
const net = require("node:net");
const phase = process.env.HWLAB_PROXY_PROBE_PHASE || "unknown";
const target = new URL(process.env.HWLAB_PROXY_PROBE_URL || "http://deb.debian.org/debian/dists/bookworm/InRelease");
const proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || "";
function redactProxy(value) {
if (!value) return "";
try {
const parsed = new URL(value);
if (parsed.username || parsed.password) {
parsed.username = "***";
parsed.password = "";
}
return parsed.toString();
} catch {
return "<invalid-proxy-url>";
}
}
function emit(payload, exitCode = 0) {
console.log(JSON.stringify({
event: "dependency-proxy-probe",
phase,
target: target.href,
proxy: redactProxy(proxyRaw),
...payload
}));
if (exitCode) process.exit(exitCode);
}
if (!proxyRaw) emit({ ok: false, reason: "proxy-env-missing" }, 21);
let proxy;
try {
proxy = new URL(proxyRaw);
} catch (error) {
emit({ ok: false, reason: "proxy-url-invalid", error: error.message }, 22);
}
if (proxy.protocol !== "http:" && proxy.protocol !== "https:") {
emit({ ok: false, reason: "http-proxy-required", protocol: proxy.protocol }, 23);
}
const startedAt = Date.now();
const socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === "https:" ? 443 : 80)) });
let bytes = 0;
let firstByteMs = null;
let responseHead = "";
let finished = false;
const timeout = setTimeout(() => {
if (finished) return;
finished = true;
socket.destroy();
emit({ ok: false, reason: "timeout", timeoutMs: 15000 }, 24);
}, 15000);
socket.on("connect", () => {
socket.write("GET " + target.href + " HTTP/1.1\\r\\nHost: " + target.host + "\\r\\nUser-Agent: hwlab-g14-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n");
});
socket.on("data", (chunk) => {
if (firstByteMs === null) firstByteMs = Date.now() - startedAt;
bytes += chunk.length;
if (responseHead.length < 240) responseHead += chunk.toString("utf8", 0, Math.min(chunk.length, 240 - responseHead.length));
});
socket.on("end", () => {
if (finished) return;
finished = true;
clearTimeout(timeout);
const totalMs = Date.now() - startedAt;
const statusLine = responseHead.split("\\r\\n", 1)[0] || "";
const statusCode = Number(statusLine.split(" ")[1] || 0);
const ok = statusLine.startsWith("HTTP/") && statusCode >= 200 && statusCode < 400;
emit({
ok,
reason: ok ? null : "bad-http-status",
statusLine,
statusCode,
firstByteMs,
totalMs,
bytes,
speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0
}, ok ? 0 : 25);
});
socket.on("error", (error) => {
if (finished) return;
finished = true;
clearTimeout(timeout);
emit({ ok: false, reason: "proxy-connect-failed", error: error.message, totalMs: Date.now() - startedAt }, 26);
});
NODE`;
}
function curlDownloadProbeFunction() {
return `redact_proxy_value() {
printf '%s' "\${1:-}" | sed -E 's#(https?://)[^/@]+@#\\1***@#; s#(socks5h?://)[^/@]+@#\\1***@#'
}
curl_dependency_probe() {
phase="$1"
url="$2"
proxy="\${HTTPS_PROXY:-\${https_proxy:-\${HTTP_PROXY:-\${http_proxy:-}}}}"
if [ -z "$proxy" ]; then
echo '{"event":"dependency-curl-probe","phase":"'"$phase"'","ok":false,"reason":"proxy-env-missing"}'
return 21
fi
safe_proxy="$(redact_proxy_value "$proxy")"
echo '{"event":"dependency-curl-probe-start","phase":"'"$phase"'","proxy":"'"$safe_proxy"'","target":"'"$url"'"}'
curl -sS -L --range 0-1048575 -o /dev/null --connect-timeout 15 --max-time 60 --proxy "$proxy" \
-w '{"event":"dependency-curl-probe","phase":"'"$phase"'","http_code":"%{http_code}","time_namelookup":%{time_namelookup},"time_connect":%{time_connect},"time_starttransfer":%{time_starttransfer},"time_total":%{time_total},"speed_download":%{speed_download},"size_download":%{size_download},"remote_ip":"%{remote_ip}"}\n' \
"$url"
}`;
}
function gitSshShellFunction() {
return `git_ssh_setup() {
mkdir -p /root/.ssh
cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa
chmod 600 /root/.ssh/id_rsa
timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true
timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true
export GIT_SSH_COMMAND="ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -o ServerAliveInterval=10 -o ServerAliveCountMax=2"
git config --global url."ssh://git@ssh.github.com:443/".insteadOf "git@github.com:"
git config --global url."ssh://git@ssh.github.com:443/".insteadOf "ssh://git@github.com/"
}
git_now_ms() {
node -e 'console.log(Date.now())' 2>/dev/null || date +%s000
}
git_timed() {
phase="$1"
timeout_seconds="$2"
shift 2
started_ms="$(git_now_ms)"
echo '{"event":"git-operation","phase":"'"$phase"'","status":"started","timeoutSeconds":'"$timeout_seconds"'}' >&2
set +e
timeout "$timeout_seconds" "$@"
status="$?"
set -e
finished_ms="$(git_now_ms)"
duration_ms="$((finished_ms - started_ms))"
if [ "$status" -eq 0 ]; then
echo '{"event":"git-operation","phase":"'"$phase"'","status":"succeeded","durationMs":'"$duration_ms"'}' >&2
return 0
fi
if [ "$status" -eq 124 ] || [ "$status" -eq 137 ]; then
echo '{"event":"git-operation","phase":"'"$phase"'","status":"failed","reason":"timeout","durationMs":'"$duration_ms"',"timeoutSeconds":'"$timeout_seconds"'}' >&2
else
echo '{"event":"git-operation","phase":"'"$phase"'","status":"failed","exitCode":'"$status"',"durationMs":'"$duration_ms"'}' >&2
fi
return "$status"
}
`;
}
function prepareSourceScript() {
const lines = [
"#!/bin/sh",
"set -eu",
ciTimingShellFunction(),
"export HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"",
"export HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"",
"export HWLAB_TEKTON_TASK=\"prepare-source\"",
"export HWLAB_SOURCE_REVISION=\"$(params.revision)\"",
`echo '{"event":"ci-base-image","phase":"prepare-source","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'`,
"for tool in node git timeout; do command -v \"$tool\" >/dev/null 2>&1 || { echo '{\"event\":\"ci-base-image\",\"ok\":false,\"reason\":\"missing-tool\",\"tool\":\"'\"$tool\"'\"}'; exit 31; }; done",
"node -e 'console.log(JSON.stringify({event:\"ci-base-image\",phase:\"prepare-source\",ok:true,node:process.version}))'",
gitSshShellFunction(),
"git_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }",
"git_read_url=\"$(params.git-read-url)\"",
"if git_url_requires_ssh \"$git_read_url\"; then",
" for tool in ssh ssh-keyscan; do command -v \"$tool\" >/dev/null 2>&1 || { echo '{\"event\":\"ci-base-image\",\"ok\":false,\"reason\":\"missing-tool\",\"tool\":\"'\"$tool\"'\"}'; exit 31; }; done",
" git_ssh_setup",
"else",
" echo '{\"event\":\"git-ssh-setup\",\"phase\":\"prepare-source\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-read-url\"}'",
"fi",
"rm -rf /workspace/source/repo",
"source_clone_started_ms=\"$(ci_now_ms)\"",
"git_timed source-clone 180 git clone --branch \"$(params.source-branch)\" \"$git_read_url\" /workspace/source/repo",
"cd /workspace/source/repo",
"git config --global --add safe.directory /workspace/source/repo",
"git remote set-url origin \"$git_read_url\"",
"git checkout \"$(params.revision)\"",
"test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"",
"git merge-base --is-ancestor \"$(params.revision)\" \"origin/$(params.source-branch)\" || { echo '{\"event\":\"source-ancestry\",\"status\":\"failed\",\"revision\":\"'\"$(params.revision)\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\"}'; exit 32; }",
"ci_timing_emit source-clone succeeded \"$source_clone_started_ms\"",
"catalog_path=\"$(params.catalog-path)\"",
"mkdir -p \"$(dirname \"$catalog_path\")\"",
"catalog_fetch_started_ms=\"$(ci_now_ms)\"",
"if git_timed catalog-ls-remote 45 git ls-remote --exit-code --heads \"$git_read_url\" \"$(params.gitops-branch)\" >/dev/null; then",
" git remote set-url gitops-catalog \"$git_read_url\" 2>/dev/null || git remote add gitops-catalog \"$git_read_url\"",
" git_timed catalog-fetch 90 git fetch --depth 1 gitops-catalog \"$(params.gitops-branch)\" >/dev/null || true",
" if git cat-file -e FETCH_HEAD:\"$catalog_path\" 2>/dev/null; then",
" git show FETCH_HEAD:\"$catalog_path\" > /tmp/hwlab-gitops-artifact-catalog.json",
" if node -e 'const fs=require(\"fs\"); const ids=(doc)=>(doc.services||[]).map((s)=>s.serviceId); const selected=(process.argv[3]||\"\").split(\",\").filter(Boolean); const gitops=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")); const deploy=JSON.parse(fs.readFileSync(process.argv[2],\"utf8\")); const expected=selected.length ? selected : ids(deploy); if (JSON.stringify(ids(gitops)) !== JSON.stringify(expected)) process.exit(42);' /tmp/hwlab-gitops-artifact-catalog.json deploy/deploy.json \"$(params.services)\"; then",
" cp /tmp/hwlab-gitops-artifact-catalog.json \"$catalog_path\"",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"loaded\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'",
" else",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"ignored-stale\",\"reason\":\"service-ids-mismatch\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'",
" fi",
" else",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"missing-on-gitops-branch\"}'",
" fi",
"else",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"gitops-branch-missing\"}'",
"fi",
"if [ ! -s \"$catalog_path\" ] && [ \"$(params.lane)\" = \"v02\" ]; then",
" node - \"$catalog_path\" \"$(params.revision)\" \"$(params.registry-prefix)\" \"$(params.image-tag-mode)\" \"$(params.services)\" <<'NODE'",
"const fs = require('node:fs');",
"const path = require('node:path');",
"const [catalogPath, revision, registryPrefix, imageTagMode, selectedServices] = process.argv.slice(2);",
"const deploy = JSON.parse(fs.readFileSync('deploy/deploy.json', 'utf8'));",
"const tag = imageTagMode === 'full' ? revision : revision.slice(0, 7);",
"const namespace = 'hwlab-v02';",
"const selected = (selectedServices || '').split(',').filter(Boolean);",
"const serviceIds = selected.length ? selected : (deploy.services || []).map((service) => service.serviceId);",
"const services = serviceIds.map((serviceId) => {",
" const service = { serviceId, profile: 'v02', namespace, commitId: tag, sourceCommitId: revision, image: `${registryPrefix}/${serviceId}:${tag}`, imageTag: tag, digest: null, buildBackend: 'contract-skeleton' };",
" if (serviceId === 'hwlab-device-pod') {",
" const environmentImage = `${registryPrefix}/${serviceId}-env:env-${revision.slice(0, 12)}`;",
" Object.assign(service, { image: environmentImage, imageTag: environmentImage.slice(environmentImage.lastIndexOf(':') + 1), runtimeMode: 'env-reuse-git-mirror-checkout', envReuse: true, environmentImage, environmentDigest: null, environmentInputHash: null, codeInputHash: null, bootRepo: 'git@github.com:pikasTech/HWLAB.git', bootCommit: revision, bootSh: 'deploy/runtime/boot/hwlab-device-pod.sh', bootEnvEvidence: { env: { HWLAB_BOOT_REPO: 'git@github.com:pikasTech/HWLAB.git', HWLAB_BOOT_COMMIT: revision, HWLAB_BOOT_SH: 'deploy/runtime/boot/hwlab-device-pod.sh', HWLAB_ENVIRONMENT_IMAGE: environmentImage, HWLAB_ENVIRONMENT_DIGEST: '' } } });",
" }",
" return service;",
"});",
"fs.mkdirSync(path.dirname(catalogPath), { recursive: true });",
"fs.writeFileSync(catalogPath, JSON.stringify({ catalogVersion: 'v1', kind: 'hwlab-artifact-catalog', environment: 'v02', profile: 'v02', namespace, endpoint: 'http://74.48.78.17:19667', commitId: tag, artifactState: 'contract-skeleton', publish: { registryPrefix, sourceCommitId: revision, imageTag: tag, publishedAt: null }, allowedProfiles: ['v02'], forbiddenProfiles: ['dev', 'prod'], services }, null, 2) + '\\n');",
"NODE",
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seeded-v02-skeleton\"}'",
"fi",
"ci_timing_emit catalog-fetch succeeded \"$catalog_fetch_started_ms\"",
"echo '{\"event\":\"prepare-source-dependencies\",\"status\":\"skipped\",\"reason\":\"ci-tools-image-provides-required-node-runtime\"}'",
`echo ${shellSingleQuote(`G14 prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}`
];
return `${lines.join("\n")}\n`;
}
function sourceValidationScript(commands) {
return [
"#!/bin/sh",
"set -eu",
"git config --global --add safe.directory /workspace/source/repo",
"cd /workspace/source/repo",
"test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"",
...commands
].join("\n") + "\n";
}
function primitiveValidationTaskNames() {
return primitiveValidationTasks.map((task) => task.name);
}
function primitiveValidationTask(task) {
return {
name: task.name,
runAfter: ["prepare-source"],
workspaces: [{ name: "source", workspace: "source" }],
taskSpec: {
params: [
{ name: "revision" },
{ name: "lane" },
{ name: "catalog-path" },
{ name: "image-tag-mode" },
{ name: "source-branch" },
{ name: "gitops-branch" }
],
workspaces: [{ name: "source" }],
steps: [{ name: task.name, image: ciToolsRunnerImage, env: proxyEnv(), script: sourceValidationScript(task.commands) }]
},
params: [
{ name: "revision", value: "$(params.revision)" },
{ name: "lane", value: "$(params.lane)" },
{ name: "catalog-path", value: "$(params.catalog-path)" },
{ name: "image-tag-mode", value: "$(params.image-tag-mode)" },
{ name: "source-branch", value: "$(params.source-branch)" },
{ name: "gitops-branch", value: "$(params.gitops-branch)" }
]
};
}
function planArtifactsScript() {
return `#!/bin/sh
set -eu
echo '{"event":"ci-base-image","phase":"plan-artifacts","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'
for tool in node git; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"plan-artifacts","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
cd /workspace/source/repo
test "$(git rev-parse HEAD)" = "$(params.revision)"
node scripts/g14-ci-plan.mjs --lane "$(params.lane)" --target-ref HEAD --deploy-json deploy/deploy.json --artifact-catalog "$(params.catalog-path)" --registry-prefix "$(params.registry-prefix)" --services "$(params.services)" > /workspace/source/ci-plan.json
node - <<'NODE'
const fs = require("node:fs");
const plan = JSON.parse(fs.readFileSync("/workspace/source/ci-plan.json", "utf8"));
const selected = String(process.env.HWLAB_SELECTED_SERVICES || "").split(",").map((item) => item.trim()).filter(Boolean);
const allServices = selected.length > 0 ? selected : ${JSON.stringify(defaultServiceIds)};
const affected = new Set(plan.affectedServices || []);
const plannedBuildServices = Array.isArray(plan.buildServices) ? new Set(plan.buildServices) : null;
const selectedSet = new Set(selected);
const byService = new Map((plan.services || []).map((service) => [service.serviceId, service]));
const entries = allServices.map((serviceId) => {
const service = byService.get(serviceId) || {};
const serviceSelected = selectedSet.has(serviceId);
const rolloutAffected = serviceSelected && affected.has(serviceId);
const envReuse = service.runtimeMode === "env-reuse-git-mirror-checkout" || service.envReuse === true;
const buildRequired = serviceSelected && (plannedBuildServices ? plannedBuildServices.has(serviceId) : (envReuse ? service.envChanged === true : rolloutAffected));
return {
serviceId,
selected: serviceSelected,
affected: buildRequired,
buildRequired,
rolloutAffected,
runtimeMode: service.runtimeMode || "service-image",
envChanged: service.envChanged ?? null,
codeChanged: service.codeChanged ?? null
};
});
for (const entry of entries) {
fs.writeFileSync("/tekton/results/affected-" + entry.serviceId, entry.affected ? "true" : "false");
}
fs.writeFileSync("/workspace/source/affected-services.json", JSON.stringify({
sourceCommitId: plan.sourceCommitId,
affectedServices: plan.affectedServices || [],
rolloutServices: plan.rolloutServices || plan.affectedServices || [],
buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId),
reusedServices: plan.reusedServices || [],
buildSkippedCount: plan.buildSkippedCount || 0,
changedPathSummary: plan.changedPathSummary || null,
ciCdPlan: plan.ciCdPlan || null,
services: plan.services || [],
entries
}, null, 2) + String.fromCharCode(10));
console.log(JSON.stringify({ event: "g14-ci-plan", sourceCommitId: plan.sourceCommitId, affectedServices: plan.affectedServices || [], rolloutServices: plan.rolloutServices || plan.affectedServices || [], buildServices: plan.buildServices || [], reusedServices: plan.reusedServices || [], buildSkippedCount: plan.buildSkippedCount || 0 }));
NODE
`;
}
function perServicePublishScript() {
return `#!/bin/sh
set -eu
${ciTimingShellFunction()}
export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)"
export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)"
export HWLAB_TEKTON_TASK="build-$(params.service-id)"
export HWLAB_SOURCE_REVISION="$(params.revision)"
export HWLAB_TIMING_SERVICE_ID="$(params.service-id)"
${dependencyProxyProbeScript("service-image-publish-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")}
echo '{"event":"ci-base-image","phase":"service-image-publish","serviceId":"'"$(params.service-id)"'","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'
for tool in node npm git python3 ssh curl; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"service-image-publish","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
git config --global --add safe.directory /workspace/source/repo
cd /workspace/source/repo
test "$(git rev-parse HEAD)" = "$(params.revision)"
export HWLAB_ARTIFACT_LANE="$(params.lane)"
export HWLAB_ARTIFACT_CATALOG_PATH="$(params.catalog-path)"
export HWLAB_DEPLOY_MANIFEST_PATH="deploy/deploy.json"
export HWLAB_ARTIFACT_IMAGE_TAG_MODE="$(params.image-tag-mode)"
mkdir -p /workspace/source/service-results
if [ -s /workspace/source/affected-services.json ] && ! node -e 'const fs=require("node:fs"); const p=JSON.parse(fs.readFileSync("/workspace/source/affected-services.json","utf8")); const service=process.argv[1]; const item=(p.entries||[]).find((entry)=>entry.serviceId===service); process.exit(item && item.affected ? 0 : 1);' "$(params.service-id)"; then
node - "$(params.service-id)" <<'NODE'
const fs = require("node:fs");
const serviceId = process.argv[2];
const catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, "utf8"));
const plan = fs.existsSync("/workspace/source/affected-services.json") ? JSON.parse(fs.readFileSync("/workspace/source/affected-services.json", "utf8")) : {};
const service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};
const planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};
const envReuse = planned.runtimeMode === "env-reuse-git-mirror-checkout" || service.runtimeMode === "env-reuse-git-mirror-checkout" || planned.envReuse === true || service.envReuse === true;
const image = envReuse ? (service.environmentImage || service.image || "") : (service.image || "");
const digest = envReuse ? (service.environmentDigest || service.digest || "not_published") : (service.digest || "not_published");
const imageTag = service.imageTag || (image.includes(":") ? image.slice(image.lastIndexOf(":") + 1) : "");
const repository = image.includes(":") ? image.slice(0, image.lastIndexOf(":")) : image;
const revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;
const values = {
"service-id": serviceId,
status: /^sha256:[a-f0-9]{64}$/.test(digest) ? "reused" : "blocked_reuse_unavailable",
image,
"image-tag": imageTag,
digest,
"repository-digest": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + "@" + digest : "",
"source-commit-id": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),
"component-input-hash": planned.componentInputHash || service.componentInputHash || "",
"environment-input-hash": planned.environmentInputHash || service.environmentInputHash || "",
"code-input-hash": planned.codeInputHash || service.codeInputHash || "",
"runtime-mode": envReuse ? "env-reuse-git-mirror-checkout" : "service-image",
"boot-repo": planned.bootRepo || service.bootRepo || "",
"boot-commit": envReuse ? revision : (service.bootCommit || ""),
"boot-sh": planned.bootSh || service.bootSh || "",
"build-created-at": service.buildCreatedAt || "",
"build-backend": envReuse ? "reused-env-catalog" : "reused-catalog",
"reused-from": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || "catalog"
};
for (const [name, value] of Object.entries(values)) fs.writeFileSync("/tekton/results/" + name, String(value || ""));
NODE
echo '{"event":"service-build-skip","serviceId":"'"$(params.service-id)"'","reason":"component-inputs-unchanged"}'
exit 0
fi
rm -rf /workspace/service-work/repo
mkdir -p /workspace/service-work/repo
tar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -
cd /workspace/service-work/repo
export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)-$(params.service-id)"
export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton"
export HWLAB_DEV_REGISTRY_PREFIX="$(params.registry-prefix)"
export HWLAB_DEV_REGISTRY_K8S_NATIVE=1
export HWLAB_G14_CICD_TIMING=1
export HWLAB_TEKTON_PIPELINERUN
export HWLAB_TEKTON_TASKRUN
export HWLAB_TEKTON_TASK
export HWLAB_SOURCE_REVISION
export PATH="/workspace/buildkit-bin:$PATH"
export HWLAB_BUILDKIT_ADDR="unix:///workspace/buildkit-run/buildkitd.sock"
if [ -n "$(params.base-image)" ]; then export HWLAB_DEV_BASE_IMAGE="$(params.base-image)"; fi
if [ -n "\${HWLAB_DEV_BASE_IMAGE:-}" ]; then
echo '{"event":"dependency-local-registry-probe-start","phase":"service-image-publish-pre-base-image","serviceId":"'"$(params.service-id)"'","target":"http://127.0.0.1:5000/v2/"}'
registry_started_at="$(date +%s)"
curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null
registry_finished_at="$(date +%s)"
echo '{"event":"dependency-local-registry-probe","phase":"service-image-publish-pre-base-image","serviceId":"'"$(params.service-id)"'","ok":true,"durationSeconds":'"$((registry_finished_at - registry_started_at))"'}'
fi
buildkit_ready=0
for attempt in $(seq 1 60); do
if /workspace/buildkit-bin/buildctl --addr "$HWLAB_BUILDKIT_ADDR" debug workers >/dev/null 2>&1; then
buildkit_ready=1
break
fi
sleep 1
done
if [ "$buildkit_ready" != "1" ]; then
echo '{"event":"buildkit-sidecar-not-ready","serviceId":"'"$(params.service-id)"'","addr":"'"$HWLAB_BUILDKIT_ADDR"'"}' >&2
exit 1
fi
node scripts/g14-artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-json deploy/deploy.json --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report "/workspace/source/service-results/$(params.service-id).json" --tekton-results-dir /tekton/results --quiet-build --concurrency 1 --services "$(params.service-id)" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr "$HWLAB_BUILDKIT_ADDR"
`;
}
function collectArtifactsScript() {
return `#!/bin/sh
set -eu
echo '{"event":"ci-base-image","phase":"collect-artifacts","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'
for tool in node git; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"collect-artifacts","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
cd /workspace/source/repo
test "$(git rev-parse HEAD)" = "$(params.revision)"
export HWLAB_CI_ARTIFACT_RUN_ID="$(context.pipelineRun.name)-collect"
export HWLAB_CI_ARTIFACT_RUN_OWNER="tekton"
export HWLAB_DEV_REGISTRY_PREFIX="$(params.registry-prefix)"
export HWLAB_DEV_REGISTRY_K8S_NATIVE=1
if [ -n "$(params.base-image)" ]; then export HWLAB_DEV_BASE_IMAGE="$(params.base-image)"; fi
export HWLAB_ARTIFACT_LANE="$(params.lane)"
export HWLAB_ARTIFACT_CATALOG_PATH="$(params.catalog-path)"
export HWLAB_DEPLOY_MANIFEST_PATH="deploy/deploy.json"
export HWLAB_ARTIFACT_IMAGE_TAG_MODE="$(params.image-tag-mode)"
node scripts/g14-artifact-publish.mjs --publish --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-json deploy/deploy.json --image-tag-mode "$(params.image-tag-mode)" --registry-prefix "$(params.registry-prefix)" --report /workspace/source/dev-artifacts.json --quiet-build --concurrency 1 --services "$(params.services)" --external-service-report-dir /workspace/source/service-results
node scripts/refresh-artifact-catalog.mjs --lane "$(params.lane)" --catalog-path "$(params.catalog-path)" --deploy-json deploy/deploy.json --image-tag-mode "$(params.image-tag-mode)" --target-ref HEAD --publish-report /workspace/source/dev-artifacts.json --no-write
`;
}
function gitopsPromoteScript() {
return `#!/bin/sh
set -eu
${ciTimingShellFunction()}
export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)"
export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)"
export HWLAB_TEKTON_TASK="gitops-promote"
export HWLAB_SOURCE_REVISION="$(params.revision)"
printf 'true' > /tekton/results/runtime-ready-required
echo '{"event":"ci-base-image","phase":"gitops-promote","image":"${ciToolsRunnerImage}","policy":"no-runtime-apt"}'
for tool in node git timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"gitops-promote","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
${gitSshShellFunction()}
git_url_requires_ssh() { case "$1" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }
lane="$(params.lane)"
source_head_url_for_setup="$(params.git-url)"
gitops_read_url_for_setup="$(params.git-url)"
if [ "$lane" = "v02" ]; then
source_head_url_for_setup="$(params.git-read-url)"
gitops_read_url_for_setup="$(params.git-read-url)"
fi
gitops_write_url_for_setup="$(params.git-write-url)"
if git_url_requires_ssh "$source_head_url_for_setup" || git_url_requires_ssh "$gitops_read_url_for_setup" || git_url_requires_ssh "$gitops_write_url_for_setup"; then
for tool in ssh ssh-keyscan; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"gitops-promote","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
git_ssh_setup
else
echo '{"event":"git-ssh-setup","phase":"gitops-promote","status":"skipped","reason":"non-ssh-git-url"}'
fi
cd /workspace/source/repo
test "$(git rev-parse HEAD)" = "$(params.revision)"
check_source_head() {
phase="$1"
expected="\${2:-$(params.revision)}"
source_head_url="$(params.git-url)"
if [ "$(params.lane)" = "v02" ]; then
source_head_url="$(params.git-read-url)"
fi
latest_file="$(mktemp)"
if ! git_timed "source-head-$phase" 45 git ls-remote "$source_head_url" "refs/heads/$(params.source-branch)" > "$latest_file"; then
echo '{"status":"failed","reason":"source-head-check-failed","phase":"'"$phase"'","sourceBranch":"'"$(params.source-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
exit 1
fi
latest="$(cut -f1 "$latest_file" | head -n 1)"
if [ -z "$latest" ]; then
echo '{"status":"failed","reason":"source-head-unresolved","phase":"'"$phase"'","sourceBranch":"'"$(params.source-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
exit 1
fi
if [ "$latest" != "$expected" ]; then
echo '{"status":"skipped-stale-source","phase":"'"$phase"'","sourceBranch":"'"$(params.source-branch)"'","sourceRevision":"'"$(params.revision)"'","expectedRevision":"'"$expected"'","latestRevision":"'"$latest"'"}'
exit 0
fi
}
check_source_head before-render
git config --global user.name "HWLAB G14 GitOps Bot"
git config --global user.email "hwlab-g14-gitops-bot@users.noreply.github.com"
catalog_path="$(params.catalog-path)"
runtime_path="$(params.runtime-path)"
argo_hard_refresh_v02() {
if [ "$lane" != "v02" ]; then return 0; fi
node <<'NODE'
const fs = require("node:fs");
const https = require("node:https");
const host = process.env.KUBERNETES_SERVICE_HOST;
const port = process.env.KUBERNETES_SERVICE_PORT || "443";
const startedAt = Date.now();
const application = "hwlab-g14-v02";
const pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;
const taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;
const task = process.env.HWLAB_TEKTON_TASK || null;
const revision = process.env.HWLAB_SOURCE_REVISION || null;
function emit(payload) {
console.log(JSON.stringify({
event: "g14-cicd-timing",
schemaVersion: "v1",
stage: "argo-hard-refresh",
pipelineRun,
taskRun,
task,
revision,
application,
source: "scripts/g14-gitops-render.mjs",
at: new Date().toISOString(),
...payload
}));
}
function safeJson(text) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
function request(method, path, body, contentType = "application/merge-patch+json") {
const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8");
const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt");
const payload = body ? JSON.stringify(body) : "";
const headers = { Authorization: "Bearer " + token };
if (payload) {
headers["Content-Type"] = contentType;
headers["Content-Length"] = Buffer.byteLength(payload);
}
return new Promise((resolve, reject) => {
const req = https.request({ host, port, method, path, ca, headers }, (res) => {
let data = "";
res.setEncoding("utf8");
res.on("data", (chunk) => { data += chunk; });
res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) }));
});
req.on("error", reject);
if (payload) req.write(payload);
req.end();
});
}
(async () => {
if (!host) {
emit({ status: "skipped", reason: "kubernetes-service-host-missing", durationMs: Date.now() - startedAt });
return;
}
const response = await request(
"PATCH",
"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/" + encodeURIComponent(application) + "?fieldManager=hwlab-v02-gitops-promote",
{ metadata: { annotations: { "argocd.argoproj.io/refresh": "hard" } } }
);
if (response.statusCode >= 200 && response.statusCode < 300) {
emit({ status: "succeeded", durationMs: Date.now() - startedAt, statusCode: response.statusCode });
return;
}
emit({ status: "degraded", reason: "argo-refresh-patch-failed", durationMs: Date.now() - startedAt, statusCode: response.statusCode, body: response.body.slice(0, 1000) });
})().catch((error) => {
emit({ status: "degraded", reason: "argo-refresh-patch-error", durationMs: Date.now() - startedAt, error: error.message });
});
NODE
}
if [ -s /workspace/source/dev-artifacts.json ]; then
node scripts/refresh-artifact-catalog.mjs --lane "$lane" --catalog-path "$catalog_path" --deploy-json deploy/deploy.json --image-tag-mode "$(params.image-tag-mode)" --target-ref "$(params.revision)" --publish-report /workspace/source/dev-artifacts.json --write
fi
gitops_render_started_ms="$(ci_now_ms)"
node scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images
node scripts/g14-gitops-render.mjs --lane "$lane" --catalog-path "$catalog_path" --image-tag-mode "$(params.image-tag-mode)" --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images --check
ci_timing_emit gitops-render succeeded "$gitops_render_started_ms"
check_source_head before-push
workdir="$(mktemp -d)"
gitops_read_url="$(params.git-url)"
gitops_write_url="$(params.git-write-url)"
if [ "$lane" = "v02" ]; then
gitops_read_url="$(params.git-read-url)"
fi
gitops_clone_started_ms="$(ci_now_ms)"
git_timed gitops-clone 180 git clone --no-checkout "$gitops_read_url" "$workdir/gitops"
cd "$workdir/gitops"
git remote set-url origin "$gitops_write_url"
old_runtime_snapshot="$workdir/old-runtime-snapshot"
if git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin "$(params.gitops-branch)" >/dev/null; then
git_timed gitops-fetch 90 git fetch origin "$(params.gitops-branch)"
git checkout -B "$(params.gitops-branch)" "origin/$(params.gitops-branch)"
if [ "$lane" = "v02" ] && [ -d "$runtime_path" ]; then
mkdir -p "$old_runtime_snapshot"
cp -a "$runtime_path"/. "$old_runtime_snapshot"/
fi
if [ "$lane" = "v02" ]; then rm -rf "$runtime_path" "$catalog_path"; else rm -rf deploy/gitops/g14 "$catalog_path"; fi
else
git checkout --orphan "$(params.gitops-branch)"
git rm -rf . >/dev/null 2>&1 || true
fi
ci_timing_emit gitops-clone succeeded "$gitops_clone_started_ms"
mkdir -p "$(dirname "$runtime_path")" "$(dirname "$catalog_path")"
if [ "$lane" = "v02" ]; then
cp -a "/workspace/source/repo/$runtime_path" "$runtime_path"
cp "/workspace/source/repo/$catalog_path" "$catalog_path"
git add "$catalog_path" "$runtime_path"
else
mkdir -p deploy/gitops
cp -a /workspace/source/repo/deploy/gitops/g14 deploy/gitops/g14
cp "/workspace/source/repo/$catalog_path" "$catalog_path"
git add "$catalog_path" deploy/gitops/g14
fi
if [ "$lane" = "v02" ] && [ -s /workspace/source/affected-services.json ]; then
runtime_noop_decision="$(node - "$runtime_path" "$old_runtime_snapshot" /workspace/source/affected-services.json <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2);
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function stable(value) {
if (Array.isArray(value)) return "[" + value.map(stable).join(",") + "]";
if (value && typeof value === "object") {
return "{" + Object.keys(value).sort().map((key) => JSON.stringify(key) + ":" + stable(value[key])).join(",") + "}";
}
return JSON.stringify(value);
}
function scrub(value) {
if (Array.isArray(value)) return value.map(scrub);
if (!value || typeof value !== "object") return value;
const result = {};
for (const [key, child] of Object.entries(value)) {
if (key === "hwlab.pikastech.local/source-commit" ||
key === "hwlab.pikastech.local/artifact-source-commit" ||
key === "hwlab.pikastech.local/boot-commit" ||
key === "sourceCommitId" ||
key === "bootCommit") {
result[key] = "<runtime-source-identity>";
continue;
}
if (key === "value" && /^HWLAB_.*COMMIT/.test(String(value.name || ""))) {
result[key] = "<runtime-source-identity>";
continue;
}
result[key] = scrub(child);
}
return result;
}
function listFiles(rootDir) {
if (!rootDir || !fs.existsSync(rootDir)) return null;
const files = [];
function walk(current) {
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) walk(fullPath);
else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, "/"));
}
}
walk(rootDir);
return files.sort();
}
function normalizedTree(rootDir) {
const files = listFiles(rootDir);
if (!files) return null;
const entries = {};
for (const relativePath of files) {
const filePath = path.join(rootDir, relativePath);
const text = fs.readFileSync(filePath, "utf8");
try {
entries[relativePath] = stable(scrub(JSON.parse(text)));
} catch {
entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, "<runtime-source-identity>");
}
}
return stable(entries);
}
const plan = readJson(planPath);
const buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];
const rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : [];
if (buildServices.length > 0 || rolloutServices.length > 0) {
process.stdout.write("runtime-required");
process.exit(0);
}
const oldTree = normalizedTree(oldRuntimePath);
const newTree = normalizedTree(runtimePath);
process.stdout.write(oldTree && newTree && oldTree === newTree ? "runtime-identity-only" : "runtime-required");
NODE
)"
if [ "$runtime_noop_decision" = "runtime-identity-only" ]; then
printf 'false' > /tekton/results/runtime-ready-required
echo '{"status":"skipped-runtime-unchanged","reason":"runtime-identity-only","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
ci_timing_emit gitops-commit skipped "$(ci_now_ms)"
ci_timing_emit gitops-push skipped "$(ci_now_ms)"
exit 0
fi
fi
if git diff --cached --quiet; then
printf 'false' > /tekton/results/runtime-ready-required
echo '{"status":"unchanged","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
ci_timing_emit gitops-commit unchanged "$(ci_now_ms)"
ci_timing_emit gitops-push unchanged "$(ci_now_ms)"
exit 0
fi
short="$(printf '%.7s' "$(params.revision)")"
gitops_commit_started_ms="$(ci_now_ms)"
git commit -m "chore: promote G14 GitOps source $short"
ci_timing_emit gitops-commit succeeded "$gitops_commit_started_ms"
gitops_push_started_ms="$(ci_now_ms)"
git_timed gitops-push 120 git push origin "HEAD:$(params.gitops-branch)"
ci_timing_emit gitops-push succeeded "$gitops_push_started_ms"
argo_hard_refresh_v02
echo '{"status":"pushed","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'","gitopsWriteUrl":"'"$gitops_write_url"'"}'
`;
}
function controlPlaneReconcileScript() {
return `#!/bin/sh
set -eu
${dependencyProxyProbeScript("control-plane-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")}
echo '{"event":"ci-base-image","phase":"control-plane-reconciler","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'
for tool in node npm git python3 ssh curl timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"control-plane-reconciler","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
${gitSshShellFunction()}
git_ssh_setup
workdir="$(mktemp -d)"
git_timed control-plane-clone 180 git clone --depth 1 --branch "$SOURCE_BRANCH" "$GIT_READ_URL" "$workdir/repo"
cd "$workdir/repo"
git remote set-url origin "$GIT_READ_URL"
revision="$(git rev-parse HEAD)"
: "\${LANE:?LANE is required}"
: "\${CATALOG_PATH:?CATALOG_PATH is required}"
: "\${IMAGE_TAG_MODE:?IMAGE_TAG_MODE is required}"
node scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX"
node scripts/g14-gitops-render.mjs --lane "$LANE" --catalog-path "$CATALOG_PATH" --image-tag-mode "$IMAGE_TAG_MODE" --source-revision "$revision" --source-repo "$GIT_URL" --source-branch "$SOURCE_BRANCH" --gitops-branch "$GITOPS_BRANCH" --registry-prefix "$REGISTRY_PREFIX" --check
node <<'NODE'
const fs = require("node:fs");
const https = require("node:https");
function requiredEnv(name) {
const value = process.env[name];
if (!value) throw new Error(name + " is required");
return value;
}
const namespace = requiredEnv("POD_NAMESPACE");
const host = process.env.KUBERNETES_SERVICE_HOST;
const port = process.env.KUBERNETES_SERVICE_PORT || "443";
if (!host) throw new Error("KUBERNETES_SERVICE_HOST is not available");
const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8");
const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt");
const tektonDir = requiredEnv("TEKTON_DIR");
const argoFiles = String(process.env.ARGO_FILES || "").split(",").map((item) => item.trim()).filter(Boolean);
const fieldManager = requiredEnv("FIELD_MANAGER");
const eventName = requiredEnv("RECONCILE_EVENT");
const manifestFiles = [
"deploy/gitops/g14/" + tektonDir + "/rbac.yaml",
"deploy/gitops/g14/" + tektonDir + "/pipeline.yaml",
...(process.env.LANE === "v02" ? [] : [
"deploy/gitops/g14/" + tektonDir + "/poller.yaml",
"deploy/gitops/g14/" + tektonDir + "/control-plane-reconciler.yaml"
]),
...argoFiles
];
const plurals = new Map([
["ServiceAccount", "serviceaccounts"],
["Role", "roles"],
["RoleBinding", "rolebindings"],
["Pipeline", "pipelines"],
["CronJob", "cronjobs"],
["Application", "applications"],
["AppProject", "appprojects"]
]);
function encode(value) {
return encodeURIComponent(String(value));
}
function itemsFrom(file) {
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
return parsed.kind === "List" ? parsed.items || [] : [parsed];
}
function apiPath(item) {
if (item.kind === "Namespace") return null;
const name = item.metadata?.name;
if (!name) throw new Error("manifest item is missing metadata.name");
const ns = item.metadata?.namespace || namespace;
const plural = plurals.get(item.kind);
if (!plural) throw new Error("unsupported reconciler manifest kind " + item.kind);
if (item.apiVersion === "v1") return "/api/v1/namespaces/" + encode(ns) + "/" + plural + "/" + encode(name);
const parts = String(item.apiVersion || "").split("/");
if (parts.length !== 2) throw new Error("unsupported apiVersion " + item.apiVersion + " for " + item.kind);
return "/apis/" + encode(parts[0]) + "/" + encode(parts[1]) + "/namespaces/" + encode(ns) + "/" + plural + "/" + encode(name);
}
function reconcilePolicy(item) {
const ns = item.metadata?.namespace || namespace;
const crossNamespaceRbac = (item.kind === "Role" || item.kind === "RoleBinding") && ns !== namespace;
if (crossNamespaceRbac && process.env.HWLAB_RECONCILE_CROSS_NAMESPACE_RBAC !== "1") {
return { action: "skip", reason: "cross-namespace-rbac-bootstrap-managed", namespace: ns };
}
return { action: "apply", namespace: ns };
}
function request(method, path, body) {
const payload = body ? JSON.stringify(body) : "";
const headers = { Authorization: "Bearer " + token };
if (payload) {
headers["Content-Type"] = "application/apply-patch+yaml";
headers["Content-Length"] = Buffer.byteLength(payload);
}
return new Promise((resolve, reject) => {
const req = https.request({ host, port, method, path, ca, headers }, (res) => {
let data = "";
res.setEncoding("utf8");
res.on("data", (chunk) => { data += chunk; });
res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data }));
});
req.on("error", reject);
if (payload) req.write(payload);
req.end();
});
}
(async () => {
const results = [];
for (const file of manifestFiles) {
for (const item of itemsFrom(file)) {
const policy = reconcilePolicy(item);
if (policy.action === "skip") {
results.push({ file, kind: item.kind, name: item.metadata?.name || null, namespace: policy.namespace, status: "skipped", reason: policy.reason });
continue;
}
const path = apiPath(item);
if (!path) {
results.push({ file, kind: item.kind, name: item.metadata?.name || null, status: "skipped-cluster-scoped" });
continue;
}
const response = await request("PATCH", path + "?fieldManager=" + encode(fieldManager) + "&force=true", item);
if (response.statusCode < 200 || response.statusCode > 299) {
throw new Error("apply failed for " + item.kind + "/" + item.metadata.name + " status=" + response.statusCode + " body=" + response.body.slice(0, 1000));
}
results.push({ file, kind: item.kind, name: item.metadata.name, status: "applied", statusCode: response.statusCode });
}
}
console.log(JSON.stringify({ event: eventName, sourceRevision: process.env.RECONCILE_REVISION || null, results }, null, 2));
})().catch((error) => {
console.error(error.stack || error.message);
process.exitCode = 1;
});
NODE
`;
}
function pollerScript() {
return `#!/bin/sh
set -eu
${dependencyProxyProbeScript("poller-proxy-preflight", "http://deb.debian.org/debian/dists/bookworm/InRelease")}
echo '{"event":"ci-base-image","phase":"poller","image":"${ciToolsRunnerImage}","policy":"no-runtime-apk"}'
for tool in node git ssh curl timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"poller","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
${gitSshShellFunction()}
git_ssh_setup
workdir="$(mktemp -d)"
git_timed poller-clone 180 git clone --depth 1 --branch "$SOURCE_BRANCH" "$GIT_READ_URL" "$workdir/repo"
cd "$workdir/repo"
git remote set-url origin "$GIT_READ_URL"
revision="$(git rev-parse HEAD)"
subject="$(git log -1 --pretty=%s)"
case "$subject" in
"chore: promote G14 GitOps source "*)
echo "Skipping generated GitOps promotion commit $revision"
exit 0
;;
esac
short="$(printf '%.12s' "$revision")"
: "\${PIPELINERUN_PREFIX:?PIPELINERUN_PREFIX is required}"
: "\${GITOPS_TARGET:?GITOPS_TARGET is required}"
prefix="$PIPELINERUN_PREFIX"
name="$prefix-$short"
export POLLER_REVISION="$revision"
export POLLER_PIPELINERUN="$name"
export POLLER_SOURCE_SUBJECT="$subject"
echo "Checking $GITOPS_TARGET source $revision with PipelineRun $name"
node <<'NODE'
const fs = require("node:fs");
const https = require("node:https");
function requiredEnv(name) {
const value = process.env[name];
if (!value) throw new Error(name + " is required");
return value;
}
function optionalEnv(name) {
return process.env[name] || "";
}
const namespace = requiredEnv("POD_NAMESPACE");
const name = requiredEnv("POLLER_PIPELINERUN");
const revision = requiredEnv("POLLER_REVISION");
const host = process.env.KUBERNETES_SERVICE_HOST;
const port = process.env.KUBERNETES_SERVICE_PORT || "443";
if (!host) throw new Error("KUBERNETES_SERVICE_HOST is not available");
const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8");
const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt");
function request(method, path, body) {
const payload = body ? JSON.stringify(body) : "";
const headers = { Authorization: "Bearer " + token };
if (payload) {
headers["Content-Type"] = "application/json";
headers["Content-Length"] = Buffer.byteLength(payload);
}
return new Promise((resolve, reject) => {
const req = https.request({ host, port, method, path, ca, headers }, (res) => {
let data = "";
res.setEncoding("utf8");
res.on("data", (chunk) => { data += chunk; });
res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data }));
});
req.on("error", reject);
if (payload) req.write(payload);
req.end();
});
}
const labels = {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/gitops-target": requiredEnv("GITOPS_TARGET"),
"hwlab.pikastech.local/source-commit": revision,
"hwlab.pikastech.local/trigger": "polling"
};
const pipelineRun = {
apiVersion: "tekton.dev/v1",
kind: "PipelineRun",
metadata: {
name,
namespace,
labels,
annotations: {
"hwlab.pikastech.local/source-subject": optionalEnv("POLLER_SOURCE_SUBJECT"),
"hwlab.pikastech.local/source-branch": requiredEnv("SOURCE_BRANCH"),
"hwlab.pikastech.local/gitops-branch": requiredEnv("GITOPS_BRANCH")
}
},
spec: {
pipelineRef: { name: requiredEnv("PIPELINE_NAME") },
taskRunTemplate: {
serviceAccountName: requiredEnv("SERVICE_ACCOUNT_NAME"),
podTemplate: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", securityContext: { fsGroup: 1000 } }
},
params: [
{ name: "git-url", value: requiredEnv("GIT_URL") },
{ name: "git-read-url", value: requiredEnv("GIT_READ_URL") },
{ name: "source-branch", value: requiredEnv("SOURCE_BRANCH") },
{ name: "gitops-branch", value: requiredEnv("GITOPS_BRANCH") },
{ name: "lane", value: requiredEnv("LANE") },
{ name: "catalog-path", value: requiredEnv("CATALOG_PATH") },
{ name: "image-tag-mode", value: requiredEnv("IMAGE_TAG_MODE") },
{ name: "runtime-path", value: requiredEnv("RUNTIME_PATH") },
{ name: "revision", value: revision },
{ name: "registry-prefix", value: requiredEnv("REGISTRY_PREFIX") },
{ name: "services", value: requiredEnv("SERVICES") },
{ name: "base-image", value: requiredEnv("BASE_IMAGE") }
],
workspaces: [
{ name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } },
{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }
]
}
};
const base = "/apis/tekton.dev/v1/namespaces/" + encodeURIComponent(namespace) + "/pipelineruns";
const item = base + "/" + encodeURIComponent(name);
(async () => {
const existing = await request("GET", item);
if (existing.statusCode === 200) {
console.log(JSON.stringify({ status: "exists", pipelineRun: name, revision }));
return;
}
if (existing.statusCode !== 404) {
throw new Error("unexpected PipelineRun lookup status " + existing.statusCode + ": " + existing.body.slice(0, 500));
}
const created = await request("POST", base, pipelineRun);
if (created.statusCode < 200 || created.statusCode > 299) {
throw new Error("PipelineRun create failed with status " + created.statusCode + ": " + created.body.slice(0, 1000));
}
console.log(JSON.stringify({ status: "created", pipelineRun: name, revision }));
})().catch((error) => {
console.error(error.stack || error.message);
process.exitCode = 1;
});
NODE
`;
}
function ciLaneSettings(argsOrLane = "g14") {
const lane = typeof argsOrLane === "string" ? argsOrLane : argsOrLane.lane;
if (lane === "v02") {
return {
lane: "v02",
profile: "v02",
gitopsTarget: "v02",
pipelineName: "hwlab-v02-ci-image-publish",
pipelineRunPrefix: "hwlab-v02-ci-poll",
serviceAccountName: "hwlab-v02-tekton-runner",
pollerName: "hwlab-v02-branch-poller",
controlPlaneReconcilerName: "hwlab-v02-control-plane-reconciler",
tektonDir: "tekton-v02",
catalogPath: "deploy/artifact-catalog.v02.json",
imageTagMode: "full",
runtimeNamespace: "hwlab-v02"
};
}
return {
lane: "g14",
profile: "dev",
gitopsTarget: "g14",
pipelineName: "hwlab-g14-ci-image-publish",
pipelineRunPrefix: "hwlab-g14-ci-poll",
serviceAccountName: "hwlab-tekton-runner",
pollerName: "hwlab-g14-branch-poller",
controlPlaneReconcilerName: "hwlab-g14-control-plane-reconciler",
tektonDir: "tekton",
catalogPath: "deploy/artifact-catalog.dev.json",
imageTagMode: "short",
runtimeNamespace: "hwlab-dev"
};
}
function tektonRbac(args = { lane: "g14" }) {
const settings = ciLaneSettings(args);
if (settings.lane === "v02") {
return {
apiVersion: "v1",
kind: "List",
items: [
{ apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } },
{ apiVersion: "v1", kind: "ServiceAccount", metadata: { name: settings.serviceAccountName, namespace: "hwlab-ci" } },
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "Role",
metadata: { name: "hwlab-v02-runtime-observer", namespace: "hwlab-v02" },
rules: [
{ apiGroups: [""], resources: ["pods", "services", "configmaps"], verbs: ["get", "list", "watch"] },
{ apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"] },
{ apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] }
]
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: { name: "hwlab-v02-runtime-observer", namespace: "hwlab-v02" },
subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }],
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-v02-runtime-observer" }
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "Role",
metadata: { name: "hwlab-v02-pipelinerun-writer", namespace: "hwlab-ci" },
rules: [
{ apiGroups: ["tekton.dev"], resources: ["pipelineruns"], verbs: ["get", "list", "create"] },
{ apiGroups: ["tekton.dev"], resources: ["pipelines"], verbs: ["get", "patch", "create"] },
{ apiGroups: ["batch"], resources: ["cronjobs"], verbs: ["get", "patch", "create"] },
{ apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] },
{ apiGroups: [""], resources: ["serviceaccounts"], verbs: ["get", "patch", "create"] }
]
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: { name: "hwlab-v02-pipelinerun-writer", namespace: "hwlab-ci" },
subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }],
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-v02-pipelinerun-writer" }
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "Role",
metadata: { name: "hwlab-v02-argocd-reconciler", namespace: "argocd" },
rules: [
{ apiGroups: ["argoproj.io"], resources: ["applications", "appprojects"], verbs: ["get", "patch", "create"] },
{ apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] }
]
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: { name: "hwlab-v02-argocd-reconciler", namespace: "argocd" },
subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }],
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-v02-argocd-reconciler" }
}
]
};
}
return {
apiVersion: "v1",
kind: "List",
items: [
{ apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } },
{ apiVersion: "v1", kind: "ServiceAccount", metadata: { name: "hwlab-tekton-runner", namespace: "hwlab-ci" } },
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "Role",
metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" },
rules: [
{ apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] }
]
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: { name: "hwlab-g14-control-plane-reconciler", namespace: "hwlab-dev" },
subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }],
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-g14-control-plane-reconciler" }
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "Role",
metadata: { name: "hwlab-tekton-runtime-observer", namespace: "hwlab-dev" },
rules: [
{ apiGroups: [""], resources: ["pods", "services", "configmaps"], verbs: ["get", "list", "watch"] },
{ apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"] },
{ apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] }
]
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: { name: "hwlab-tekton-runtime-observer", namespace: "hwlab-dev" },
subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }],
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-tekton-runtime-observer" }
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "Role",
metadata: { name: "hwlab-tekton-pipelinerun-writer", namespace: "hwlab-ci" },
rules: [
{ apiGroups: ["tekton.dev"], resources: ["pipelineruns"], verbs: ["get", "list", "create"] },
{ apiGroups: ["tekton.dev"], resources: ["pipelines"], verbs: ["get", "patch", "create"] },
{ apiGroups: ["batch"], resources: ["cronjobs"], verbs: ["get", "patch", "create"] },
{ apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] },
{ apiGroups: [""], resources: ["serviceaccounts"], verbs: ["get", "patch", "create"] }
]
},
{
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: { name: "hwlab-tekton-pipelinerun-writer", namespace: "hwlab-ci" },
subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }],
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-tekton-pipelinerun-writer" }
}
]
};
}
function buildTaskName(serviceId) {
return `build-${serviceId}`;
}
function affectedResultName(serviceId) {
return `affected-${serviceId}`;
}
const serviceResultFields = Object.freeze([
"status",
"service-id",
"image",
"image-tag",
"digest",
"repository-digest",
"source-commit-id",
"component-input-hash",
"environment-input-hash",
"code-input-hash",
"runtime-mode",
"boot-repo",
"boot-commit",
"boot-sh",
"build-created-at",
"build-backend",
"reused-from"
]);
function serviceResultParamName(serviceId, field) {
return `${serviceId}-${field}`;
}
function serviceResultEnvName(serviceId, field) {
return `HWLAB_SERVICE_RESULT_${serviceId.toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}_${field.toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}`;
}
function serviceResultParams(serviceIds = defaultServiceIds) {
return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({ name: serviceResultParamName(serviceId, field), default: "" })));
}
function serviceResultParamValues(serviceIds = defaultServiceIds) {
return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({
name: serviceResultParamName(serviceId, field),
value: `$(tasks.${buildTaskName(serviceId)}.results.${field})`
})));
}
function serviceResultEnv(serviceIds = defaultServiceIds) {
return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({
name: serviceResultEnvName(serviceId, field),
value: `$(params.${serviceResultParamName(serviceId, field)})`
})));
}
function serviceWorkVolume() {
return { name: "service-work", emptyDir: {} };
}
function buildkitBinVolume() {
return { name: "buildkit-bin", emptyDir: {} };
}
function buildkitRunVolume() {
return { name: "buildkit-run", emptyDir: {} };
}
function prepareBuildkitClientStep() {
return {
name: "prepare-buildkit-client",
image: buildkitRunnerImage,
securityContext: { runAsUser: 0, runAsGroup: 0 },
volumeMounts: [{ name: "buildkit-bin", mountPath: "/workspace/buildkit-bin" }],
script: `#!/bin/sh
set -eu
mkdir -p /workspace/buildkit-bin
cp /usr/bin/buildctl /workspace/buildkit-bin/
chmod +x /workspace/buildkit-bin/*
`
};
}
function buildkitSidecar() {
return {
name: "buildkitd",
image: buildkitRunnerImage,
args: ["--addr", "unix:///workspace/buildkit-run/buildkitd.sock", "--oci-worker-no-process-sandbox"],
env: proxyEnv(),
volumeMounts: [{ name: "buildkit-run", mountPath: "/workspace/buildkit-run" }],
securityContext: {
runAsUser: 1000,
runAsGroup: 1000,
allowPrivilegeEscalation: true,
appArmorProfile: { type: "Unconfined" },
seccompProfile: { type: "Unconfined" }
}
};
}
function planArtifactsTask({ serviceIds = defaultServiceIds } = {}) {
return {
name: "plan-artifacts",
runAfter: primitiveValidationTaskNames(),
workspaces: [{ name: "source", workspace: "source" }],
taskSpec: {
params: [
{ name: "revision" },
{ name: "catalog-path" },
{ name: "registry-prefix" },
{ name: "services" }
],
results: serviceIds.map((serviceId) => ({ name: affectedResultName(serviceId), description: `${serviceId} changed according to g14-ci-plan` })),
workspaces: [{ name: "source" }],
steps: [{
name: "plan",
image: ciToolsRunnerImage,
env: [{ name: "HWLAB_SELECTED_SERVICES", value: "$(params.services)" }, ...proxyEnv()],
script: planArtifactsScript()
}]
},
params: [
{ name: "revision", value: "$(params.revision)" },
{ name: "catalog-path", value: "$(params.catalog-path)" },
{ name: "registry-prefix", value: "$(params.registry-prefix)" },
{ name: "services", value: "$(params.services)" }
]
};
}
function perServiceBuildTask(serviceId, { runAfter = ["plan-artifacts"] } = {}) {
return {
name: buildTaskName(serviceId),
runAfter,
workspaces: [{ name: "source", workspace: "source" }],
when: [{ input: `$(tasks.plan-artifacts.results.${affectedResultName(serviceId)})`, operator: "in", values: ["true"] }],
taskSpec: {
params: [
{ name: "revision" },
{ name: "lane" },
{ name: "catalog-path" },
{ name: "image-tag-mode" },
{ name: "registry-prefix" },
{ name: "service-id" },
{ name: "base-image" }
],
results: serviceResultFields.map((field) => ({ name: field, description: `${serviceId} ${field}` })),
workspaces: [{ name: "source" }],
volumes: [serviceWorkVolume(), buildkitBinVolume(), buildkitRunVolume()],
sidecars: [buildkitSidecar()],
steps: [
prepareBuildkitClientStep(),
{
name: "publish",
image: ciToolsRunnerImage,
securityContext: { runAsUser: 1000, runAsGroup: 1000 },
env: proxyEnv(),
volumeMounts: [{ name: "service-work", mountPath: "/workspace/service-work" }, { name: "buildkit-bin", mountPath: "/workspace/buildkit-bin" }, { name: "buildkit-run", mountPath: "/workspace/buildkit-run" }],
script: perServicePublishScript()
}
]
},
params: [
{ name: "revision", value: "$(params.revision)" },
{ name: "lane", value: "$(params.lane)" },
{ name: "catalog-path", value: "$(params.catalog-path)" },
{ name: "image-tag-mode", value: "$(params.image-tag-mode)" },
{ name: "registry-prefix", value: "$(params.registry-prefix)" },
{ name: "service-id", value: serviceId },
{ name: "base-image", value: "$(params.base-image)" }
]
};
}
function collectArtifactsTask({ serviceIds = defaultServiceIds } = {}) {
return {
name: "collect-artifacts",
runAfter: serviceIds.map(buildTaskName),
workspaces: [{ name: "source", workspace: "source" }],
taskSpec: {
params: [
{ name: "revision" },
{ name: "lane" },
{ name: "catalog-path" },
{ name: "image-tag-mode" },
{ name: "registry-prefix" },
{ name: "services" },
{ name: "base-image" }
],
workspaces: [{ name: "source" }],
steps: [{ name: "collect", image: ciToolsRunnerImage, env: proxyEnv(), script: collectArtifactsScript() }]
},
params: [
{ name: "revision", value: "$(params.revision)" },
{ name: "lane", value: "$(params.lane)" },
{ name: "catalog-path", value: "$(params.catalog-path)" },
{ name: "image-tag-mode", value: "$(params.image-tag-mode)" },
{ name: "registry-prefix", value: "$(params.registry-prefix)" },
{ name: "services", value: "$(params.services)" },
{ name: "base-image", value: "$(params.base-image)" }
]
};
}
function runtimeReadyScript() {
return `#!/bin/sh
set -eu
${ciTimingShellFunction()}
export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)"
export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)"
export HWLAB_TEKTON_TASK="runtime-ready"
export HWLAB_SOURCE_REVISION="$(params.revision)"
node <<'NODE'
const fs = require("node:fs");
const https = require("node:https");
const host = process.env.KUBERNETES_SERVICE_HOST;
const port = process.env.KUBERNETES_SERVICE_PORT || "443";
function requiredEnv(name) {
const value = process.env[name];
if (!value) throw new Error(name + " is required");
return value;
}
const revision = requiredEnv("HWLAB_SOURCE_REVISION");
const runtimeNamespace = requiredEnv("HWLAB_RUNTIME_NAMESPACE");
const gitopsTarget = requiredEnv("HWLAB_GITOPS_TARGET");
const pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;
const taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;
const task = process.env.HWLAB_TEKTON_TASK || null;
const timeoutMs = 240000;
const pollMs = 1000;
function emit(payload) {
console.log(JSON.stringify({
event: "g14-cicd-timing",
schemaVersion: "v1",
pipelineRun,
taskRun,
task,
revision,
source: "scripts/g14-gitops-render.mjs",
at: new Date().toISOString(),
...payload
}));
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function safeJson(text) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
if (!host) {
emit({ stage: "argo-refresh", status: "failed", reason: "kubernetes-service-host-missing", durationMs: 0 });
process.exit(1);
}
const token = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/token", "utf8");
const ca = fs.readFileSync("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt");
function request(method, path, body, contentType = "application/json") {
const payload = body ? JSON.stringify(body) : "";
const headers = { Authorization: "Bearer " + token };
if (payload) {
headers["Content-Type"] = contentType;
headers["Content-Length"] = Buffer.byteLength(payload);
}
return new Promise((resolve, reject) => {
const req = https.request({ host, port, method, path, ca, headers }, (res) => {
let data = "";
res.setEncoding("utf8");
res.on("data", (chunk) => { data += chunk; });
res.on("end", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) }));
});
req.on("error", reject);
if (payload) req.write(payload);
req.end();
});
}
function listPath(group, version, namespace, resource) {
if (group) return "/apis/" + group + "/" + version + "/namespaces/" + encodeURIComponent(namespace) + "/" + resource;
return "/api/" + version + "/namespaces/" + encodeURIComponent(namespace) + "/" + resource;
}
async function runtimeItems() {
const [deployments, statefulsets] = await Promise.all([
request("GET", listPath("apps", "v1", runtimeNamespace, "deployments")),
request("GET", listPath("apps", "v1", runtimeNamespace, "statefulsets"))
]);
if (deployments.statusCode === 403 || statefulsets.statusCode === 403) return { status: "rbac-denied", items: [] };
const items = [...(deployments.json?.items || []), ...(statefulsets.json?.items || [])]
.filter((item) => item.metadata?.labels?.["hwlab.pikastech.local/gitops-target"] === gitopsTarget);
return { status: "ok", items };
}
function runtimeSourceCommit(item) {
return item.metadata?.labels?.["hwlab.pikastech.local/source-commit"] ||
item.metadata?.annotations?.["hwlab.pikastech.local/source-commit"] ||
item.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/source-commit"] ||
item.spec?.template?.metadata?.annotations?.["hwlab.pikastech.local/source-commit"] ||
null;
}
function readyWorkload(item) {
const specReplicas = item.spec?.replicas ?? 1;
if (specReplicas === 0) return { ready: true, skipped: true };
const status = item.status || {};
const desired = specReplicas;
const ready = status.readyReplicas ?? 0;
const updated = status.updatedReplicas ?? 0;
const observedGeneration = status.observedGeneration ?? 0;
const generation = item.metadata?.generation ?? 0;
return { ready: observedGeneration >= generation && ready >= desired && updated >= desired, desired, readyReplicas: ready, updatedReplicas: updated };
}
async function waitForArgoRefresh() {
const startedAt = Date.now();
for (;;) {
const runtime = await runtimeItems();
if (runtime.status === "rbac-denied") {
emit({ stage: "argo-refresh", status: "failed", reason: "runtime-observer-rbac-denied", durationMs: Date.now() - startedAt });
process.exitCode = 1;
return { observed: false, skipped: true };
}
const pending = runtime.items
.map((item) => ({ kind: item.kind, name: item.metadata?.name, sourceCommit: runtimeSourceCommit(item) }))
.filter((item) => item.sourceCommit !== revision);
if (runtime.items.length > 0 && pending.length === 0) {
emit({ stage: "argo-refresh", status: "succeeded", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length });
return { observed: true, skipped: false };
}
if (Date.now() - startedAt >= timeoutMs) {
emit({ stage: "argo-refresh", status: "timeout", reason: "source-commit-refresh-timeout", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length, pending: pending.slice(0, 8) });
process.exitCode = 1;
return { observed: false, skipped: false };
}
await sleep(pollMs);
}
}
async function waitForWorkloads() {
const startedAt = Date.now();
for (;;) {
const runtime = await runtimeItems();
if (runtime.status === "rbac-denied") {
emit({ stage: "workload-ready", status: "failed", reason: "runtime-observer-rbac-denied", durationMs: Date.now() - startedAt });
process.exitCode = 1;
return;
}
const blocked = runtime.items.map((item) => ({ kind: item.kind, name: item.metadata?.name, ...readyWorkload(item) })).filter((item) => !item.ready);
if (runtime.items.length > 0 && blocked.length === 0) {
emit({ stage: "workload-ready", status: "succeeded", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length });
return;
}
if (Date.now() - startedAt >= timeoutMs) {
emit({ stage: "workload-ready", status: "timeout", durationMs: Date.now() - startedAt, workloadCount: runtime.items.length, blocked: blocked.slice(0, 8) });
process.exitCode = 1;
return;
}
await sleep(pollMs);
}
}
(async () => {
const refresh = await waitForArgoRefresh();
if (!refresh.observed) return;
await waitForWorkloads();
})().catch((error) => {
emit({ stage: "runtime-ready", status: "failed", reason: error.message, durationMs: 0 });
process.exitCode = 1;
});
NODE
`;
}
function runtimeReadyTask({ profile = "dev" } = {}) {
return {
name: "runtime-ready",
runAfter: ["gitops-promote"],
taskSpec: {
params: [{ name: "revision" }, { name: "runtime-namespace" }, { name: "gitops-target" }],
steps: [{
name: "runtime-ready",
image: ciToolsRunnerImage,
env: [
...proxyEnv(),
{ name: "HWLAB_RUNTIME_NAMESPACE", value: "$(params.runtime-namespace)" },
{ name: "HWLAB_GITOPS_TARGET", value: "$(params.gitops-target)" }
],
script: runtimeReadyScript()
}]
},
params: [
{ name: "revision", value: "$(params.revision)" },
{ name: "runtime-namespace", value: namespaceNameForProfile(profile) },
{ name: "gitops-target", value: gitopsTargetForProfile(profile) }
]
};
}
function imagePublishTaskSet(args = { lane: "g14" }) {
const serviceIds = serviceIdsForLane(args.lane);
const buildTasks = serviceIds.map((serviceId) => perServiceBuildTask(serviceId, {
runAfter: ["plan-artifacts"]
}));
return [
planArtifactsTask({ serviceIds }),
...buildTasks,
collectArtifactsTask({ serviceIds })
];
}
function tektonPipeline(args = { lane: "g14" }) {
const settings = ciLaneSettings(args);
const runtimePath = `deploy/gitops/g14/${runtimePathForProfile(settings.profile)}`;
return {
apiVersion: "tekton.dev/v1",
kind: "Pipeline",
metadata: {
name: settings.pipelineName,
namespace: "hwlab-ci",
labels: {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/gitops-target": settings.gitopsTarget
},
annotations: {
"hwlab.pikastech.local/source-config": "scripts/g14-gitops-render.mjs#tekton-native-primitive-ci",
"hwlab.pikastech.local/ci-contract": "tekton-native-primitive-tasks",
"hwlab.pikastech.local/policy": "native-per-service-taskrun-image-publish"
}
},
spec: {
params: [
{ name: "git-url", type: "string", default: defaultSourceRepo },
{ name: "git-read-url", type: "string", default: args.gitReadUrl },
{ name: "git-write-url", type: "string", default: args.gitWriteUrl },
{ name: "source-branch", type: "string", default: args.sourceBranch },
{ name: "gitops-branch", type: "string", default: args.gitopsBranch },
{ name: "lane", type: "string", default: settings.lane },
{ name: "catalog-path", type: "string", default: args.catalogPath || settings.catalogPath },
{ name: "image-tag-mode", type: "string", default: args.imageTagMode || settings.imageTagMode },
{ name: "runtime-path", type: "string", default: runtimePath },
{ name: "revision", type: "string", description: "Full git commit SHA; the only source truth for image tags." },
{ name: "registry-prefix", type: "string", default: defaultRegistryPrefix },
{ name: "services", type: "string", default: servicesParamForLane(args.lane) },
{ name: "base-image", type: "string", default: defaultDevBaseImage }
],
workspaces: [
{ name: "source" },
{ name: "git-ssh" }
],
tasks: [
{
name: "prepare-source",
workspaces: [
{ name: "source", workspace: "source" },
{ name: "git-ssh", workspace: "git-ssh" }
],
taskSpec: {
params: [
{ name: "git-url" },
{ name: "git-read-url" },
{ name: "source-branch" },
{ name: "gitops-branch" },
{ name: "lane" },
{ name: "catalog-path" },
{ name: "image-tag-mode" },
{ name: "registry-prefix" },
{ name: "services" },
{ name: "revision" }
],
workspaces: [{ name: "source" }, { name: "git-ssh" }],
steps: [{ name: "prepare-source", image: ciToolsRunnerImage, env: proxyEnv(), script: prepareSourceScript() }]
},
params: [
{ name: "git-url", value: "$(params.git-url)" },
{ name: "git-read-url", value: "$(params.git-read-url)" },
{ name: "source-branch", value: "$(params.source-branch)" },
{ name: "gitops-branch", value: "$(params.gitops-branch)" },
{ name: "lane", value: "$(params.lane)" },
{ name: "catalog-path", value: "$(params.catalog-path)" },
{ name: "image-tag-mode", value: "$(params.image-tag-mode)" },
{ name: "registry-prefix", value: "$(params.registry-prefix)" },
{ name: "services", value: "$(params.services)" },
{ name: "revision", value: "$(params.revision)" }
]
},
...primitiveValidationTasks.map(primitiveValidationTask),
...imagePublishTaskSet(args),
{
name: "gitops-promote",
runAfter: ["collect-artifacts"],
workspaces: [
{ name: "source", workspace: "source" },
{ name: "git-ssh", workspace: "git-ssh" }
],
taskSpec: {
params: [
{ name: "git-url" },
{ name: "git-read-url" },
{ name: "git-write-url" },
{ name: "source-branch" },
{ name: "gitops-branch" },
{ name: "lane" },
{ name: "catalog-path" },
{ name: "image-tag-mode" },
{ name: "runtime-path" },
{ name: "revision" },
{ name: "registry-prefix" }
],
results: [{ name: "runtime-ready-required", description: "true when GitOps promotion changed runtime desired state and runtime readiness must be observed" }],
workspaces: [{ name: "source" }, { name: "git-ssh" }],
steps: [{ name: "promote", image: ciToolsRunnerImage, env: proxyEnv(), script: gitopsPromoteScript() }]
},
params: [
{ name: "git-url", value: "$(params.git-url)" },
{ name: "git-read-url", value: "$(params.git-read-url)" },
{ name: "git-write-url", value: "$(params.git-write-url)" },
{ name: "source-branch", value: "$(params.source-branch)" },
{ name: "gitops-branch", value: "$(params.gitops-branch)" },
{ name: "lane", value: "$(params.lane)" },
{ name: "catalog-path", value: "$(params.catalog-path)" },
{ name: "image-tag-mode", value: "$(params.image-tag-mode)" },
{ name: "runtime-path", value: "$(params.runtime-path)" },
{ name: "revision", value: "$(params.revision)" },
{ name: "registry-prefix", value: "$(params.registry-prefix)" }
]
},
{
...runtimeReadyTask({ profile: settings.profile }),
when: [{ input: "$(tasks.gitops-promote.results.runtime-ready-required)", operator: "in", values: ["true"] }]
}
]
}
};
}
function tektonPipelineRunTemplate({ source, args }) {
const settings = ciLaneSettings(args);
const runtimePath = `deploy/gitops/g14/${runtimePathForProfile(settings.profile)}`;
return {
apiVersion: "tekton.dev/v1",
kind: "PipelineRun",
metadata: {
generateName: `${settings.pipelineRunPrefix}-manual-`,
namespace: "hwlab-ci",
labels: {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/gitops-target": settings.gitopsTarget,
"hwlab.pikastech.local/source-commit": source.full
}
},
spec: {
pipelineRef: { name: settings.pipelineName },
taskRunTemplate: {
serviceAccountName: settings.serviceAccountName,
podTemplate: {
hostNetwork: true,
dnsPolicy: "ClusterFirstWithHostNet",
securityContext: { fsGroup: 1000 }
}
},
params: [
{ name: "git-url", value: args.sourceRepo },
{ name: "git-read-url", value: args.gitReadUrl },
{ name: "git-write-url", value: args.gitWriteUrl },
{ name: "source-branch", value: args.sourceBranch },
{ name: "gitops-branch", value: args.gitopsBranch },
{ name: "lane", value: settings.lane },
{ name: "catalog-path", value: args.catalogPath || settings.catalogPath },
{ name: "image-tag-mode", value: args.imageTagMode || settings.imageTagMode },
{ name: "runtime-path", value: runtimePath },
{ name: "revision", value: source.full },
{ name: "registry-prefix", value: args.registryPrefix },
{ name: "base-image", value: defaultDevBaseImage }
],
workspaces: [
{ name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } },
{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }
]
}
};
}
function tektonPollerCronJob(args) {
const settings = ciLaneSettings(args);
if (settings.lane === "v02") throw new Error("v02 CI/CD is manually triggered by UniDesk CLI and must not render a branch poller CronJob");
return {
apiVersion: "batch/v1",
kind: "CronJob",
metadata: {
name: settings.pollerName,
namespace: "hwlab-ci",
labels: {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/gitops-target": settings.gitopsTarget
},
annotations: {
"hwlab.pikastech.local/source-branch": args.sourceBranch,
"hwlab.pikastech.local/gitops-branch": args.gitopsBranch
}
},
spec: {
schedule: settings.lane === "v02" ? "*/10 * * * *" : "* * * * *",
concurrencyPolicy: "Forbid",
successfulJobsHistoryLimit: 3,
failedJobsHistoryLimit: 3,
jobTemplate: {
spec: {
backoffLimit: 1,
template: {
metadata: {
labels: {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/gitops-target": settings.gitopsTarget
}
},
spec: {
serviceAccountName: settings.serviceAccountName,
restartPolicy: "Never",
hostNetwork: true,
dnsPolicy: "ClusterFirstWithHostNet",
containers: [{
name: "poll",
image: ciToolsRunnerImage,
imagePullPolicy: "IfNotPresent",
env: [
...proxyEnv(),
{ name: "POD_NAMESPACE", valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } },
{ name: "PIPELINE_NAME", value: settings.pipelineName },
{ name: "PIPELINERUN_PREFIX", value: settings.pipelineRunPrefix },
{ name: "SERVICE_ACCOUNT_NAME", value: settings.serviceAccountName },
{ name: "GITOPS_TARGET", value: settings.gitopsTarget },
{ name: "LANE", value: settings.lane },
{ name: "CATALOG_PATH", value: args.catalogPath || settings.catalogPath },
{ name: "IMAGE_TAG_MODE", value: args.imageTagMode || settings.imageTagMode },
{ name: "RUNTIME_PATH", value: `deploy/gitops/g14/${runtimePathForProfile(settings.profile)}` },
{ name: "GIT_URL", value: args.sourceRepo },
{ name: "GIT_READ_URL", value: args.gitReadUrl },
{ name: "SOURCE_BRANCH", value: args.sourceBranch },
{ name: "GITOPS_BRANCH", value: args.gitopsBranch },
{ name: "REGISTRY_PREFIX", value: args.registryPrefix },
{ name: "SERVICES", value: servicesParamForLane(args.lane) },
{ name: "BASE_IMAGE", value: defaultDevBaseImage }
],
volumeMounts: [{ name: "git-ssh", mountPath: "/workspace/git-ssh", readOnly: true }],
command: ["/bin/sh", "-c"],
args: [pollerScript()]
}],
volumes: [{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }]
}
}
}
}
}
};
}
function tektonControlPlaneReconcilerCronJob(args) {
const settings = ciLaneSettings(args);
if (settings.lane === "v02") throw new Error("v02 CI/CD is manually triggered by UniDesk CLI and must not render a control-plane reconciler CronJob");
return {
apiVersion: "batch/v1",
kind: "CronJob",
metadata: {
name: settings.controlPlaneReconcilerName,
namespace: "hwlab-ci",
labels: {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/gitops-target": settings.gitopsTarget
},
annotations: {
"hwlab.pikastech.local/source-branch": args.sourceBranch,
"hwlab.pikastech.local/gitops-branch": args.gitopsBranch,
"hwlab.pikastech.local/purpose": "render-and-apply-g14-ci-control-plane"
}
},
spec: {
schedule: "*/10 * * * *",
concurrencyPolicy: "Forbid",
successfulJobsHistoryLimit: 3,
failedJobsHistoryLimit: 3,
jobTemplate: {
spec: {
backoffLimit: 1,
template: {
metadata: {
labels: {
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/gitops-target": settings.gitopsTarget
}
},
spec: {
serviceAccountName: settings.serviceAccountName,
restartPolicy: "Never",
hostNetwork: true,
dnsPolicy: "ClusterFirstWithHostNet",
containers: [{
name: "reconcile",
image: ciToolsRunnerImage,
imagePullPolicy: "IfNotPresent",
env: [
...proxyEnv(),
{ name: "POD_NAMESPACE", valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } },
{ name: "LANE", value: settings.lane },
{ name: "CATALOG_PATH", value: args.catalogPath || settings.catalogPath },
{ name: "IMAGE_TAG_MODE", value: args.imageTagMode || settings.imageTagMode },
{ name: "TEKTON_DIR", value: settings.tektonDir },
{ name: "ARGO_FILES", value: settings.lane === "v02" ? "deploy/gitops/g14/argocd/project.yaml,deploy/gitops/g14/argocd/application-v02.yaml" : "" },
{ name: "FIELD_MANAGER", value: settings.controlPlaneReconcilerName },
{ name: "RECONCILE_EVENT", value: `${settings.gitopsTarget}-control-plane-reconciled` },
{ name: "GIT_URL", value: args.sourceRepo },
{ name: "GIT_READ_URL", value: args.gitReadUrl },
{ name: "SOURCE_BRANCH", value: args.sourceBranch },
{ name: "GITOPS_BRANCH", value: args.gitopsBranch },
{ name: "REGISTRY_PREFIX", value: args.registryPrefix }
],
volumeMounts: [{ name: "git-ssh", mountPath: "/workspace/git-ssh", readOnly: true }],
command: ["/bin/sh", "-c"],
args: [controlPlaneReconcileScript()]
}],
volumes: [{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }]
}
}
}
}
}
};
}
function registryManifest() {
return {
apiVersion: "v1",
kind: "List",
items: [
{ apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } },
{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } },
spec: {
replicas: 1,
selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-registry" } },
template: {
metadata: { labels: { "app.kubernetes.io/name": "hwlab-registry" } },
spec: {
hostNetwork: true,
dnsPolicy: "ClusterFirstWithHostNet",
containers: [{
name: "registry",
image: "registry:2.8.3",
imagePullPolicy: "IfNotPresent",
ports: [{ name: "registry", containerPort: 5000, hostPort: 5000 }],
env: [{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" }],
volumeMounts: [{ name: "storage", mountPath: "/var/lib/registry" }],
readinessProbe: { httpGet: { path: "/v2/", port: "registry" } },
livenessProbe: { httpGet: { path: "/v2/", port: "registry" } }
}],
volumes: [{ name: "storage", hostPath: { path: "/var/lib/hwlab/registry", type: "DirectoryOrCreate" } }]
}
}
}
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } },
spec: { selector: { "app.kubernetes.io/name": "hwlab-registry" }, ports: [{ name: "registry", port: 5000, targetPort: "registry" }] }
}
]
};
}
function devopsInfraGitMirrorManifest() {
const labels = {
"app.kubernetes.io/name": "git-mirror",
"app.kubernetes.io/part-of": "devops-infra"
};
return {
apiVersion: "v1",
kind: "List",
items: [
{ apiVersion: "v1", kind: "Namespace", metadata: { name: "devops-infra" } },
{
apiVersion: "v1",
kind: "PersistentVolumeClaim",
metadata: { name: "git-mirror-cache", namespace: "devops-infra", labels },
spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "20Gi" } } }
},
{
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: "git-mirror-sync-script", namespace: "devops-infra", labels },
data: {
"sync.sh": `#!/bin/sh
set -eu
repo_url="ssh://git@ssh.github.com:443/pikasTech/HWLAB.git"
repo_path="/cache/pikasTech/HWLAB.git"
lock_dir="/cache/.hwlab-sync.lock"
sync_started_ms=$(node -e 'console.log(Date.now())')
now_ms() { node -e 'console.log(Date.now())'; }
emit_timing() {
phase="$1"
status="$2"
started_ms="$3"
finished_ms=$(now_ms)
duration_ms=$((finished_ms - started_ms))
printf '{"event":"git-mirror-sync-timing","phase":"%s","status":"%s","durationMs":%s,"repository":"pikasTech/HWLAB"}\n' "$phase" "$status" "$duration_ms"
}
mkdir -p /cache/pikasTech /root/.ssh
if ! mkdir "$lock_dir" 2>/dev/null; then
echo "git mirror sync already running"
exit 0
fi
trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT INT TERM
cp /git-ssh/ssh-privatekey /root/.ssh/id_rsa
chmod 0400 /root/.ssh/id_rsa
export GIT_SSH_COMMAND="ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=1"
if [ -d "$repo_path/objects" ] && [ -f "$repo_path/HEAD" ]; then
git -C "$repo_path" remote set-url origin "$repo_url" || git -C "$repo_path" remote add origin "$repo_url"
else
rm -rf "$repo_path"
git init --bare "$repo_path"
git -C "$repo_path" remote add origin "$repo_url"
fi
/script/install-hooks.sh
git -C "$repo_path" config uploadpack.hideRefs refs/mirror-stage
git -C "$repo_path" config --unset-all remote.origin.fetch 2>/dev/null || true
git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/v0.2:refs/mirror-stage/heads/v0.2'
git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/v0.2-gitops:refs/mirror-stage/heads/v0.2-gitops'
git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/G14:refs/mirror-stage/heads/G14'
git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/G14-gitops:refs/mirror-stage/heads/G14-gitops'
fetch_started_ms=$(now_ms)
timeout 180 git -C "$repo_path" fetch --quiet --prune origin
emit_timing fetch succeeded "$fetch_started_ms"
git -C "$repo_path" for-each-ref --format='%(objectname) %(refname)' refs/mirror-stage/heads > /tmp/hwlab-stage-heads
git -C "$repo_path" for-each-ref --format='%(refname)' refs/mirror-stage/heads > /tmp/hwlab-stage-head-refs
git -C "$repo_path" for-each-ref --format='%(objectname) %(refname)' refs/mirror-stage/tags > /tmp/hwlab-stage-tags
git -C "$repo_path" for-each-ref --format='%(refname)' refs/mirror-stage/tags > /tmp/hwlab-stage-tag-refs
if [ ! -s /tmp/hwlab-stage-heads ]; then
echo "git mirror sync fetched no branch refs" >&2
exit 41
fi
for required_ref in refs/mirror-stage/heads/v0.2 refs/mirror-stage/heads/v0.2-gitops refs/mirror-stage/heads/G14 refs/mirror-stage/heads/G14-gitops; do
git -C "$repo_path" show-ref --verify --quiet "$required_ref" || { echo "git mirror sync missing required ref $required_ref" >&2; exit 43; }
done
validate_started_ms=$(now_ms)
while read -r sha ref; do
[ -n "$sha" ] || continue
git -C "$repo_path" cat-file -e "$sha^{commit}"
git -C "$repo_path" cat-file -e "$sha^{tree}"
if git -C "$repo_path" rev-list --objects --missing=print "$sha" | grep -q '^?'; then
echo "git mirror sync found missing objects for $ref $sha" >&2
exit 42
fi
done < /tmp/hwlab-stage-heads
emit_timing validate succeeded "$validate_started_ms"
publish_started_ms=$(now_ms)
while read -r sha ref; do
[ -n "$sha" ] || continue
name=$(printf '%s\n' "$ref" | sed 's#^refs/mirror-stage/heads/##')
if [ "$name" = "v0.2-gitops" ]; then
local_sha=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true)
if [ -n "$local_sha" ] && [ "$local_sha" != "$sha" ]; then
if git -C "$repo_path" merge-base --is-ancestor "$sha" "$local_sha"; then
echo "git mirror sync keeps local v0.2-gitops ahead of GitHub"
continue
fi
if ! git -C "$repo_path" merge-base --is-ancestor "$local_sha" "$sha"; then
echo "git mirror sync found divergent v0.2-gitops local=$local_sha github=$sha" >&2
exit 44
fi
fi
fi
git -C "$repo_path" update-ref "refs/heads/$name" "$sha"
done < /tmp/hwlab-stage-heads
while read -r sha ref; do
[ -n "$sha" ] || continue
name=$(printf '%s\n' "$ref" | sed 's#^refs/mirror-stage/tags/##')
git -C "$repo_path" update-ref "refs/tags/$name" "$sha"
done < /tmp/hwlab-stage-tags
git -C "$repo_path" for-each-ref --format='%(refname)' refs/heads > /tmp/hwlab-public-heads
while read -r ref; do
[ -n "$ref" ] || continue
name=$(printf '%s\n' "$ref" | sed 's#^refs/heads/##')
if ! grep -Fxq "refs/mirror-stage/heads/$name" /tmp/hwlab-stage-head-refs; then
git -C "$repo_path" update-ref -d "$ref"
fi
done < /tmp/hwlab-public-heads
git -C "$repo_path" for-each-ref --format='%(refname)' refs/tags > /tmp/hwlab-public-tags
while read -r ref; do
[ -n "$ref" ] || continue
name=$(printf '%s\n' "$ref" | sed 's#^refs/tags/##')
if ! grep -Fxq "refs/mirror-stage/tags/$name" /tmp/hwlab-stage-tag-refs; then
git -C "$repo_path" update-ref -d "$ref"
fi
done < /tmp/hwlab-public-tags
emit_timing publish succeeded "$publish_started_ms"
if git -C "$repo_path" show-ref --verify --quiet refs/heads/v0.2; then
git -C "$repo_path" symbolic-ref HEAD refs/heads/v0.2
elif git -C "$repo_path" show-ref --verify --quiet refs/heads/G14; then
git -C "$repo_path" symbolic-ref HEAD refs/heads/G14
fi
fsck_started_ms=$(now_ms)
git -C "$repo_path" fsck --connectivity-only --no-dangling >/tmp/hwlab-git-fsck.out
emit_timing fsck succeeded "$fsck_started_ms"
git -C "$repo_path" update-server-info
v02_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2 2>/dev/null || true)
g14_head=$(git -C "$repo_path" rev-parse refs/heads/G14 2>/dev/null || true)
v02_gitops_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true)
v02_gitops_github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/v0.2-gitops 2>/dev/null || true)
published_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
printf '%s\n' "$v02_head" | tee /cache/HWLAB-v0.2.head
printf '%s\n' "$published_at" > /cache/HWLAB.last-sync
export v02_head g14_head v02_gitops_head v02_gitops_github_head published_at
node - <<'NODE' | tee /cache/HWLAB.last-sync.json
const payload = {
event: "git-mirror-sync",
status: "published",
repository: "pikasTech/HWLAB",
publishedAt: process.env.published_at,
pendingFlush: Boolean(process.env.v02_gitops_head && process.env.v02_gitops_github_head && process.env.v02_gitops_head !== process.env.v02_gitops_github_head),
refs: {
"refs/heads/v0.2": process.env.v02_head || null,
"refs/heads/G14": process.env.g14_head || null,
"refs/heads/v0.2-gitops": process.env.v02_gitops_head || null,
"refs/mirror-stage/heads/v0.2-gitops": process.env.v02_gitops_github_head || null
}
};
console.log(JSON.stringify(payload));
NODE
emit_timing total succeeded "$sync_started_ms"
`,
"install-hooks.sh": `#!/bin/sh
set -eu
repo_path="/cache/pikasTech/HWLAB.git"
mkdir -p "$repo_path/hooks"
cat > "$repo_path/hooks/pre-receive" <<'HOOK'
#!/bin/sh
set -eu
zero="0000000000000000000000000000000000000000"
status=0
while read -r old new ref; do
if [ "$ref" != "refs/heads/v0.2-gitops" ]; then
echo "git mirror write rejected: ref $ref is not allowlisted" >&2
status=1
continue
fi
if [ "$new" = "$zero" ]; then
echo "git mirror write rejected: deleting $ref is forbidden" >&2
status=1
continue
fi
git cat-file -e "$new^{commit}" || status=1
git cat-file -e "$new^{tree}" || status=1
if git rev-list --objects --missing=print "$new" | grep -q '^?'; then
echo "git mirror write rejected: missing objects for $new" >&2
status=1
continue
fi
if [ "$old" != "$zero" ] && ! git merge-base --is-ancestor "$old" "$new"; then
echo "git mirror write rejected: non-fast-forward $ref" >&2
status=1
continue
fi
if [ "$old" = "$zero" ]; then
git diff-tree --no-commit-id --name-only -r "$new"
else
git diff --name-only "$old" "$new"
fi | while read -r path; do
case "$path" in
deploy/artifact-catalog.v02.json|deploy/gitops/g14/runtime-v02/*) ;;
"") ;;
*) echo "git mirror write rejected: path $path is outside v0.2 GitOps outputs" >&2; exit 64 ;;
esac
done || status=1
done
exit "$status"
HOOK
cat > "$repo_path/hooks/post-receive" <<'HOOK'
#!/bin/sh
set -eu
repo_path=$(git rev-parse --git-dir)
git -C "$repo_path" update-server-info
local_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true)
github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/v0.2-gitops 2>/dev/null || true)
written_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
export local_head github_head written_at
node - <<'NODE' > /cache/HWLAB.last-write.json
const localHead = process.env.local_head || null;
const githubHead = process.env.github_head || null;
console.log(JSON.stringify({
event: "git-mirror-write",
status: "received",
repository: "pikasTech/HWLAB",
writtenAt: process.env.written_at,
pendingFlush: Boolean(localHead && localHead !== githubHead),
refs: {
"refs/heads/v0.2-gitops": localHead,
"refs/mirror-stage/heads/v0.2-gitops": githubHead
}
}));
NODE
HOOK
chmod 0755 "$repo_path/hooks/pre-receive" "$repo_path/hooks/post-receive"
git -C "$repo_path" config http.receivepack true
git -C "$repo_path" config receive.denyDeletes true
git -C "$repo_path" config receive.denyNonFastForwards true
`,
"flush.sh": `#!/bin/sh
set -eu
repo_url="ssh://git@ssh.github.com:443/pikasTech/HWLAB.git"
repo_path="/cache/pikasTech/HWLAB.git"
lock_dir="/cache/.hwlab-flush.lock"
started_ms=$(node -e 'console.log(Date.now())')
now_ms() { node -e 'console.log(Date.now())'; }
emit_timing() {
phase="$1"
status="$2"
started="$3"
finished=$(now_ms)
duration=$((finished - started))
printf '{"event":"git-mirror-flush-timing","phase":"%s","status":"%s","durationMs":%s,"repository":"pikasTech/HWLAB"}\n' "$phase" "$status" "$duration"
}
if ! mkdir "$lock_dir" 2>/dev/null; then
echo "git mirror flush already running"
exit 0
fi
trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT INT TERM
mkdir -p /root/.ssh
cp /git-ssh/ssh-privatekey /root/.ssh/id_rsa
chmod 0400 /root/.ssh/id_rsa
export GIT_SSH_COMMAND="ssh -i /root/.ssh/id_rsa -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.ssh/known_hosts -o ConnectTimeout=10 -o ServerAliveInterval=5 -o ServerAliveCountMax=1"
/script/install-hooks.sh
local_head=$(git -C "$repo_path" rev-parse refs/heads/v0.2-gitops 2>/dev/null || true)
if [ -z "$local_head" ]; then
echo "git mirror flush missing local refs/heads/v0.2-gitops" >&2
exit 51
fi
fetch_started=$(now_ms)
timeout 90 git -C "$repo_path" fetch --quiet "$repo_url" refs/heads/v0.2-gitops:refs/mirror-stage/heads/v0.2-gitops
emit_timing fetch succeeded "$fetch_started"
github_head=$(git -C "$repo_path" rev-parse refs/mirror-stage/heads/v0.2-gitops 2>/dev/null || true)
if [ -n "$github_head" ] && [ "$github_head" != "$local_head" ] && ! git -C "$repo_path" merge-base --is-ancestor "$github_head" "$local_head"; then
echo "git mirror flush rejected divergent v0.2-gitops local=$local_head github=$github_head" >&2
exit 52
fi
push_started=$(now_ms)
timeout 120 git -C "$repo_path" push "$repo_url" "$local_head:refs/heads/v0.2-gitops"
emit_timing push succeeded "$push_started"
git -C "$repo_path" update-ref refs/mirror-stage/heads/v0.2-gitops "$local_head"
git -C "$repo_path" update-server-info
flushed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
export local_head flushed_at
node - <<'NODE' | tee /cache/HWLAB.last-flush.json
console.log(JSON.stringify({
event: "git-mirror-flush",
status: "flushed",
repository: "pikasTech/HWLAB",
flushedAt: process.env.flushed_at,
pendingFlush: false,
refs: { "refs/heads/v0.2-gitops": process.env.local_head || null }
}));
NODE
emit_timing total succeeded "$started_ms"
`,
"http.mjs": `import { createServer } from "node:http";
import { spawn } from "node:child_process";
import { createReadStream, statSync } from "node:fs";
import path from "node:path";
import { createGunzip, createInflate } from "node:zlib";
const cacheRoot = process.env.GIT_MIRROR_CACHE_ROOT || "/cache";
const port = Number(process.env.PORT || 8080);
createServer((req, res) => {
handle(req, res).catch((error) => {
if (!res.headersSent) res.writeHead(500, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: false, error: error.message }));
});
}).listen(port, "0.0.0.0", () => {
console.log(JSON.stringify({ event: "git-mirror-smart-http-listening", port, cacheRoot }));
});
async function handle(req, res) {
const url = new URL(req.url || "/", "http://git-mirror-http");
const writeHost = isWriteHost(req.headers.host || "");
if (req.method === "GET" && (url.pathname === "/" || url.pathname === "/health")) {
res.writeHead(200, { "content-type": "application/json", "cache-control": "no-cache" });
res.end(JSON.stringify({ ok: true, service: "git-mirror-smart-http", writeHost }));
return;
}
if (req.method === "GET" && url.pathname.endsWith("/info/refs") && url.searchParams.get("service") === "git-upload-pack") {
await runUploadPackAdvertisement(repoPathFromGitServicePath(url.pathname, "/info/refs"), res);
return;
}
if (req.method === "POST" && url.pathname.endsWith("/git-upload-pack")) {
await runUploadPackRpc(repoPathFromGitServicePath(url.pathname, "/git-upload-pack"), req, res);
return;
}
if (req.method === "GET" && url.pathname.endsWith("/info/refs") && url.searchParams.get("service") === "git-receive-pack") {
if (!writeHost) return rejectReadOnly(res);
await runReceivePackAdvertisement(repoPathFromGitServicePath(url.pathname, "/info/refs"), res);
return;
}
if (req.method === "POST" && url.pathname.endsWith("/git-receive-pack")) {
if (!writeHost) return rejectReadOnly(res);
await runReceivePackRpc(repoPathFromGitServicePath(url.pathname, "/git-receive-pack"), req, res);
return;
}
if (req.method === "GET" || req.method === "HEAD") {
serveStaticFile(url.pathname, req, res);
return;
}
res.writeHead(405, { "content-type": "text/plain" });
res.end("method not allowed\\n");
}
function isWriteHost(hostHeader) {
const host = String(hostHeader || "").split(":", 1)[0].toLowerCase();
return host === "git-mirror-write" || host.startsWith("git-mirror-write.");
}
function rejectReadOnly(res) {
res.writeHead(403, { "content-type": "application/json", "cache-control": "no-cache" });
res.end(JSON.stringify({ ok: false, error: "git mirror receive-pack is only available through git-mirror-write" }));
}
function repoPathFromGitServicePath(urlPath, suffix) {
const repoUrlPath = urlPath.slice(0, -suffix.length);
if (!repoUrlPath.endsWith(".git")) throw new Error(` + "`" + `unsupported git repo path: \${urlPath}` + "`" + `);
return safePath(repoUrlPath);
}
function safePath(urlPath) {
const decoded = decodeURIComponent(urlPath.replace(/^[/]+/u, ""));
const fullPath = path.resolve(cacheRoot, decoded);
const normalizedRoot = path.resolve(cacheRoot);
if (fullPath !== normalizedRoot && !fullPath.startsWith(` + "`" + `\${normalizedRoot}\${path.sep}` + "`" + `)) throw new Error("path escapes cache root");
return fullPath;
}
function smartHeaders(contentType) {
return {
"content-type": contentType,
"cache-control": "no-cache, max-age=0, must-revalidate",
expires: "Fri, 01 Jan 1980 00:00:00 GMT",
pragma: "no-cache"
};
}
function pktLine(text) {
const length = Buffer.byteLength(text) + 4;
return ` + "`" + `\${length.toString(16).padStart(4, "0")}\${text}` + "`" + `;
}
function requestBodyStream(req) {
const encoding = String(req.headers["content-encoding"] || "identity").toLowerCase();
if (encoding === "identity" || encoding === "") return req;
if (encoding === "gzip" || encoding === "x-gzip") return req.pipe(createGunzip());
if (encoding === "deflate") return req.pipe(createInflate());
throw new Error(` + "`" + `unsupported git upload-pack content-encoding: \${encoding}` + "`" + `);
}
function runUploadPackAdvertisement(repoPath, res) {
return new Promise((resolve, reject) => {
const child = spawn("git-upload-pack", ["--stateless-rpc", "--advertise-refs", repoPath], { stdio: ["ignore", "pipe", "pipe"] });
let stderr = "";
child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); });
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-upload-pack advertise failed: \${stderr.trim()}` + "`" + `));
else resolve();
});
res.writeHead(200, smartHeaders("application/x-git-upload-pack-advertisement"));
res.write(pktLine("# service=git-upload-pack\\n"));
res.write("0000");
child.stdout.pipe(res, { end: false });
child.stdout.on("end", () => res.end());
});
}
function runUploadPackRpc(repoPath, req, res) {
return new Promise((resolve, reject) => {
const child = spawn("git-upload-pack", ["--stateless-rpc", repoPath], { stdio: ["pipe", "pipe", "pipe"] });
let stderr = "";
child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); });
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-upload-pack rpc failed: \${stderr.trim()}` + "`" + `));
else resolve();
});
res.writeHead(200, smartHeaders("application/x-git-upload-pack-result"));
const body = requestBodyStream(req);
body.on("error", (error) => child.stdin.destroy(error));
body.pipe(child.stdin);
child.stdout.pipe(res);
});
}
function runReceivePackAdvertisement(repoPath, res) {
return new Promise((resolve, reject) => {
const child = spawn("git-receive-pack", ["--stateless-rpc", "--advertise-refs", repoPath], { stdio: ["ignore", "pipe", "pipe"] });
let stderr = "";
child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); });
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-receive-pack advertise failed: \${stderr.trim()}` + "`" + `));
else resolve();
});
res.writeHead(200, smartHeaders("application/x-git-receive-pack-advertisement"));
res.write(pktLine("# service=git-receive-pack\\n"));
res.write("0000");
child.stdout.pipe(res, { end: false });
child.stdout.on("end", () => res.end());
});
}
function runReceivePackRpc(repoPath, req, res) {
return new Promise((resolve, reject) => {
const child = spawn("git-receive-pack", ["--stateless-rpc", repoPath], { stdio: ["pipe", "pipe", "pipe"] });
let stderr = "";
child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); });
child.on("error", reject);
child.on("close", (code) => {
if (code !== 0 && !res.headersSent) reject(new Error(` + "`" + `git-receive-pack rpc failed: \${stderr.trim()}` + "`" + `));
else resolve();
});
res.writeHead(200, smartHeaders("application/x-git-receive-pack-result"));
const body = requestBodyStream(req);
body.on("error", (error) => child.stdin.destroy(error));
body.pipe(child.stdin);
child.stdout.pipe(res);
});
}
function serveStaticFile(urlPath, req, res) {
const fullPath = safePath(urlPath);
let stat;
try { stat = statSync(fullPath); } catch {
res.writeHead(404, { "content-type": "text/plain" });
res.end("not found\\n");
return;
}
if (!stat.isFile()) {
res.writeHead(404, { "content-type": "text/plain" });
res.end("not found\\n");
return;
}
res.writeHead(200, { "content-length": stat.size, "content-type": contentType(fullPath), "cache-control": "no-cache" });
if (req.method === "HEAD") {
res.end();
return;
}
createReadStream(fullPath).pipe(res);
}
function contentType(filePath) {
if (filePath.endsWith(".pack")) return "application/x-git-packed-objects";
if (filePath.endsWith(".idx")) return "application/x-git-packed-objects-toc";
return "text/plain";
}
`
}
},
{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: { name: "git-mirror-http", namespace: "devops-infra", labels: { ...labels, "app.kubernetes.io/component": "read-only-http" } },
spec: {
replicas: 1,
selector: { matchLabels: { "app.kubernetes.io/name": "git-mirror", "app.kubernetes.io/component": "read-only-http" } },
template: {
metadata: { labels: { ...labels, "app.kubernetes.io/component": "read-only-http" } },
spec: {
containers: [{
name: "http",
image: ciToolsRunnerImage,
imagePullPolicy: "IfNotPresent",
command: ["node"],
args: ["/script/http.mjs"],
ports: [{ name: "http", containerPort: 8080 }],
readinessProbe: { httpGet: { path: "/pikasTech/HWLAB.git/HEAD", port: "http" }, initialDelaySeconds: 5, periodSeconds: 10 },
livenessProbe: { httpGet: { path: "/", port: "http" }, initialDelaySeconds: 10, periodSeconds: 30 },
volumeMounts: [
{ name: "cache", mountPath: "/cache" },
{ name: "script", mountPath: "/script", readOnly: true }
]
}],
volumes: [
{ name: "cache", persistentVolumeClaim: { claimName: "git-mirror-cache" } },
{ name: "script", configMap: { name: "git-mirror-sync-script", defaultMode: 0o555 } }
]
}
}
}
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name: "git-mirror-http", namespace: "devops-infra", labels: { ...labels, "app.kubernetes.io/component": "read-only-http" } },
spec: { selector: { "app.kubernetes.io/name": "git-mirror", "app.kubernetes.io/component": "read-only-http" }, ports: [{ name: "http", port: 80, targetPort: "http" }] }
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name: "git-mirror-write", namespace: "devops-infra", labels: { ...labels, "app.kubernetes.io/component": "write-relay" } },
spec: { selector: { "app.kubernetes.io/name": "git-mirror", "app.kubernetes.io/component": "read-only-http" }, ports: [{ name: "http", port: 80, targetPort: "http" }] }
}
]
};
}
function argoProject(args = { lane: "g14" }) {
if (args.lane === "v02") {
return {
apiVersion: "argoproj.io/v1alpha1",
kind: "AppProject",
metadata: { name: "hwlab-v02", namespace: "argocd" },
spec: {
description: "HWLAB v0.2 additive GitOps project on G14; DEV/PROD remain outside this lane.",
sourceRepos: [args.gitReadUrl],
destinations: [{ server: "https://kubernetes.default.svc", namespace: "hwlab-v02" }],
clusterResourceWhitelist: [{ group: "", kind: "Namespace" }],
namespaceResourceWhitelist: [{ group: "*", kind: "*" }]
}
};
}
return {
apiVersion: "argoproj.io/v1alpha1",
kind: "AppProject",
metadata: { name: "hwlab-g14", namespace: "argocd" },
spec: {
description: "HWLAB G14 GitOps project; D601 remains outside this project.",
sourceRepos: [defaultSourceRepo],
destinations: [
{ server: "https://kubernetes.default.svc", namespace: "hwlab-dev" },
{ server: "https://kubernetes.default.svc", namespace: "hwlab-prod" }
],
clusterResourceWhitelist: [{ group: "", kind: "Namespace" }],
namespaceResourceWhitelist: [{ group: "*", kind: "*" }]
}
};
}
function argoApplication(args, profile = "dev") {
const namespace = namespaceNameForProfile(profile);
const runtimePath = runtimePathForProfile(profile);
const gitopsTarget = gitopsTargetForProfile(profile);
const project = profile === "v02" ? "hwlab-v02" : "hwlab-g14";
const repoURL = profile === "v02" ? args.gitReadUrl : args.sourceRepo;
return {
apiVersion: "argoproj.io/v1alpha1",
kind: "Application",
metadata: {
name: argoApplicationName(profile),
namespace: "argocd",
labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": gitopsTarget }
},
spec: {
project,
source: { repoURL, targetRevision: args.gitopsBranch, path: `deploy/gitops/g14/${runtimePath}` },
destination: { server: "https://kubernetes.default.svc", namespace },
syncPolicy: { automated: { prune: profile === "v02", selfHeal: true }, syncOptions: ["CreateNamespace=true", "ApplyOutOfSyncOnly=true"] }
}
};
}
function g14FrpcManifest({ profile = "dev", source = null } = {}) {
const namespace = namespaceNameForProfile(profile);
const profileLabel = runtimeLabelForProfile(profile);
const deploymentName = profile === "v02" ? "hwlab-v02-frpc" : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc";
const configName = `${deploymentName}-config`;
const proxyPrefix = profile === "v02" ? "hwlab-v02" : profile === "prod" ? "hwlab-g14-prod" : "hwlab-g14";
const webRemotePort = profile === "v02" ? 19666 : profile === "prod" ? 18666 : 17666;
const edgeRemotePort = profile === "v02" ? 19667 : profile === "prod" ? 18667 : 17667;
const labels = {
"app.kubernetes.io/name": deploymentName,
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/environment": profileLabel,
"hwlab.pikastech.local/gitops-target": gitopsTargetForProfile(profile),
"hwlab.pikastech.local/profile": profileLabel
};
const sourceCommitLabel = source?.full ? { "hwlab.pikastech.local/source-commit": source.full } : {};
const renderedLabels = { ...labels, ...sourceCommitLabel };
return {
apiVersion: "v1",
kind: "List",
items: [
{
apiVersion: "v1",
kind: "ConfigMap",
metadata: {
name: configName,
namespace,
labels: renderedLabels
},
data: {
"frpc.toml": `serverAddr = "74.48.78.17"
serverPort = 7000
loginFailExit = true
[[proxies]]
name = "${proxyPrefix}-cloud-web"
type = "tcp"
localIP = "hwlab-cloud-web.${namespace}.svc.cluster.local"
localPort = 8080
remotePort = ${webRemotePort}
[[proxies]]
name = "${proxyPrefix}-edge-proxy"
type = "tcp"
localIP = "hwlab-edge-proxy.${namespace}.svc.cluster.local"
localPort = 6667
remotePort = ${edgeRemotePort}
`
}
},
{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: deploymentName,
namespace,
labels: renderedLabels
},
spec: {
replicas: 1,
selector: { matchLabels: { "app.kubernetes.io/name": deploymentName } },
template: {
metadata: { labels: renderedLabels, annotations: sourceCommitLabel },
spec: {
containers: [{
name: "frpc",
image: "fatedier/frpc:v0.68.1",
imagePullPolicy: "IfNotPresent",
args: ["-c", "/etc/frp/frpc.toml"],
volumeMounts: [{ name: "config", mountPath: "/etc/frp", readOnly: true }]
}],
volumes: [{ name: "config", configMap: { name: configName } }]
}
}
}
}
]
};
}
function deepSeekProxyManifest({ profile = "dev", source, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false } = {}) {
const namespace = namespaceNameForProfile(profile);
const bridgeServiceId = "hwlab-cloud-api";
const digestPin = profile === "v02";
const bridgeImage = runtimeImageForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin });
const bridgeCommit = runtimeCommitForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin });
const bridgeImageTag = runtimeImageTagForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin });
const labels = {
"app.kubernetes.io/name": "hwlab-deepseek-proxy",
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/environment": runtimeLabelForProfile(profile),
"hwlab.pikastech.local/gitops-target": gitopsTargetForProfile(profile),
"hwlab.pikastech.local/service-id": "hwlab-deepseek-proxy",
"hwlab.pikastech.local/source-commit": source.full
};
const templateAnnotations = {
"hwlab.pikastech.local/bridge-service-id": bridgeServiceId,
"hwlab.pikastech.local/source-commit": source.full,
"hwlab.pikastech.local/bridge-source-commit": bridgeCommit,
"hwlab.pikastech.local/bridge-image-tag": bridgeImageTag,
"hwlab.pikastech.local/bridge-image": bridgeImage
};
return {
apiVersion: "v1",
kind: "List",
items: [
{
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: "hwlab-deepseek-proxy-config", namespace, labels },
data: {
"render-config.sh": moonBridgeConfigRenderScript()
}
},
{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: { name: "hwlab-deepseek-proxy", namespace, labels, annotations: { "hwlab.pikastech.local/moonbridge-source-ref": moonBridgeSourceRef, ...templateAnnotations } },
spec: {
replicas: 1,
selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" } },
template: {
metadata: { labels, annotations: templateAnnotations },
spec: {
initContainers: [{
name: "moonbridge-config",
image: bridgeImage,
imagePullPolicy: "IfNotPresent",
command: ["/bin/sh", "-eu", "/scripts/render-config.sh"],
env: [{ name: "DEEPSEEK_API_KEY", valueFrom: { secretKeyRef: { name: secretNameForProfile("hwlab-code-agent-provider", profile), key: "openai-api-key", optional: true } } }],
volumeMounts: [
{ name: "moonbridge-scripts", mountPath: "/scripts", readOnly: true },
{ name: "moonbridge-config", mountPath: "/config" }
]
}],
containers: [{
name: "responses-bridge",
image: bridgeImage,
imagePullPolicy: "IfNotPresent",
command: ["/usr/local/bin/bun", "run", "/app/cmd/hwlab-deepseek-responses-bridge/main.ts"],
env: [
{ name: "PORT", value: "4000" },
{ name: "HWLAB_COMMIT_ID", value: bridgeCommit },
{ name: "HWLAB_IMAGE", value: bridgeImage },
{ name: "HWLAB_IMAGE_TAG", value: bridgeImageTag },
{ name: "HWLAB_DEEPSEEK_BRIDGE_UPSTREAM", value: "http://127.0.0.1:4001" },
{ name: "HWLAB_DEEPSEEK_BRIDGE_MODEL", value: deepSeekProfileModel }
],
ports: [{ name: "http", containerPort: 4000 }],
readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 },
livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 }
}, {
name: "moonbridge",
image: moonBridgeImage,
imagePullPolicy: "IfNotPresent",
args: ["-config", "/config/config.yml"],
ports: [{ name: "moonbridge-http", containerPort: 4001 }],
readinessProbe: { httpGet: { path: "/v1/models", port: "moonbridge-http" }, initialDelaySeconds: 10, periodSeconds: 10 },
livenessProbe: { httpGet: { path: "/v1/models", port: "moonbridge-http" }, initialDelaySeconds: 20, periodSeconds: 20 },
volumeMounts: [
{ name: "moonbridge-config", mountPath: "/config", readOnly: true },
{ name: "moonbridge-data", mountPath: "/data" }
]
}],
volumes: [
{ name: "moonbridge-scripts", configMap: { name: "hwlab-deepseek-proxy-config" } },
{ name: "moonbridge-config", emptyDir: {} },
{ name: "moonbridge-data", emptyDir: {} }
]
}
}
}
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name: "hwlab-deepseek-proxy", namespace, labels },
spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }] }
}
]
};
}
function moonBridgeConfigRenderScript() {
return `#!/bin/sh
if [ -z "\${DEEPSEEK_API_KEY:-}" ]; then
echo "DEEPSEEK_API_KEY is required" >&2
exit 1
fi
cat > /config/config.yml <<EOF
mode: "Transform"
log:
level: "info"
format: "text"
server:
addr: "0.0.0.0:4001"
persistence:
active_provider: db_sqlite
extensions:
db_sqlite:
enabled: true
config:
path: /data/moonbridge.db
wal: true
busy_timeout_ms: 5000
max_open_conns: 1
metrics:
enabled: true
config:
default_limit: 100
max_limit: 1000
deepseek_v4:
config:
reinforce_instructions: true
reinforce_prompt: "[System Reminder]: Please pay close attention to the system instructions, AGENTS.md files, and any other context provided. Follow them carefully and completely in your response.\\n[User]:"
cache:
mode: "explicit"
ttl: "5m"
prompt_caching: true
automatic_prompt_cache: false
explicit_cache_breakpoints: true
allow_retention_downgrade: false
max_breakpoints: 4
min_cache_tokens: 1024
expected_reuse: 2
minimum_value_score: 2048
min_breakpoint_tokens: 1024
defaults:
model: "${deepSeekProfileModel}"
max_tokens: 8192
trace:
enabled: false
models:
${deepSeekProfileModel}:
context_window: 128000
max_output_tokens: 8192
display_name: "${deepSeekProfileModel}"
description: "DeepSeek via Moon Bridge"
default_reasoning_level: null
supported_reasoning_levels: []
extensions:
deepseek_v4:
enabled: true
providers:
deepseek:
base_url: "https://api.deepseek.com/anthropic"
api_key: "\${DEEPSEEK_API_KEY}"
version: "2023-06-01"
user_agent: "moonbridge/1.0"
offers:
- model: ${deepSeekProfileModel}
routes:
${deepSeekProfileModel}:
model: ${deepSeekProfileModel}
provider: deepseek
EOF
`;
}
function deviceAgent71FreqServerScript() {
return [
"import http from \"node:http\";",
"",
"const deviceId = process.env.DEVICE_ID || \"71-freq\";",
"const port = Number(process.env.PORT || 7601);",
"const cloudApiUrl = (process.env.HWLAB_CLOUD_API_URL || \"http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667\").replace(/\\/+$/u, \"\");",
"const gatewaySessionId = process.env.HWLAB_GATEWAY_SESSION_ID || \"gws_d601_win_71_freq\";",
"const resourceId = process.env.HWLAB_GATEWAY_RESOURCE_ID || \"res_d601_windows_host\";",
"const capabilityId = process.env.HWLAB_GATEWAY_CAPABILITY_ID || \"cap_d601_windows_cmd_exec\";",
"const workspaceRoot = process.env.DEVICE_WORKSPACE_ROOT || \"F:\\\\Work\\\\ConStart\";",
"",
"function sendJson(res, statusCode, payload) {",
" const body = JSON.stringify(payload, null, 2);",
" res.writeHead(statusCode, { \"content-type\": \"application/json; charset=utf-8\" });",
" res.end(body);",
"}",
"",
"function readBody(req) {",
" return new Promise((resolve, reject) => {",
" let body = \"\";",
" req.setEncoding(\"utf8\");",
" req.on(\"data\", (chunk) => {",
" body += chunk;",
" if (body.length > 1024 * 1024) req.destroy(new Error(\"request body too large\"));",
" });",
" req.on(\"end\", () => resolve(body ? JSON.parse(body) : {}));",
" req.on(\"error\", reject);",
" });",
"}",
"",
"function postJson(url, payload, timeoutMs) {",
" return new Promise((resolve, reject) => {",
" const parsed = new URL(url);",
" const body = JSON.stringify(payload);",
" const request = http.request({",
" hostname: parsed.hostname,",
" port: parsed.port || 80,",
" path: parsed.pathname + parsed.search,",
" method: \"POST\",",
" headers: { \"content-type\": \"application/json\", \"content-length\": Buffer.byteLength(body) },",
" timeout: timeoutMs",
" }, (response) => {",
" let responseBody = \"\";",
" response.setEncoding(\"utf8\");",
" response.on(\"data\", (chunk) => { responseBody += chunk; });",
" response.on(\"end\", () => {",
" try {",
" const parsedBody = responseBody ? JSON.parse(responseBody) : null;",
" resolve({ statusCode: response.statusCode, body: parsedBody });",
" } catch (error) {",
" reject(error);",
" }",
" });",
" });",
" request.on(\"timeout\", () => request.destroy(new Error(\"request timeout after \" + timeoutMs + \"ms\")));",
" request.on(\"error\", reject);",
" request.end(body);",
" });",
"}",
"",
"function gatewayPayload(input) {",
" const traceId = input.traceId || \"trc_device_agent_71_freq_\" + Date.now();",
" return {",
" jsonrpc: \"2.0\",",
" id: input.id || \"req_device_agent_71_freq_\" + Date.now(),",
" method: \"hardware.invoke.shell\",",
" meta: { serviceId: \"hwlab-cloud-api\", actorId: \"device-agent-71-freq\", environment: \"dev\", traceId, deviceId, workspaceRoot },",
" params: {",
" gatewaySessionId,",
" resourceId,",
" capabilityId,",
" input: {",
" command: input.command,",
" cwd: input.cwd || workspaceRoot,",
" timeoutMs: input.timeoutMs || 30000",
" }",
" }",
" };",
"}",
"",
"const server = http.createServer(async (req, res) => {",
" try {",
" const url = new URL(req.url || \"/\", \"http://device-agent-71-freq\");",
" if (req.method === \"GET\" && url.pathname === \"/health\") {",
" return sendJson(res, 200, { ok: true, deviceId, workspaceRoot, gatewaySessionId, resourceId, capabilityId, cloudApiUrl });",
" }",
" if (req.method === \"GET\" && url.pathname === \"/skills\") {",
" return sendJson(res, 200, { ok: true, deviceId, skills: [{ name: \"cmd\", description: \"Run a bounded Windows cmd command through hwlab-gateway.\" }] });",
" }",
" if (req.method === \"POST\" && url.pathname === \"/run\") {",
" const input = await readBody(req);",
" if (!input.command || typeof input.command !== \"string\") return sendJson(res, 400, { ok: false, error: \"command is required\" });",
" const timeoutMs = Number(input.timeoutMs || 30000);",
" const upstream = await postJson(cloudApiUrl + \"/json-rpc\", gatewayPayload({ ...input, timeoutMs }), timeoutMs + 5000);",
" return sendJson(res, upstream.statusCode || 502, { ok: upstream.statusCode >= 200 && upstream.statusCode < 300, deviceId, gatewaySessionId, upstream: upstream.body });",
" }",
" return sendJson(res, 404, { ok: false, error: \"not found\" });",
" } catch (error) {",
" return sendJson(res, 500, { ok: false, error: error.message });",
" }",
"});",
"",
"server.listen(port, \"0.0.0.0\", () => {",
" console.log(JSON.stringify({ ok: true, service: \"device-agent-71-freq\", port, deviceId, workspaceRoot, cloudApiUrl }));",
"});"
].join("\n") + "\n";
}
function deviceAgent71FreqManifest({ profile = "dev", source, registryPrefix, catalog = null, useDeployImages = false }) {
assert.equal(profile, "dev", "71-freq device-agent is dev-only");
const namespace = namespaceNameForProfile(profile);
const name = "device-agent-71-freq";
const bridgeServiceId = "hwlab-cloud-api";
const bridgeImage = runtimeImageForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages });
const bridgeCommit = runtimeCommitForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages });
const bridgeImageTag = runtimeImageTagForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages });
const labels = {
"app.kubernetes.io/name": name,
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/component": "device-agent",
"hwlab.pikastech.local/device-id": "71-freq",
"hwlab.pikastech.local/environment": runtimeLabelForProfile(profile),
"hwlab.pikastech.local/gitops-target": "g14",
"hwlab.pikastech.local/profile": runtimeLabelForProfile(profile),
"hwlab.pikastech.local/service-id": name,
"hwlab.pikastech.local/source-commit": source.full
};
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" };
const selector = { "app.kubernetes.io/name": name };
const script = deviceAgent71FreqServerScript();
const scriptSha256 = createHash("sha256").update(script).digest("hex");
const templateLabels = { ...labels, ...selector, "hwlab.pikastech.local/source-commit": bridgeCommit };
const templateAnnotations = {
...annotations,
"hwlab.pikastech.local/script-sha256": scriptSha256,
"hwlab.pikastech.local/bridge-service-id": bridgeServiceId,
"hwlab.pikastech.local/bridge-source-commit": bridgeCommit,
"hwlab.pikastech.local/bridge-image-tag": bridgeImageTag,
"hwlab.pikastech.local/bridge-image": bridgeImage
};
return {
apiVersion: "v1",
kind: "List",
items: [
{
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: `${name}-script`, namespace, labels, annotations: templateAnnotations },
data: { "server.mjs": script }
},
{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: { name, namespace, labels, annotations },
spec: {
replicas: 1,
selector: { matchLabels: selector },
template: {
metadata: { labels: templateLabels, annotations: templateAnnotations },
spec: {
containers: [{
name: "device-agent",
image: bridgeImage,
imagePullPolicy: "IfNotPresent",
command: ["node", "/opt/device-agent/server.mjs"],
env: [
{ name: "PORT", value: "7601" },
{ name: "HWLAB_COMMIT_ID", value: bridgeCommit },
{ name: "HWLAB_IMAGE", value: bridgeImage },
{ name: "HWLAB_IMAGE_TAG", value: bridgeImageTag },
{ name: "DEVICE_ID", value: "71-freq" },
{ name: "DEVICE_WORKSPACE_ROOT", value: "F:\\Work\\ConStart" },
{ name: "DEVICE_POD_PROFILE_DIR", value: "/workspace/hwlab/.device-pod" },
{ name: "HWLAB_CLOUD_API_URL", value: `http://hwlab-cloud-api.${namespace}.svc.cluster.local:6667` },
{ name: "HWLAB_GATEWAY_SESSION_ID", value: "gws_d601_win_71_freq" },
{ name: "HWLAB_GATEWAY_RESOURCE_ID", value: "res_d601_windows_host" },
{ name: "HWLAB_GATEWAY_CAPABILITY_ID", value: "cap_d601_windows_cmd_exec" }
],
ports: [{ name: "http", containerPort: 7601 }],
readinessProbe: { httpGet: { path: "/health", port: "http" }, initialDelaySeconds: 3, periodSeconds: 10 },
livenessProbe: { httpGet: { path: "/health", port: "http" }, initialDelaySeconds: 10, periodSeconds: 20 },
resources: { requests: { cpu: "10m", memory: "64Mi" }, limits: { cpu: "200m", memory: "256Mi" } },
volumeMounts: [
{ name: "script", mountPath: "/opt/device-agent", readOnly: true },
{ name: "hwlab-code-agent-workspace", mountPath: "/workspace" }
]
}],
volumes: [
{ name: "script", configMap: { name: `${name}-script` } },
{ name: "hwlab-code-agent-workspace", persistentVolumeClaim: { claimName: "hwlab-code-agent-workspace" } }
]
}
}
}
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name, namespace, labels, annotations },
spec: { type: "ClusterIP", selector, ports: [{ name: "http", port: 7601, targetPort: "http" }] }
}
]
};
}
function v02PostgresManifest({ migrationSql, source }) {
const namespace = "hwlab-v02";
const name = "hwlab-v02-postgres";
const labels = {
"app.kubernetes.io/name": name,
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/environment": "v02",
"hwlab.pikastech.local/gitops-target": "v02",
"hwlab.pikastech.local/profile": "v02",
"hwlab.pikastech.local/source-commit": source.full
};
const templateAnnotations = { "hwlab.pikastech.local/source-commit": source.full };
return {
apiVersion: "v1",
kind: "List",
items: [
{
apiVersion: "v1",
kind: "ConfigMap",
metadata: { name: `${name}-init`, namespace, labels },
data: { "0001_cloud_core_skeleton.sql": migrationSql }
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name, namespace, labels },
spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": name }, ports: [{ name: "postgres", port: 5432, targetPort: "postgres" }] }
},
{
apiVersion: "apps/v1",
kind: "StatefulSet",
metadata: { name, namespace, labels },
spec: {
serviceName: name,
replicas: 1,
selector: { matchLabels: { "app.kubernetes.io/name": name } },
template: {
metadata: { labels, annotations: templateAnnotations },
spec: {
containers: [{
name: "postgres",
image: "127.0.0.1:5000/hwlab/postgres:16-alpine",
imagePullPolicy: "IfNotPresent",
env: [
{ name: "POSTGRES_DB", value: "hwlab_v02" },
{ name: "POSTGRES_USER", value: "hwlab_v02" },
{ name: "POSTGRES_PASSWORD", valueFrom: { secretKeyRef: { name: "hwlab-v02-postgres", key: "POSTGRES_PASSWORD" } } }
],
ports: [{ name: "postgres", containerPort: 5432 }],
readinessProbe: { tcpSocket: { port: "postgres" }, initialDelaySeconds: 5, periodSeconds: 10 },
livenessProbe: { tcpSocket: { port: "postgres" }, initialDelaySeconds: 30, periodSeconds: 20 },
volumeMounts: [
{ name: "data", mountPath: "/var/lib/postgresql/data" },
{ name: "init", mountPath: "/docker-entrypoint-initdb.d", readOnly: true }
]
}],
volumes: [{ name: "init", configMap: { name: `${name}-init` } }]
}
},
volumeClaimTemplates: [{
metadata: { name: "data" },
spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } }
}]
}
}
]
};
}
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev" } = {}) {
const resources = ["namespace.yaml", "code-agent-codex-config.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"];
if (profile === "v02") resources.push("postgres.yaml");
if (includeDeviceAgent) resources.push("device-agent-71-freq.yaml");
resources.push("g14-frpc.yaml");
return {
apiVersion: "kustomize.config.k8s.io/v1beta1",
kind: "Kustomization",
namespace: namespaceNameForProfile(profile),
resources
};
}
function readme({ source, args }) {
if (args.lane === "v02") {
return `# HWLAB v0.2 GitOps CI/CD
This directory is generated by \`scripts/g14-gitops-render.mjs --lane v02\` from source branch \`${args.sourceBranch}\`.
- Target: additive G14 v0.2 lane only.
- CI: Tekton Pipeline \`hwlab-ci/hwlab-v02-ci-image-publish\`.
- Git mirror/relay: v0.2 CI reads source/catalog from \`${args.gitReadUrl}\` and writes GitOps promotion to \`${args.gitWriteUrl}\`; canonical repo \`${args.sourceRepo}\` remains the source identity and manual flush upstream.
- Artifact contract: runtime images come from \`${args.catalogPath}\`; source branch \`v0.2\` does not track this generated catalog.
- Manual trigger: run \`bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm\` from UniDesk to create commit-pinned PipelineRun \`hwlab-v02-ci-poll-<short12>\` for the current \`origin/v0.2\` commit.
- Branch split: source branch \`${args.sourceBranch}\` remains human-authored source; generated desired state is promoted to local mirror branch \`${args.gitopsBranch}\`.
- CD: Argo CD Application \`argocd/hwlab-g14-v02\` consumes \`${args.gitReadUrl}:${args.gitopsBranch}:deploy/gitops/g14/runtime-v02\` and deploys only to namespace \`hwlab-v02\`.
- Public preview: FRP exposes v0.2 web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-v02/hwlab-v02-frpc\`.
- G14 DEV/PROD boundary: this lane does not modify \`G14\`, \`G14-gitops\`, \`hwlab-dev\`, \`hwlab-prod\`, 17666/17667 or 18666/18667.
- CronJob policy: v0.2 does not generate \`hwlab-v02-branch-poller\` or \`hwlab-v02-control-plane-reconciler\`; obsolete live CronJobs should be deleted by the UniDesk control-plane apply command.
- Gate policy: old DEV/D601/main gates do not enter this lane; new checks stay limited to the fixed branch, namespace, catalog, runtime path and Argo boundaries.
`;
}
return `# HWLAB G14 GitOps CI/CD
This directory is generated from built-in Tekton-native CI primitive declarations plus human-authored \`deploy/deploy.json\`, CI-authored \`deploy/artifact-catalog.dev.json\`, and \`deploy/k8s/*\` by \`scripts/g14-gitops-render.mjs\`.
- Target: G14 k3s only.
- CI: Tekton Pipeline \`hwlab-ci/hwlab-g14-ci-image-publish\`.
- Artifact contract: runtime images come from \`deploy/artifact-catalog.dev.json\`; \`deploy/deploy.json\` only carries human-authored config such as env and topology.
- Branch split: source branch \`${args.sourceBranch}\` remains the watched code branch; generated desired state is promoted to \`${args.gitopsBranch}\`.
- CD: Argo CD Applications \`argocd/hwlab-g14-dev\` and \`argocd/hwlab-g14-prod\` consume \`${args.gitopsBranch}:deploy/gitops/g14/runtime-dev\` and \`runtime-prod\`.
- Public preview: FRP exposes G14 DEV web \`${args.webEndpoint}\` / edge \`${args.runtimeEndpoint}\` through \`hwlab-dev/hwlab-g14-frpc\`, and G14 PROD web \`${args.prodWebEndpoint}\` / edge \`${args.prodRuntimeEndpoint}\` through \`hwlab-prod/hwlab-g14-prod-frpc\`; D601 keeps \`:16666/:16667\`.
- Manual polling: run \`kubectl -n hwlab-ci create job --from=cronjob/hwlab-g14-branch-poller hwlab-g14-branch-poller-manual-$(date -u +%Y%m%d%H%M%S)\`; this reuses the same poller path and creates a commit-pinned Tekton PipelineRun only when needed.
- Control-plane reconcile: CronJob \`hwlab-ci/hwlab-g14-control-plane-reconciler\` polls \`${args.sourceBranch}\`, runs \`scripts/g14-gitops-render.mjs\`, and server-side-applies the generated Tekton RBAC/Pipeline/Poller/Reconciler manifests so CI control-plane changes do not require a manual render/apply step.
- D601 boundary: this path does not target D601, does not use UniDesk Code Queue, and does not change D601 production traffic.
- Concurrency: CI builds are per source commit and do not acquire the legacy DEV CD Lease; Argo CD reconciles the Git desired state.
`;
}
async function plannedFiles(args) {
const [deploy, namespaceTemplate, config, services, health, workloads] = await Promise.all([
readJson("deploy/deploy.json"),
readJson("deploy/k8s/base/namespace.yaml"),
readJson("deploy/k8s/base/code-agent-codex-config.yaml"),
readJson("deploy/k8s/base/services.yaml"),
readJson("deploy/k8s/dev/health-contract.yaml"),
readJson("deploy/k8s/base/workloads.yaml")
]);
const source = await resolveSourceRevision(args.sourceRevision);
source.imageTag = imageTagForSource(args, source);
let artifactCatalog = await readJsonIfPresent(args.catalogPath, null);
if (!artifactCatalog && args.lane === "v02") artifactCatalog = artifactCatalogSkeleton({ args, source });
if (!artifactCatalog) artifactCatalog = await readJson(args.catalogPath);
const settings = ciLaneSettings(args);
const profiles = laneRuntimeProfiles(args);
const migrationSql = args.lane === "v02"
? await readFile(path.join(repoRoot, "internal/db/migrations/0001_cloud_core_skeleton.sql"), "utf8")
: null;
const baseLabels = { "hwlab.pikastech.local/gitops-target": settings.gitopsTarget, "hwlab.pikastech.local/source-commit": source.full };
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" };
const files = new Map();
const putJson = (relative, value) => files.set(path.join(args.outDir, relative), jsonManifest(value));
const putText = (relative, value) => files.set(path.join(args.outDir, relative), textFile(value));
const publicEndpoints = Object.fromEntries(profiles.map((profile) => {
const endpoints = profileEndpoint(args, profile);
return [profile, { web: endpoints.webEndpoint, runtime: endpoints.runtimeEndpoint }];
}));
const sourceDescriptor = {
kind: args.lane === "v02" ? "hwlab-v02-gitops-source" : "hwlab-g14-gitops-source",
sourceCommit: source.full,
sourceShortCommit: source.short,
sourceImageTag: source.imageTag,
sourceBranch: args.sourceBranch,
gitopsBranch: args.gitopsBranch,
lane: settings.lane,
sourceRepo: args.sourceRepo,
gitReadUrl: args.gitReadUrl,
gitWriteUrl: args.gitWriteUrl,
registryPrefix: args.registryPrefix,
runtimePaths: profiles.map((profile) => `deploy/gitops/g14/${runtimePathForProfile(profile)}`),
publicEndpoints,
frpDeployments: Object.fromEntries(profiles.map((profile) => [profile, `${namespaceNameForProfile(profile)}/${profile === "v02" ? "hwlab-v02-frpc" : profile === "prod" ? "hwlab-g14-prod-frpc" : "hwlab-g14-frpc"}`])),
tektonPipeline: `hwlab-ci/${settings.pipelineName}`,
argoApplications: profiles.map((profile) => `argocd/${argoApplicationName(profile)}`)
};
if (args.useDeployImages) {
sourceDescriptor.renderMode = {
imageSource: args.catalogPath,
configSource: "deploy/deploy.json",
mixedDesiredState: true
};
}
putJson("source.json", sourceDescriptor);
putText("README.md", readme({ source, args }));
putJson("registry/registry.yaml", registryManifest());
if (args.lane === "v02") putJson("devops-infra/git-mirror.yaml", devopsInfraGitMirrorManifest());
putJson(`${settings.tektonDir}/rbac.yaml`, tektonRbac(args));
putJson(`${settings.tektonDir}/pipeline.yaml`, tektonPipeline(args));
if (args.lane !== "v02") {
putJson(`${settings.tektonDir}/poller.yaml`, tektonPollerCronJob(args));
putJson(`${settings.tektonDir}/control-plane-reconciler.yaml`, tektonControlPlaneReconcilerCronJob(args));
}
putJson(`${settings.tektonDir}/pipelinerun.sample.yaml`, tektonPipelineRunTemplate({ source, args }));
putJson("argocd/project.yaml", argoProject(args));
for (const profile of profiles) putJson(`argocd/application-${profile}.yaml`, argoApplication(args, profile));
for (const profile of profiles) {
const namespace = namespaceNameForProfile(profile);
const includeDeviceAgent = profile === "dev";
const profileLabels = { ...baseLabels, "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile) };
const runtimeNamespace = cloneJson(namespaceTemplate);
runtimeNamespace.metadata.name = namespace;
label(runtimeNamespace.metadata, profileLabels);
annotate(runtimeNamespace.metadata, annotations);
const runtimePath = runtimePathForProfile(profile);
const endpoints = profileEndpoint(args, profile);
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent }));
putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace);
putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations));
putJson(`${runtimePath}/services.yaml`, transformServices({ services, namespace, labels: profileLabels, annotations, profile }));
putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile));
putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, catalog: artifactCatalog, source, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile, useDeployImages: args.useDeployImages }));
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages }));
if (profile === "v02") putJson(`${runtimePath}/postgres.yaml`, v02PostgresManifest({ migrationSql, source }));
if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix, catalog: artifactCatalog, useDeployImages: args.useDeployImages }));
putJson(`${runtimePath}/g14-frpc.yaml`, g14FrpcManifest({ profile, source }));
}
return { files, source };
}
async function writeFiles(files) {
for (const [filePath, content] of files) {
const absolutePath = generatedPath(filePath);
await mkdir(path.dirname(absolutePath), { recursive: true });
await writeFile(absolutePath, content);
}
}
async function cleanStaleGeneratedOutput(args) {
if (args.lane !== "v02") return;
for (const relativePath of ["runtime-v02", "tekton-v02"]) {
await rm(generatedPath(path.join(args.outDir, relativePath)), { recursive: true, force: true });
}
}
async function checkFiles(files) {
const mismatches = [];
for (const [filePath, expected] of files) {
let actual = null;
try {
actual = await readFile(generatedPath(filePath), "utf8");
} catch (error) {
if (error?.code !== "ENOENT") throw error;
}
if (actual !== expected) mismatches.push(filePath);
}
return mismatches;
}
async function main() {
let args = parseArgs(process.argv.slice(2));
if (args.help) {
console.log(usage());
return;
}
ensureObject(args, "args");
const { files, source } = await plannedFiles(args);
if (args.check) {
const mismatches = await checkFiles(files);
console.log(JSON.stringify({ ok: mismatches.length === 0, sourceCommit: source.full, outDir: args.outDir, checked: files.size, mismatches }, null, 2));
process.exitCode = mismatches.length === 0 ? 0 : 1;
return;
}
if (args.write) {
await cleanStaleGeneratedOutput(args);
await writeFiles(files);
}
console.log(JSON.stringify({ ok: true, sourceCommit: source.full, sourceShort: source.short, outDir: args.outDir, files: [...files.keys()] }, null, 2));
}
main().catch((error) => {
console.error(error.stack || error.message);
process.exitCode = 1;
});