diff --git a/.tekton/hwlab-nc01-production-pac.yaml b/.tekton/hwlab-nc01-production-pac.yaml index 92338e74..c940119c 100644 --- a/.tekton/hwlab-nc01-production-pac.yaml +++ b/.tekton/hwlab-nc01-production-pac.yaml @@ -9,7 +9,7 @@ metadata: pipelinesascode.tekton.dev/on-cel-expression: "event == 'push' && target_branch == 'release' && node == 'NC01'" pipelinesascode.tekton.dev/max-keep-runs: "8" unidesk.ai/owning-config-ref: "config/hwlab-node-lanes.yaml#lanes.production.targets.NC01" - unidesk.ai/effective-config-sha256: sha256:10aa6c7e87349c06df2fefb25a2f5706b17f96ff6f96255f9fd05e512bb4173d + unidesk.ai/effective-config-sha256: sha256:632f16c327dd5db47ea8b313237b24516a2490feb094f08c82d7f719fb4f3a88 unidesk.ai/source-artifact-renderer: hwlab-runtime-lane unidesk.ai/source-artifact-mode: remote-pipeline-annotation pipelinesascode.tekton.dev/pipeline: ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml diff --git a/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml b/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml index a815aac7..17b18da1 100644 --- a/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml +++ b/ci/pipelines/hwlab-nc01-production-ci-image-publish.yaml @@ -13,7 +13,7 @@ metadata: hwlab.pikastech.local/download-profile: jd01-node-default hwlab.pikastech.local/network-profile: jd01-node-ci-egress unidesk.ai/owning-config-ref: "config/hwlab-node-lanes.yaml#lanes.production.targets.NC01" - unidesk.ai/effective-config-sha256: sha256:10aa6c7e87349c06df2fefb25a2f5706b17f96ff6f96255f9fd05e512bb4173d + unidesk.ai/effective-config-sha256: sha256:632f16c327dd5db47ea8b313237b24516a2490feb094f08c82d7f719fb4f3a88 unidesk.ai/source-artifact-renderer: hwlab-runtime-lane unidesk.ai/source-artifact-mode: remote-pipeline-annotation spec: @@ -621,7 +621,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -829,7 +829,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -1037,7 +1037,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -1245,7 +1245,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -1453,7 +1453,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -1661,7 +1661,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -1869,7 +1869,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -2077,7 +2077,7 @@ spec: mountPath: /workspace/buildkit-bin - name: buildkit-run mountPath: /workspace/buildkit-run - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"build-$(params.service-id)\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\nexport HWLAB_TIMING_SERVICE_ID=\"$(params.service-id)\"\nHWLAB_PROXY_PROBE_PHASE='service-image-publish-proxy-preflight' HWLAB_PROXY_PROBE_URL='http://deb.debian.org/debian/dists/bookworm/InRelease' node <<'NODE' || echo '{\"event\":\"dependency-proxy-probe-nonblocking\",\"phase\":\"service-image-publish-proxy-preflight\",\"ok\":false,\"reason\":\"probe-failed-but-continuing\"}'\nconst net = require(\"node:net\");\n\nconst phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\nconst target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\nconst proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n\nfunction redactProxy(value) {\n if (!value) return \"\";\n try {\n const parsed = new URL(value);\n if (parsed.username || parsed.password) {\n parsed.username = \"***\";\n parsed.password = \"\";\n }\n return parsed.toString();\n } catch {\n return \"\";\n }\n}\n\nfunction emit(payload, exitCode = 0) {\n console.log(JSON.stringify({\n event: \"dependency-proxy-probe\",\n phase,\n target: target.href,\n proxy: redactProxy(proxyRaw),\n ...payload\n }));\n if (exitCode) process.exit(exitCode);\n}\n\nif (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n\nlet proxy;\ntry {\n proxy = new URL(proxyRaw);\n} catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n}\n\nif (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n}\n\nconst startedAt = Date.now();\nconst socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\nlet bytes = 0;\nlet firstByteMs = null;\nlet responseHead = \"\";\nlet finished = false;\n\nconst timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n}, 15000);\n\nsocket.on(\"connect\", () => {\n socket.write(\"GET \" + target.href + \" HTTP/1.1\\r\\nHost: \" + target.host + \"\\r\\nUser-Agent: hwlab-node-proxy-probe\\r\\nConnection: close\\r\\n\\r\\n\");\n});\n\nsocket.on(\"data\", (chunk) => {\n if (firstByteMs === null) firstByteMs = Date.now() - startedAt;\n bytes += chunk.length;\n if (responseHead.length < 240) responseHead += chunk.toString(\"utf8\", 0, Math.min(chunk.length, 240 - responseHead.length));\n});\n\nsocket.on(\"end\", () => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n const totalMs = Date.now() - startedAt;\n const statusLine = responseHead.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number(statusLine.split(\" \")[1] || 0);\n const ok = statusLine.startsWith(\"HTTP/\") && statusCode >= 200 && statusCode < 400;\n emit({\n ok,\n reason: ok ? null : \"bad-http-status\",\n statusLine,\n statusCode,\n firstByteMs,\n totalMs,\n bytes,\n speedBytesPerSecond: totalMs > 0 ? Math.round(bytes * 1000 / totalMs) : 0\n }, ok ? 0 : 25);\n});\n\nsocket.on(\"error\", (error) => {\n if (finished) return;\n finished = true;\n clearTimeout(timeout);\n emit({ ok: false, reason: \"proxy-connect-failed\", error: error.message, totalMs: Date.now() - startedAt }, 26);\n});\nNODE\necho '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\nmkdir -p /workspace/service-work/home\nexport HOME=/workspace/service-work/home\ngit config --global --add safe.directory /workspace/source/repo\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nmkdir -p /workspace/source/service-results\nif [ -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.buildRequired ? 0 : 1);' \"$(params.service-id)\"; then\n node - \"$(params.service-id)\" <<'NODE'\nconst fs = require(\"node:fs\");\nconst serviceId = process.argv[2];\nconst catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\nconst plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\nconst service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\nconst planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\nconst envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\nconst image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\nconst digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\nconst imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\nconst repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\nconst revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\nconst componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\nconst environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\nconst codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\nconst values = {\n \"service-id\": serviceId,\n status: /^sha256:[a-f0-9]{64}$/.test(digest) ? \"reused\" : \"blocked_reuse_unavailable\",\n image,\n \"image-tag\": imageTag,\n digest,\n \"repository-digest\": /^sha256:[a-f0-9]{64}$/.test(digest) && repository ? repository + \"@\" + digest : \"\",\n \"source-commit-id\": envReuse ? revision : (service.sourceCommitId || service.commitId || imageTag),\n \"component-input-hash\": componentInputHash,\n \"environment-input-hash\": environmentInputHash,\n \"code-input-hash\": codeInputHash,\n \"runtime-mode\": envReuse ? \"env-reuse-git-mirror-checkout\" : \"service-image\",\n \"boot-repo\": planned.bootRepo || service.bootRepo || \"\",\n \"boot-commit\": envReuse ? revision : (service.bootCommit || \"\"),\n \"boot-sh\": planned.bootSh || service.bootSh || \"\",\n \"build-created-at\": service.buildCreatedAt || \"\",\n \"build-backend\": envReuse ? \"reused-env-catalog\" : \"reused-catalog\",\n \"reused-from\": (envReuse ? service.environmentInputHash : service.componentInputHash) || service.commitId || imageTag || \"catalog\"\n};\nfor (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/tekton/results/\" + name, String(value || \"\"));\nNODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\nfi\nrm -rf /workspace/service-work/repo\nmkdir -p /workspace/service-work/repo\ntar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/repo -xo -f -\ncd /workspace/service-work/repo\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$(params.service-id)\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nexport HWLAB_NODE_CICD_TIMING=1\nexport HWLAB_TEKTON_PIPELINERUN\nexport HWLAB_TEKTON_TASKRUN\nexport HWLAB_TEKTON_TASK\nexport HWLAB_SOURCE_REVISION\nexport PATH=\"/workspace/buildkit-bin:$PATH\"\nexport HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nif [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n 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/\"}'\n registry_started_at=\"$(date +%s)\"\n curl -fsS --max-time 10 http://127.0.0.1:5000/v2/ >/dev/null\n registry_finished_at=\"$(date +%s)\"\n 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))\"'}'\nfi\nbuildkit_ready=0\nfor attempt in $(seq 1 60); do\n if /workspace/buildkit-bin/buildctl --addr \"$HWLAB_BUILDKIT_ADDR\" debug workers >/dev/null 2>&1; then\n buildkit_ready=1\n break\n fi\n sleep 1\ndone\nif [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$(params.service-id)\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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\" --build-cache-mode \"$(params.build-cache-mode)\"\n" imagePullPolicy: Always params: - name: revision @@ -2150,7 +2150,7 @@ spec: value: /tekton/home - name: XDG_CONFIG_HOME value: /tekton/home/.config - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"collect-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-collect\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nconst willRunGitopsPromote = plan.ciCdPlan && plan.ciCdPlan.willRunGitopsPromote === true;\nprocess.exit(!willRunGitopsPromote && buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n rm -f /workspace/source/dev-artifacts.json\n echo '{\"event\":\"collect-artifacts\",\"status\":\"skipped\",\"reason\":\"no-build-no-rollout-plan\"}'\n exit 0\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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 --ci-plan-path /workspace/source/affected-services.json\nnode scripts/refresh-artifact-catalog.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --image-tag-mode \"$(params.image-tag-mode)\" --target-ref HEAD --publish-report /workspace/source/dev-artifacts.json --no-write\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\necho '{\"event\":\"ci-base-image\",\"phase\":\"collect-artifacts\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\nfor 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\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nexport HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-collect\"\nexport HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\nexport HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\nexport HWLAB_DEV_REGISTRY_K8S_NATIVE=1\nif [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\nexport HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\nexport HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\nexport HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\nexport HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nconst willRunGitopsPromote = plan.ciCdPlan && plan.ciCdPlan.willRunGitopsPromote === true;\nprocess.exit(!willRunGitopsPromote && buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n rm -f /workspace/source/dev-artifacts.json\n echo '{\"event\":\"collect-artifacts\",\"status\":\"skipped\",\"reason\":\"no-build-no-rollout-plan\"}'\n exit 0\nfi\nHWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/artifact-publish.mjs --publish --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --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 --ci-plan-path /workspace/source/affected-services.json\nnode scripts/refresh-artifact-catalog.mjs --lane \"$(params.lane)\" --catalog-path \"$(params.catalog-path)\" --deploy-config deploy/deploy.yaml --image-tag-mode \"$(params.image-tag-mode)\" --target-ref HEAD --publish-report /workspace/source/dev-artifacts.json --no-write\n" imagePullPolicy: Always params: - name: revision @@ -2224,7 +2224,7 @@ spec: value: /tekton/home - name: XDG_CONFIG_HOME value: /tekton/home/.config - script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"gitops-promote\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"gitops-promote\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apt\"}'\nfor 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\ngit_ssh_setup() {\n mkdir -p /root/.ssh\n cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa\n chmod 600 /root/.ssh/id_rsa\n timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n write_github_proxy_command\n 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 -o ProxyCommand='node /tmp/hwlab-github-proxy-connect.mjs 127.0.0.1 10808 %h %p'\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"git@github.com:\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"ssh://git@github.com/\"\n}\n\nwrite_github_proxy_command() {\n cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY'\n#!/usr/bin/env node\nimport net from \"node:net\";\n\nconst [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);\nconst proxyPort = Number.parseInt(proxyPortRaw || \"\", 10);\nconst targetPort = Number.parseInt(targetPortRaw || \"\", 10);\nif (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {\n console.error(\"usage: hwlab-github-proxy-connect \");\n process.exit(64);\n}\n\nconst socket = net.createConnection({ host: proxyHost, port: proxyPort });\nlet buffer = Buffer.alloc(0);\n\nsocket.setTimeout(10000, () => {\n console.error(\"proxy connect timeout \" + proxyHost + \":\" + proxyPort + \" -> \" + targetHost + \":\" + targetPort);\n socket.destroy();\n process.exit(65);\n});\n\nsocket.on(\"connect\", () => {\n socket.write(\"CONNECT \" + targetHost + \":\" + targetPort + \" HTTP/1.1\\r\\nHost: \" + targetHost + \":\" + targetPort + \"\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n\");\n});\n\nsocket.on(\"error\", (error) => {\n console.error(\"proxy connect failed: \" + error.message);\n process.exit(66);\n});\n\nfunction onData(chunk) {\n buffer = Buffer.concat([buffer, chunk]);\n const headerEnd = buffer.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1 && buffer.length < 8192) return;\n const head = buffer.slice(0, headerEnd + 4).toString(\"latin1\");\n const statusLine = head.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number.parseInt(statusLine.split(\" \")[1] || \"\", 10);\n if (!statusLine.startsWith(\"HTTP/1.\") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {\n console.error(\"proxy CONNECT rejected: \" + (statusLine || \"missing-status\"));\n socket.destroy();\n process.exit(67);\n }\n socket.off(\"data\", onData);\n socket.setTimeout(0);\n const rest = buffer.slice(headerEnd + 4);\n if (rest.length) process.stdout.write(rest);\n process.stdin.pipe(socket);\n socket.pipe(process.stdout);\n}\n\nsocket.on(\"data\", onData);\nsocket.on(\"close\", () => process.exit(0));\nNODE_PROXY\n chmod 0700 /tmp/hwlab-github-proxy-connect.mjs\n}\n\ngit_now_ms() {\n node -e 'console.log(Date.now())' 2>/dev/null || date +%s000\n}\n\ngit_timed() {\n phase=\"$1\"\n timeout_seconds=\"$2\"\n shift 2\n started_ms=\"$(git_now_ms)\"\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"started\",\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n set +e\n timeout \"$timeout_seconds\" \"$@\"\n status=\"$?\"\n set -e\n finished_ms=\"$(git_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n if [ \"$status\" -eq 0 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"succeeded\",\"durationMs\":'\"$duration_ms\"'}' >&2\n return 0\n fi\n if [ \"$status\" -eq 124 ] || [ \"$status\" -eq 137 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"reason\":\"timeout\",\"durationMs\":'\"$duration_ms\"',\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n else\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"exitCode\":'\"$status\"',\"durationMs\":'\"$duration_ms\"'}' >&2\n fi\n return \"$status\"\n}\n\ngit_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }\nlane=\"$(params.lane)\"\ncase \"$lane\" in\n node) runtime_lane=false ;;\n *) runtime_lane=true ;;\nesac\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\nelse\n printf 'true' > /tekton/results/runtime-ready-required\nfi\nsource_head_url_for_setup=\"$(params.git-url)\"\ngitops_read_url_for_setup=\"$(params.git-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url_for_setup=\"$(params.git-read-url)\"\n gitops_read_url_for_setup=\"$(params.git-read-url)\"\nfi\ngitops_write_url_for_setup=\"$(params.git-write-url)\"\nif 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\n 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\n git_ssh_setup\nelse\n echo '{\"event\":\"git-ssh-setup\",\"phase\":\"gitops-promote\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-url\"}'\nfi\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\n\ncheck_source_head() {\n phase=\"$1\"\n expected=\"${2:-$(params.revision)}\"\n source_head_url=\"$(params.git-url)\"\n if [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url=\"$(params.git-read-url)\"\n fi\n latest_file=\"$(mktemp)\"\n if ! git_timed \"source-head-$phase\" 45 git ls-remote \"$source_head_url\" \"refs/heads/$(params.source-branch)\" > \"$latest_file\"; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-check-failed\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n latest=\"$(cut -f1 \"$latest_file\" | head -n 1)\"\n if [ -z \"$latest\" ]; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-unresolved\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n if [ \"$latest\" != \"$expected\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"status\":\"skipped-stale-source\",\"verdict\":\"superseded\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"expectedRevision\":\"'\"$expected\"'\",\"latestRevision\":\"'\"$latest\"'\"}'\n exit 0\n fi\n}\n\ncheck_source_head before-render\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nprocess.exit(buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"event\":\"gitops-promote\",\"status\":\"continuing\",\"reason\":\"no-build-no-rollout-plan-gitops-verify\"}' >&2\nfi\ngit config --global user.name \"HWLAB node GitOps Bot\"\ngit config --global user.email \"hwlab-node-gitops-bot@users.noreply.github.com\"\ncatalog_path=\"$(params.catalog-path)\"\nruntime_path=\"$(params.runtime-path)\"\nunidesk_legacy_runtime_path='deploy/gitops/node/runtime-production'\ngitops_root_from_runtime_path() {\n path=\"$1\"\n case \"$path\" in\n */*) printf '%s\n' \"${path%/*}\" ;;\n *) printf '.\n' ;;\n esac\n}\ngitops_root=\"$(gitops_root_from_runtime_path \"$runtime_path\")\"\n\nargo_hard_refresh_runtime_lane() {\n if [ \"$runtime_lane\" != \"true\" ]; then return 0; fi\n ARGO_APPLICATION=\"hwlab-node-$lane\" ARGO_FIELD_MANAGER=\"hwlab-$lane-gitops-promote\" node <<'NODE'\nconst fs = require(\"node:fs\");\nconst https = require(\"node:https\");\n\nconst host = process.env.KUBERNETES_SERVICE_HOST;\nconst port = process.env.KUBERNETES_SERVICE_PORT || \"443\";\nconst startedAt = Date.now();\nconst application = process.env.ARGO_APPLICATION || \"hwlab-node-v02\";\nconst fieldManager = process.env.ARGO_FIELD_MANAGER || \"hwlab-v02-gitops-promote\";\nconst pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;\nconst taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;\nconst task = process.env.HWLAB_TEKTON_TASK || null;\nconst revision = process.env.HWLAB_SOURCE_REVISION || null;\n\nfunction emit(payload) {\n console.log(JSON.stringify({\n event: \"node-cicd-timing\",\n schemaVersion: \"v1\",\n stage: \"argo-hard-refresh\",\n pipelineRun,\n taskRun,\n task,\n revision,\n application,\n source: \"scripts/gitops-render.mjs\",\n at: new Date().toISOString(),\n ...payload\n }));\n}\n\nfunction safeJson(text) {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n\nfunction request(method, path, body, contentType = \"application/merge-patch+json\") {\n const token = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/token\", \"utf8\");\n const ca = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\");\n const payload = body ? JSON.stringify(body) : \"\";\n const headers = { Authorization: \"Bearer \" + token };\n if (payload) {\n headers[\"Content-Type\"] = contentType;\n headers[\"Content-Length\"] = Buffer.byteLength(payload);\n }\n return new Promise((resolve, reject) => {\n const req = https.request({ host, port, method, path, ca, headers }, (res) => {\n let data = \"\";\n res.setEncoding(\"utf8\");\n res.on(\"data\", (chunk) => { data += chunk; });\n res.on(\"end\", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) }));\n });\n req.on(\"error\", reject);\n if (payload) req.write(payload);\n req.end();\n });\n}\n\n(async () => {\n if (!host) {\n emit({ status: \"skipped\", reason: \"kubernetes-service-host-missing\", durationMs: Date.now() - startedAt });\n return;\n }\n const response = await request(\n \"PATCH\",\n \"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/\" + encodeURIComponent(application) + \"?fieldManager=\" + encodeURIComponent(fieldManager),\n { metadata: { annotations: { \"argocd.argoproj.io/refresh\": \"hard\" } } }\n );\n if (response.statusCode >= 200 && response.statusCode < 300) {\n emit({ status: \"succeeded\", durationMs: Date.now() - startedAt, statusCode: response.statusCode });\n return;\n }\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-failed\", durationMs: Date.now() - startedAt, statusCode: response.statusCode, body: response.body.slice(0, 1000) });\n})().catch((error) => {\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-error\", durationMs: Date.now() - startedAt, error: error.message });\n});\nNODE\n}\n\nif [ -s /workspace/source/dev-artifacts.json ]; then\n node scripts/refresh-artifact-catalog.mjs --lane \"$lane\" --catalog-path \"$catalog_path\" --deploy-config deploy/deploy.yaml --image-tag-mode \"$(params.image-tag-mode)\" --target-ref \"$(params.revision)\" --publish-report /workspace/source/dev-artifacts.json --write\nfi\ngitops_render_started_ms=\"$(ci_now_ms)\"\nnode - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/run-bun.mjs scripts/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)\" --gitops-root \"deploy/gitops/node/nc01\" --out \"deploy/gitops/node/nc01\" --registry-prefix \"$(params.registry-prefix)\" --use-deploy-images\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-postprocess.mjs\nfeature_config_schema_started_ms=\"$(date +%s%3N 2>/dev/null || date +%s000)\"\nexport UNIDESK_FEATURE_CONFIG_REPO_DIR=\"$PWD\"\nexport UNIDESK_FEATURE_CONFIG_RENDERED_ROOT=\"deploy/gitops/node/nc01/runtime-production\"\nexport UNIDESK_PAC_CONSUMER='hwlab-nc01-production'\nexport OTEL_EXPORTER_OTLP_TRACES_ENDPOINT='http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces'\nexport OTEL_SERVICE_NAME='unidesk-cicd'\nexport OTEL_TRACES_SAMPLER_ARG='1'\nexport OTEL_EXPORTER_TIMEOUT_MS='300'\nfeature_config_validator_base=''\nfeature_config_validator=''\nif feature_config_validator_base=\"$(mktemp \"${TMPDIR:-/tmp}/unidesk-feature-config-validator.XXXXXX\" 2>/dev/null)\"; then\n feature_config_validator=\"${feature_config_validator_base}.mjs\"\n if ! printf '%s' 'H4sIAAAAAAACA80ba3PbyO17fgXDZjpkT6bttNNp5fo0iq3c+c6vkeS0HdeVaWplM6ZIlQ/bGp/+ewHsm6Rk5a4fmpkk2hcWr8ViAfB373erIt+9i9Ndlj45aTZl7+L5IstL59XJw3SazT8tS1Y4K2eWZ3PHxRndKF8uysw90FPZS1yUxWiZRh0nKcqw5D9zFk4/xwnTrWmcY8OGNys0rEVYPphj2DZ3igBKyYbsP1WcMxvMPJtWCTMn4+JxhhhcDU/tyVWewMx3UZYWJWDGwR3a4D0OKJizMgxggX8g5kfP0+GaJbhl8DWLU2+RZxErigAme37HcRdh9Bjes+BrkaWur2D9s392ClDuk+wuTMYPcRFUaTxlxeM/w3ni9HpOtijjLA0TuYO7hAHXXz/UMfBT23zpn54c98cXw8nVef9L/+S0/+l0APu6nwf98dVwMDm6OP988sOkdR5yir0QUzm02qLR0Y+Ds/7ksj/+EUHCnFl8vzsDvlQ52+HNoIge2Dzk5Gt4syqNkAjnKUziKTDyM191RItGtMbjdBYA+3XlO6/vHEfKbZEdxzn0E9tzVmTJE5PTAzkMnLKEAUyREDhSl6h0hxuo0gvCO9ijKtnIXKiFLrbsGIBpt3jmeO/1KfGaYHwfyAHKU/ivqJLSc23+7XCIO/O4KOL0HqTs8h5nBvrtxIWjR/Y7TlolqK+wdcIkmYhImS+JgY7oA+x/Gl2cB4swL5hnHtgWHGHPqpz9xeUcXDlRWEYPjsfyPMt9AXYrIuKUxA2o3go0EAmHkHBmIWAw7TofXgnwGcgNzo3YZXVrkgdIvFOiEVsNcB7qimgfsygJ8xBVgg95fEslF2tdIFALEpbeg3C/d/b8b6WpFeD13s2aEbGVQVQTq2m1SGJgNyu+AbGnMI/Du4TtqNUN5DTcFvwam9bVSp5ZW7G4NPpfnz7ufdwDOYT8l0ekqXP09QmGUvYsJ3qvTpgkfOcuQKsYHKIyj6NStpSFyPJ5WMKkWZiAuqwEXIAYwAXzM1s+Z/kUwD3yX13HfdkRNnVHEOjKAzpeLhhMwI3o5NAefEMFWO7LSQmibL4ABTW1aJuTQF2OQzN6AGQK8A4P19jlntOUJSGR5UBK+AQnBAXrOl3nLW0097WPkhzalz9QuPy3Ploo5ggcgTY5y25gjDS5ug+Mbs7SKcvZ9O9Z/phk4fRIDnqvYkc5Y5hlIGZtt3WvxA0M+ILl5fI8nDMQ/cXdVxaVAYhYHudAzIhZIShb/WorpZmNtojrSxsDa5aIH1yDLSBfHHZ++cV5HxdDFqFiqvEAtqkAWxw26WlMkEce4e1tR0GcLqrSvCskT51nIQxDfEUaLoqHrGxcImpKLyiq+TwE0YNYbdtLN5s8Ik3cfcsoMGmb+3keLoO4oP89uTzg4+DbOLUuUPTrm4PtRFcxJHyOUgcy7rIKKJ+ClSHbwkWH1s4XMlUWFcRg0ixJVsLd6haQdwCdJWevHd6q6f6weVy2uD5fOBtgxlon6ElNgaEtvCiiR/pDRQlakQfPeQzCu/3wSr4At4bxTAkGlvmrf6W3tFSwQQ+Z9IQFeveKqrxKv5Uo414h2KbFsUjdmmMbzIAF8H9pDH4Th1ct1ykc3wU4mKVha7OSJQPZbfi48JILLsaD08ngH5cXw/FgOLkYn15OxsP+0WA0GZwfX16cnI9xheuaV3IZz1lWlWcojUVWxGX8xE7Skt2z3DP3HKt5Gzcdn5wNLq7Gk7ORdfEX4Ry8ivR+iETDTlb7C55ea7ORNb1tQ0HXqH92eQrb9oc/iP3QMkm2tZpQxVvS3b+HeQobIYNhX9HyXMmvydnJaHRy/oM62+R9yIbindh7a/lbe2sVMAzdq6GlHcLtMlyiBe+KvQ10uzVaVhzWSvHDkLG4nL6JGUqmm3hhHIL/RzZwMg0A+qnA7ciFHvLMPSWfCpY/xRFDT6RrHcaRHmjV1NFg+OXkaDA5758N6PhJrzSKI+2pIX7VnOUatuypA706PzkejH6GF+oRvllHV2eDobygJTTreMnOMg8jBo8tlhpel9FZ34iO2GV/OOBmwxyqrXIVGWn2fFZo6NS09j+Z1vY+mSqkF2FqDvO26dWt09bwOYxl7+e6UJU1sPTHOLwdteOMwWVxAqxDoqhhGhVr0/fqIP1WXX9L09/W8fZbbhsrR1Z7AnZuMDzvn04+w0sEQiH1A05gO//j2+HbLsxfzb1t7cSqzT3bykCs89DE8Tjk8Y2xPjFey9kzglPiVBhXvuxB1tJss8eI13r7f/aDMhsR1zz3gb24ZtCLjpMBVnTUYPxlAwg6zgYE3gYAx+jpQssz9yvDvES9uErjl/MwxWtfwP0U34MSebTcd/7g7E/29vbwb2r6mvLFU2RVHrERYAuW5Vo/Inl/F2QflgD3DoJWaHpATH3VVm9OB5773FAHKVhq1zAztv1uN8+O6oeYWaRss9turk1TbKxE1AJpmicUnDLxqPs8a6DwJ9YEXnVhVLKpKwIlYiKo4EoZ1AjexnW+iW5kW0p3mQKMBAe2G4yxEZYXgB/Me9p3NWxurm3AysR3jC7TivM/QRB4Qo+lTsoncw/OkNOlGD6Oj8SNYM9e+SY0QYSJ/MSKPbvm7Mc4BXj7FoJ1PTUH4e4wh7qbZ2+lhyTMGqbP3BSBLLVlkZ2djSvvkix65Eu5td44GwNQ9ibYs3kN5+KEUjLWUh3s3gyArg9oVGlpA6CBI+w3AdjiZU8g+bqaOXR5bykUpSIbnu6Ej2uv2lqYv0Wcv05EzVWzOC/KCTHUXkwDFAOxQdhcXt3UTkRZFXgMcWs4Luax11PlL/7/igICtUjA2y7ZosUdU26YeZ3Cv2WeJQnLZQT5DkAfqV7z6hGw8J3JSuGaeJ7vHH5vgAlCBIBJOvsZV3+Dw0WzgB9M+ZkKPYMMqRGQNHzIMPh8eTEaK4V6gAQLy4mjmCYrQaV3SohBg6jccMHD7cCuXcqSaWbfZdNl16l5QoJhSnxFfA+5wK5JGO+yHWf0YCUlQfaokghodi1PynQQb38cjy8nH17VQq4ZmI2pdW18GG8Mxpr70bgTA9fDNGLZzCHFdX7/e+6C0s1Nl4VLwqdRF8Pmwq+kyLhwatGXHRwrV/ZXgVaub52eWQwMTpSeJCzMpZqJqb5yKuve5NrMFEGjwKqKGssRCFKKeDeqDT0+3IyCx64dZm5GxWvsfnVEjgBMqkwl5hBxd+YV6Podc0IQCUGWqcLnGCIoGqALCSOdIsIArXSg5YERG8B54cFbGbDkaani01LYA3GOz8IFP7szkIfHJ1+bgf+OSgPwWN+NA+IToXM4STkgtYFum6E2JF+HQWRWblEVD96tnAbZSBOTleRJg1k6cIHnME4r1gw+zBTZNhbXLZmqG31qUeJAsFxNsheZK5S96IcnQTz3/DURry2pI7KmpJ/MaSC1DY1KyhSVrks8uGelZyNMFQ0yyK9WcERN5PzaDA2zaMDs6Gm+rZpacVE7wRFtgSdVyr+hHeEGTUq4XrxrA+wN3SUaXZWd3ff5ojkotXct0GosvJXC/PAqfgHvIRvjlBnIICnjBeT2tS5jUlzvRRUHbseFpLj1WpJCNo+nuJV1ZH59bs7KynXszJuz0sZJqKM5u6GT5qClkWSsjIIIcyJZOV4Xo2IrtUuKC/E5hBt0KqzHCKRv4eqbJgdfSvQwPxA5BqyaII8Tevdkp0yONQYom8SmFJ+2Bg1TRXUYwJAEiILnbRWhOKmcorDJk6dRo/DdobPP1dqAN80ieEbCKMCkyMGx6Cg8XGmYLGMRBE3muOCxumN5ykDyJ9BTeBKYsUqFJ7LpaMEioAgXQ6oPGr0Afi4SnvqD9oFaU7OifC1AtS0B/rGZqUmsYXyfZ9UCUb4mjyiMU3CQMGUZpzF5daLnxsTcgqCWIRQ7syjwu6ZNbjCxaPdQXtEGbIHGqrgGULVfDwNbCFT1YEcrTME4rrHBQ1iAy/jUI2eD1F0cJuokbbWOUht7+R+u2dcYYENYNzLKNBQKxzMqOEwz/fr6pmLbgtJmvf5b/lop07+qR2w4ctz9FNnPrg6ji6CNSk3vSIXZAXSVx2yXCIg6L1BMCD/WS+ysIrDaiQMWu4ECqk6e7LB0VXY2eFMvRqDOrm0hZanMO1M+lyDFEmuajJf5Sr+VlFVuNR1kEfXzZx6+0ABI+iNGyg7MEag9Ax8kw2urOT4Ty0xnbAFs4vHga5t9uOuNNH9TAXZp2b7nB7R4ngBhFSaZz6aoymX8U8xcZAtZDVSDrJUPj0tt8Psahb5TPuTZM1l/nty/bZY5KBjA3DnlBiLGpry+zAa3km6Ncf7pMUCXnKpd9QQ9fj1zRvbfLAYUEzsExXSZeEwUOaKKZblJl3OQeBwBkzNazu8yuMRP4/TRa7GzxlRJyxLnKV6j/2QBZ1g1hct2/xV4ve4y7M2TX/DB6X/YjSsw/AUEZBFj04aR7rQAE8VqseX7SBXdSkDEtlbZEIyVdjZbLQ3fuqAHvH2Y2u5M4xyV7KWkrL9R8ohzVJGjrMQDNiF3NHOs25eCBBKZa6OWEuHDCVJvXj0JO800vxxAl4cv7ieJRpwAcT9S3eLoO8oGROl/GoHIlX/aOkvnqdaiZDJvrf9AZItlsrMXYDSVP5pPwYS5+Jq270yFLPoYVOFj9+ClKXtuWuRo32f8LiNULA4a7BfXXZNWGqiT21ol1PYO5wPKH3V1FdU0A+OaZsJXFO9D98B4DPOIg6gZlhc+xRzsMdOH7rXN6IrKDQ5Y1DnWYaruNnByEIP5OuxpXCW8tqUOU3W3wZSDGGClEl7XeJRAIs8iAe/jXXcFB12ggj8FhNVt/a1ChTkU/XSMMKZjhI11nZV5xp9VtlNPxQtKEyoEz2NAilq9SZ1UPYLl5QkkOcA+FN/t3ldgNxzXl6/vAkw286D+6+Of4DrsqoeL5R9RSHv7eLQVN5bhfsun0AFiHZLvbqhr53Pr0XdNY9fiT8cu++TNeVUSjhYe670eDPIZPiDkcQaCCVvkbcxAvJHOWxtzbwTa14TY7bj65rTHW8x8O+/RluzYNpmzWu871j8GSSlghlcsBcrFNy4tRpNP4Vd+i70kza2ZS1XSrSxk8/sVUdetTOXaGQd2PKGtwqX/0xecOfl0dX6Mn62YZxKumreXtDinAq070O+ELL/3NpiW8jxVFSo+wQHW7EzhDtylfwSjAr4L+BGuFQcXLgQ5SarO8EoXeHsu4OVQHb0ahrAc+nYsjZZYsGuWg7eEmg3ytANU/xpFODTfiAcHbRYNt2ZN+GdZGHsRSSA8vCtZk/VZIOq54vMtMKNimtvZ4Jz5Hp/fEfADsailOogiRPasYMpmIdwpGHK0RxrRVgWD3uOSr67p2uJHBNxtcAVPRPlIzB2CkKODzzpIJdjlKhL8+uwIVRxt89WARIkmH7ylWbcbJQq+Tl4tSscTH+E08yU9M12CyRbKk6z82xYlbEVAXPbmdc3ELazfC3ISwuT7cR60c8C4YAUTTCyUD2d4j2K2kDUPvByaORWwLjocQ5UJ0PO+Ub5OkRVzs5YKYXPDN5iqXSlRJ8Mh1PziWhWWQZaqOypI+c+r+Z2aYDCJ9wMdEgRf4ZNN5YvBYDo92ZBujIlFS/3ub0DkMwb9WBseGDPWzb9BnGAzXo1aqwZW3E8/dHb/vbe3413v7fw13JndvP7x48o3mvt/hqZqfVzRM5C9sMgTouFw7QC8KnCkPXrB9f4NRFSz0+yZ5UchvE58Vdajih3l1I/rpjYufLsUQX5NYmhZLTUmPyex8hjQJXMYdTXXPegDQqYTRCLyGeC0W4tFIQR2awew23Ku7rIMcqSpK/wZKPNxsOsLn88nyshi2/qUNMVVjlOP0h2lWG+JxIhVYr6d+wvtE8md8v0mf2U+OgqrAmKA+AB5KMvFSKS6McU7vajKlvSwKQfb2ydHlyrAuPG3vXuziKvdx1eovJM1VBqllgIqY1CWeii0tfvPP4TbztbrcjRdqmG4/UNRj6Yp4YqLF1nti2pC1/pKWzlhYX7/BMeGFxX7wUPOZvanvzq8ewI/X3gyVS2FBzN0Xsw8d2dHBZgxqMnvX16/8ebnKa+18HPLrkDADpoiYnrXwuG6Of07B0haWZ/ggB9WHvFLDVzh1X8BgBIs15M/AAA=' | base64 -d | gzip -d >\"$feature_config_validator\"; then\n rm -f \"$feature_config_validator_base\" \"$feature_config_validator\" || true\n feature_config_validator=''\n fi\n rm -f \"$feature_config_validator_base\" || true\nfi\nexport UNIDESK_AJV2020_BUNDLE='/etc/unidesk-cicd-runtime-gitops/ajv2020.min.js'\nexport UNIDESK_FEATURE_CONFIG_VALIDATOR=\"${feature_config_validator:-${TMPDIR:-/tmp}/unidesk-feature-config-validator-unavailable.mjs}\"\nif ! node --input-type=module <<'NODE_UNIDESK_FEATURE_CONFIG_SCHEMA'\nimport { pathToFileURL } from 'node:url';\nconst validator = await import(pathToFileURL(process.env.UNIDESK_FEATURE_CONFIG_VALIDATOR).href);\nawait validator.runFeatureConfigSchemaValidation({ repoDir: process.env.UNIDESK_FEATURE_CONFIG_REPO_DIR, renderedRoot: process.env.UNIDESK_FEATURE_CONFIG_RENDERED_ROOT, consumer: process.env.UNIDESK_PAC_CONSUMER });\nNODE_UNIDESK_FEATURE_CONFIG_SCHEMA\nthen\n printf '{\"event\":\"feature-config-schema-validation\",\"warning\":true,\"blocking\":false,\"code\":\"feature-config-validator-process-failure\",\"mutation\":false,\"valuesPrinted\":false}\\n' >&2\nfi\nif [ -n \"$feature_config_validator\" ]; then rm -f \"$feature_config_validator\" || true; fi\nif command -v ci_timing_emit >/dev/null 2>&1; then\n ci_timing_emit feature-config-schema-validation succeeded \"$feature_config_schema_started_ms\"\nelse\n printf '{\"event\":\"ci.stage.timing\",\"stage\":\"feature-config-schema-validation\",\"status\":\"succeeded\",\"blocking\":false}\\n' >&2\nfi\n\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-verify.mjs\nci_timing_emit gitops-render succeeded \"$gitops_render_started_ms\"\ncheck_source_head before-push\nworkdir=\"$(mktemp -d)\"\ngitops_read_url=\"$(params.git-url)\"\ngitops_write_url=\"$(params.git-write-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n gitops_read_url=\"$(params.git-read-url)\"\nfi\ngitops_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-clone 180 git clone --no-checkout \"$gitops_read_url\" \"$workdir/gitops\"\ncd \"$workdir/gitops\"\ngit remote set-url origin \"$gitops_write_url\"\nold_runtime_snapshot=\"$workdir/old-runtime-snapshot\"\nif git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin \"$(params.gitops-branch)\" >/dev/null; then\n git_timed gitops-fetch 90 git fetch origin \"$(params.gitops-branch)\"\n git checkout -B \"$(params.gitops-branch)\" \"origin/$(params.gitops-branch)\"\n if [ \"$runtime_lane\" = \"true\" ] && [ -d \"$runtime_path\" ]; then\n mkdir -p \"$old_runtime_snapshot\"\n cp -a \"$runtime_path\"/. \"$old_runtime_snapshot\"/\n fi\n if [ \"$runtime_lane\" = \"true\" ]; then rm -rf \"$runtime_path\" \"$catalog_path\" \"$unidesk_legacy_runtime_path\"; else rm -rf deploy/gitops/node \"$catalog_path\"; fi\nelse\n git checkout --orphan \"$(params.gitops-branch)\"\n git rm -rf . >/dev/null 2>&1 || true\nfi\nci_timing_emit gitops-clone succeeded \"$gitops_clone_started_ms\"\nmkdir -p \"$(dirname \"$runtime_path\")\" \"$(dirname \"$catalog_path\")\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n cp -a \"/workspace/source/repo/$runtime_path\" \"$runtime_path\"\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" \"$runtime_path\"\n if [ -n \"${unidesk_legacy_runtime_path:-}\" ]; then git add -A \"$unidesk_legacy_runtime_path\" || true; fi\nelse\n mkdir -p deploy/gitops\n cp -a /workspace/source/repo/deploy/gitops/node deploy/gitops/node\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" deploy/gitops/node\nfi\nif [ \"$runtime_lane\" = \"true\" ] && [ -s /workspace/source/affected-services.json ]; then\n runtime_noop_decision=\"$(node - \"$runtime_path\" \"$old_runtime_snapshot\" /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2);\n\nfunction readJson(filePath) {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n}\n\nfunction stable(value) {\n if (Array.isArray(value)) return \"[\" + value.map(stable).join(\",\") + \"]\";\n if (value && typeof value === \"object\") {\n return \"{\" + Object.keys(value).sort().map((key) => JSON.stringify(key) + \":\" + stable(value[key])).join(\",\") + \"}\";\n }\n return JSON.stringify(value);\n}\n\nfunction scrub(value) {\n if (Array.isArray(value)) return value.map(scrub);\n if (!value || typeof value !== \"object\") return value;\n const result = {};\n for (const [key, child] of Object.entries(value)) {\n if (key === \"hwlab.pikastech.local/source-commit\" ||\n key === \"hwlab.pikastech.local/artifact-source-commit\" ||\n key === \"hwlab.pikastech.local/boot-commit\" ||\n key === \"sourceCommitId\" ||\n key === \"bootCommit\") {\n result[key] = \"\";\n continue;\n }\n const envName = String(value.name || \"\");\n if (key === \"value\" && /^HWLAB_.*COMMIT/.test(envName)) {\n result[key] = \"\";\n continue;\n }\n result[key] = scrub(child);\n }\n return result;\n}\n\nfunction listFiles(rootDir) {\n if (!rootDir || !fs.existsSync(rootDir)) return null;\n const files = [];\n function walk(current) {\n for (const entry of fs.readdirSync(current, { withFileTypes: true })) {\n const fullPath = path.join(current, entry.name);\n if (entry.isDirectory()) walk(fullPath);\n else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, \"/\"));\n }\n }\n walk(rootDir);\n return files.sort();\n}\n\nfunction normalizedTree(rootDir) {\n const files = listFiles(rootDir);\n if (!files) return null;\n const entries = {};\n for (const relativePath of files) {\n const filePath = path.join(rootDir, relativePath);\n const text = fs.readFileSync(filePath, \"utf8\");\n try {\n entries[relativePath] = stable(scrub(JSON.parse(text)));\n } catch {\n entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, \"\");\n }\n }\n return stable(entries);\n}\n\nconst plan = readJson(planPath);\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : [];\nif (buildServices.length > 0 || rolloutServices.length > 0) {\n process.stdout.write(\"runtime-required\");\n process.exit(0);\n}\n\nconst oldTree = normalizedTree(oldRuntimePath);\nconst newTree = normalizedTree(runtimePath);\nprocess.stdout.write(oldTree && newTree && oldTree === newTree ? \"runtime-identity-only\" : \"runtime-required\");\nNODE\n)\"\n if [ \"$runtime_noop_decision\" = \"runtime-identity-only\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"skipped-runtime-unchanged\",\"reason\":\"runtime-identity-only\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit skipped \"$(ci_now_ms)\"\n ci_timing_emit gitops-push skipped \"$(ci_now_ms)\"\n exit 0\n fi\nfi\nif git diff --cached --quiet; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"unchanged\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit unchanged \"$(ci_now_ms)\"\n ci_timing_emit gitops-push unchanged \"$(ci_now_ms)\"\n exit 0\nfi\nshort=\"$(printf '%.7s' \"$(params.revision)\")\"\ngitops_commit_started_ms=\"$(ci_now_ms)\"\ngit commit -m \"chore: promote node GitOps source $short\"\nci_timing_emit gitops-commit succeeded \"$gitops_commit_started_ms\"\ngitops_push_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-push 120 git push origin \"HEAD:$(params.gitops-branch)\"\nci_timing_emit gitops-push succeeded \"$gitops_push_started_ms\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"event\":\"runtime-ready\",\"phase\":\"gitops-promote\",\"status\":\"delegated-post-flush-closeout\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\nfi\nargo_hard_refresh_runtime_lane\necho '{\"status\":\"pushed\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"gitopsWriteUrl\":\"'\"$gitops_write_url\"'\"}'\n" + script: "# unidesk-step-env-bootstrap\nexport HOME='/tekton/home'\nexport XDG_CONFIG_HOME='/tekton/home/.config'\nmkdir -p \"$HOME\"\nmkdir -p \"$XDG_CONFIG_HOME\"\nci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\nif [ -d \"$ci_node_deps\" ]; then\n if [ -d /workspace/source/repo ]; then ci_node_deps_target=/workspace/source/repo/node_modules; else ci_node_deps_target=./node_modules; fi\n mkdir -p \"$ci_node_deps_target\"\n for ci_node_dep in yaml; do if [ -d \"$ci_node_deps/$ci_node_dep\" ] && [ ! -e \"$ci_node_deps_target/$ci_node_dep\" ]; then ln -s \"$ci_node_deps/$ci_node_dep\" \"$ci_node_deps_target/$ci_node_dep\"; fi; done\nfi\n#!/bin/sh\nset -eu\nci_now_ms() {\n node -e 'console.log(Date.now())'\n}\n\nci_timing_emit() {\n stage=\"$1\"\n status=\"$2\"\n started_ms=\"$3\"\n finished_ms=\"$(ci_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n service_id=\"${HWLAB_TIMING_SERVICE_ID:-}\"\n node -e 'const [stage,status,durationMs,serviceId]=process.argv.slice(1); const payload={event:\"node-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/gitops-render.mjs\",at:new Date().toISOString()}; console.log(JSON.stringify(payload));' \"$stage\" \"$status\" \"$duration_ms\" \"$service_id\"\n}\n\nexport HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\nexport HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\nexport HWLAB_TEKTON_TASK=\"gitops-promote\"\nexport HWLAB_SOURCE_REVISION=\"$(params.revision)\"\necho '{\"event\":\"ci-base-image\",\"phase\":\"gitops-promote\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apt\"}'\nfor 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\ngit_ssh_setup() {\n mkdir -p /root/.ssh\n cp /workspace/git-ssh/ssh-privatekey /root/.ssh/id_rsa\n chmod 600 /root/.ssh/id_rsa\n timeout 10 ssh-keyscan github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n timeout 10 ssh-keyscan -p 443 ssh.github.com >> /root/.ssh/known_hosts 2>/dev/null || true\n write_github_proxy_command\n 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 -o ProxyCommand='node /tmp/hwlab-github-proxy-connect.mjs 127.0.0.1 10808 %h %p'\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"git@github.com:\"\n git config --global url.\"ssh://git@ssh.github.com:443/\".insteadOf \"ssh://git@github.com/\"\n}\n\nwrite_github_proxy_command() {\n cat > /tmp/hwlab-github-proxy-connect.mjs <<'NODE_PROXY'\n#!/usr/bin/env node\nimport net from \"node:net\";\n\nconst [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);\nconst proxyPort = Number.parseInt(proxyPortRaw || \"\", 10);\nconst targetPort = Number.parseInt(targetPortRaw || \"\", 10);\nif (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {\n console.error(\"usage: hwlab-github-proxy-connect \");\n process.exit(64);\n}\n\nconst socket = net.createConnection({ host: proxyHost, port: proxyPort });\nlet buffer = Buffer.alloc(0);\n\nsocket.setTimeout(10000, () => {\n console.error(\"proxy connect timeout \" + proxyHost + \":\" + proxyPort + \" -> \" + targetHost + \":\" + targetPort);\n socket.destroy();\n process.exit(65);\n});\n\nsocket.on(\"connect\", () => {\n socket.write(\"CONNECT \" + targetHost + \":\" + targetPort + \" HTTP/1.1\\r\\nHost: \" + targetHost + \":\" + targetPort + \"\\r\\nProxy-Connection: Keep-Alive\\r\\n\\r\\n\");\n});\n\nsocket.on(\"error\", (error) => {\n console.error(\"proxy connect failed: \" + error.message);\n process.exit(66);\n});\n\nfunction onData(chunk) {\n buffer = Buffer.concat([buffer, chunk]);\n const headerEnd = buffer.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd === -1 && buffer.length < 8192) return;\n const head = buffer.slice(0, headerEnd + 4).toString(\"latin1\");\n const statusLine = head.split(\"\\r\\n\", 1)[0] || \"\";\n const statusCode = Number.parseInt(statusLine.split(\" \")[1] || \"\", 10);\n if (!statusLine.startsWith(\"HTTP/1.\") || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {\n console.error(\"proxy CONNECT rejected: \" + (statusLine || \"missing-status\"));\n socket.destroy();\n process.exit(67);\n }\n socket.off(\"data\", onData);\n socket.setTimeout(0);\n const rest = buffer.slice(headerEnd + 4);\n if (rest.length) process.stdout.write(rest);\n process.stdin.pipe(socket);\n socket.pipe(process.stdout);\n}\n\nsocket.on(\"data\", onData);\nsocket.on(\"close\", () => process.exit(0));\nNODE_PROXY\n chmod 0700 /tmp/hwlab-github-proxy-connect.mjs\n}\n\ngit_now_ms() {\n node -e 'console.log(Date.now())' 2>/dev/null || date +%s000\n}\n\ngit_timed() {\n phase=\"$1\"\n timeout_seconds=\"$2\"\n shift 2\n started_ms=\"$(git_now_ms)\"\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"started\",\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n set +e\n timeout \"$timeout_seconds\" \"$@\"\n status=\"$?\"\n set -e\n finished_ms=\"$(git_now_ms)\"\n duration_ms=\"$((finished_ms - started_ms))\"\n if [ \"$status\" -eq 0 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"succeeded\",\"durationMs\":'\"$duration_ms\"'}' >&2\n return 0\n fi\n if [ \"$status\" -eq 124 ] || [ \"$status\" -eq 137 ]; then\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"reason\":\"timeout\",\"durationMs\":'\"$duration_ms\"',\"timeoutSeconds\":'\"$timeout_seconds\"'}' >&2\n else\n echo '{\"event\":\"git-operation\",\"phase\":\"'\"$phase\"'\",\"status\":\"failed\",\"exitCode\":'\"$status\"',\"durationMs\":'\"$duration_ms\"'}' >&2\n fi\n return \"$status\"\n}\n\ngit_url_requires_ssh() { case \"$1\" in git@*|ssh://*) return 0 ;; *) return 1 ;; esac; }\nlane=\"$(params.lane)\"\ncase \"$lane\" in\n node) runtime_lane=false ;;\n *) runtime_lane=true ;;\nesac\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\nelse\n printf 'true' > /tekton/results/runtime-ready-required\nfi\nsource_head_url_for_setup=\"$(params.git-url)\"\ngitops_read_url_for_setup=\"$(params.git-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url_for_setup=\"$(params.git-read-url)\"\n gitops_read_url_for_setup=\"$(params.git-read-url)\"\nfi\ngitops_write_url_for_setup=\"$(params.git-write-url)\"\nif 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\n 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\n git_ssh_setup\nelse\n echo '{\"event\":\"git-ssh-setup\",\"phase\":\"gitops-promote\",\"status\":\"skipped\",\"reason\":\"non-ssh-git-url\"}'\nfi\ncd /workspace/source/repo\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\n\ncheck_source_head() {\n phase=\"$1\"\n expected=\"${2:-$(params.revision)}\"\n source_head_url=\"$(params.git-url)\"\n if [ \"$runtime_lane\" = \"true\" ]; then\n source_head_url=\"$(params.git-read-url)\"\n fi\n latest_file=\"$(mktemp)\"\n if ! git_timed \"source-head-$phase\" 45 git ls-remote \"$source_head_url\" \"refs/heads/$(params.source-branch)\" > \"$latest_file\"; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-check-failed\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n latest=\"$(cut -f1 \"$latest_file\" | head -n 1)\"\n if [ -z \"$latest\" ]; then\n echo '{\"status\":\"failed\",\"reason\":\"source-head-unresolved\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n exit 1\n fi\n if [ \"$latest\" != \"$expected\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"status\":\"skipped-stale-source\",\"verdict\":\"superseded\",\"phase\":\"'\"$phase\"'\",\"sourceBranch\":\"'\"$(params.source-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"expectedRevision\":\"'\"$expected\"'\",\"latestRevision\":\"'\"$latest\"'\"}'\n exit 0\n fi\n}\n\ncheck_source_head before-render\nif [ -s /workspace/source/affected-services.json ] && node - /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst plan = JSON.parse(fs.readFileSync(process.argv[2], \"utf8\"));\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst affectedServices = Array.isArray(plan.affectedServices) ? plan.affectedServices : [];\nprocess.exit(buildServices.length === 0 && affectedServices.length === 0 ? 0 : 1);\nNODE\nthen\n printf 'false' > /tekton/results/runtime-ready-required || true\n echo '{\"event\":\"gitops-promote\",\"status\":\"continuing\",\"reason\":\"no-build-no-rollout-plan-gitops-verify\"}' >&2\nfi\ngit config --global user.name \"HWLAB node GitOps Bot\"\ngit config --global user.email \"hwlab-node-gitops-bot@users.noreply.github.com\"\ncatalog_path=\"$(params.catalog-path)\"\nruntime_path=\"$(params.runtime-path)\"\nunidesk_legacy_runtime_path='deploy/gitops/node/runtime-production'\ngitops_root_from_runtime_path() {\n path=\"$1\"\n case \"$path\" in\n */*) printf '%s\n' \"${path%/*}\" ;;\n *) printf '.\n' ;;\n esac\n}\ngitops_root=\"$(gitops_root_from_runtime_path \"$runtime_path\")\"\n\nargo_hard_refresh_runtime_lane() {\n if [ \"$runtime_lane\" != \"true\" ]; then return 0; fi\n ARGO_APPLICATION=\"hwlab-node-$lane\" ARGO_FIELD_MANAGER=\"hwlab-$lane-gitops-promote\" node <<'NODE'\nconst fs = require(\"node:fs\");\nconst https = require(\"node:https\");\n\nconst host = process.env.KUBERNETES_SERVICE_HOST;\nconst port = process.env.KUBERNETES_SERVICE_PORT || \"443\";\nconst startedAt = Date.now();\nconst application = process.env.ARGO_APPLICATION || \"hwlab-node-v02\";\nconst fieldManager = process.env.ARGO_FIELD_MANAGER || \"hwlab-v02-gitops-promote\";\nconst pipelineRun = process.env.HWLAB_TEKTON_PIPELINERUN || null;\nconst taskRun = process.env.HWLAB_TEKTON_TASKRUN || null;\nconst task = process.env.HWLAB_TEKTON_TASK || null;\nconst revision = process.env.HWLAB_SOURCE_REVISION || null;\n\nfunction emit(payload) {\n console.log(JSON.stringify({\n event: \"node-cicd-timing\",\n schemaVersion: \"v1\",\n stage: \"argo-hard-refresh\",\n pipelineRun,\n taskRun,\n task,\n revision,\n application,\n source: \"scripts/gitops-render.mjs\",\n at: new Date().toISOString(),\n ...payload\n }));\n}\n\nfunction safeJson(text) {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\n}\n\nfunction request(method, path, body, contentType = \"application/merge-patch+json\") {\n const token = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/token\", \"utf8\");\n const ca = fs.readFileSync(\"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\");\n const payload = body ? JSON.stringify(body) : \"\";\n const headers = { Authorization: \"Bearer \" + token };\n if (payload) {\n headers[\"Content-Type\"] = contentType;\n headers[\"Content-Length\"] = Buffer.byteLength(payload);\n }\n return new Promise((resolve, reject) => {\n const req = https.request({ host, port, method, path, ca, headers }, (res) => {\n let data = \"\";\n res.setEncoding(\"utf8\");\n res.on(\"data\", (chunk) => { data += chunk; });\n res.on(\"end\", () => resolve({ statusCode: res.statusCode || 0, body: data, json: safeJson(data) }));\n });\n req.on(\"error\", reject);\n if (payload) req.write(payload);\n req.end();\n });\n}\n\n(async () => {\n if (!host) {\n emit({ status: \"skipped\", reason: \"kubernetes-service-host-missing\", durationMs: Date.now() - startedAt });\n return;\n }\n const response = await request(\n \"PATCH\",\n \"/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/\" + encodeURIComponent(application) + \"?fieldManager=\" + encodeURIComponent(fieldManager),\n { metadata: { annotations: { \"argocd.argoproj.io/refresh\": \"hard\" } } }\n );\n if (response.statusCode >= 200 && response.statusCode < 300) {\n emit({ status: \"succeeded\", durationMs: Date.now() - startedAt, statusCode: response.statusCode });\n return;\n }\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-failed\", durationMs: Date.now() - startedAt, statusCode: response.statusCode, body: response.body.slice(0, 1000) });\n})().catch((error) => {\n emit({ status: \"degraded\", reason: \"argo-refresh-patch-error\", durationMs: Date.now() - startedAt, error: error.message });\n});\nNODE\n}\n\nif [ -s /workspace/source/dev-artifacts.json ]; then\n node scripts/refresh-artifact-catalog.mjs --lane \"$lane\" --catalog-path \"$catalog_path\" --deploy-config deploy/deploy.yaml --image-tag-mode \"$(params.image-tag-mode)\" --target-ref \"$(params.revision)\" --publish-report /workspace/source/dev-artifacts.json --write\nfi\ngitops_render_started_ms=\"$(ci_now_ms)\"\nnode - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\nconst fs = require('fs');\nconst YAML = require('yaml');\nconst overlay = {\"nodeId\":\"NC01\",\"lane\":\"production\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-production\",\"runtimeRenderDir\":\"runtime-production\",\"runtimeNamespace\":\"hwlab-production\",\"environment\":\"production\",\"catalogPath\":\"deploy/artifact-catalog.nc01-production.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab.hwpod.com\",\"publicApiUrl\":\"https://lab.hwpod.com\",\"externalPostgres\":{\"enabled\":true,\"serviceName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432,\"runtimeAccess\":{\"routeName\":\"nc01-host-postgres\",\"endpointAddress\":\"10.42.0.1\",\"port\":5432},\"sslmode\":\"require\",\"migrationSecretName\":\"hwlab-cloud-api-production-db\",\"migrationSecretKey\":\"migration-database-url\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-release.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-production-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-release\",\"secretNamespace\":\"agentrun-release\",\"providerSecretNames\":{\"codex\":\"agentrun-release-provider-codex\",\"gpt-pika\":\"agentrun-release-provider-gpt-pika\",\"grok\":\"agentrun-release-provider-grok\",\"dsflash-go\":\"agentrun-release-provider-dsflash-go\"},\"repoUrlFrom\":\"runtimeGitReadUrl\",\"repoUrl\":\"http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git\",\"providerIdFrom\":\"runtimeNodeId\",\"providerId\":\"NC01\",\"defaultProviderProfile\":\"gpt.pika\",\"codexStdioSupervisor\":\"repo-owned\",\"kafkaShadowProducer\":null,\"kafkaEventBridge\":{\"enabled\":true,\"features\":{\"directPublish\":true,\"liveKafkaSse\":true,\"kafkaRefreshReplay\":true,\"transactionalProjector\":false,\"projectionOutboxRelay\":false,\"projectionRealtime\":false},\"refreshReplay\":{\"groupIdPrefix\":\"hwlab-production-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-production-cloud-api\",\"bootstrapServers\":\"platform-infra-kafka-kafka-bootstrap.platform-infra.svc.cluster.local:9092\",\"stdioTopic\":\"codex-stdio.raw.v1\",\"agentRunEventTopic\":\"agentrun.event.v1\",\"hwlabEventTopic\":\"hwlab.event.v1\",\"clientId\":\"hwlab-production-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-production-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-production-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-production-workbench-live-sse\"},\"valuesPrinted\":false},\"observability\":{\"prometheusOperator\":false,\"webProbe\":{\"sentinels\":[{\"id\":\"nc01-web-probe-sentinel\",\"enabled\":true,\"configRef\":\"config/hwlab-web-probe-sentinel/profiles.yaml#nodes.NC01.sentinels.nc01-web-probe-sentinel.sentinel\"}],\"monitor\":{\"configRef\":\"config/hwlab-web-probe-monitor/runtime.yaml#monitor\"},\"monitorRoot\":{\"enabled\":true,\"sentinelId\":\"nc01-web-probe-sentinel\",\"publicBaseUrl\":\"https://monitor.pikapython.com\",\"routePrefix\":\"/\",\"caddyManagedBlockOwner\":\"hwlab-web-probe-sentinel-active-root\"}},\"recordingRules\":[],\"warningAlerts\":[]},\"deployYamlGitMirror\":{\"id\":\"nc01-production\",\"node\":\"NC01\",\"lane\":\"production\",\"namespace\":\"devops-infra\",\"serviceReadName\":\"git-mirror-http\",\"serviceWriteName\":\"git-mirror-write\",\"cachePvcName\":\"hwlab-git-mirror-cache\",\"cachePvcStorage\":\"20Gi\",\"cacheHostPath\":null,\"servicePort\":8080,\"secretName\":\"git-mirror-github-ssh\",\"syncConfigMapName\":\"git-mirror-sync-script\",\"syncJobPrefix\":\"git-mirror-hwlab-nc01-v03-sync-manual\",\"flushJobPrefix\":\"git-mirror-hwlab-nc01-v03-flush-manual\",\"toolsImage\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"toolsImagePullPolicy\":\"Always\",\"sourceRepository\":\"pikasTech/HWLAB\",\"sourceBranch\":\"release\",\"gitopsBranch\":\"release-gitops\",\"egressProxy\":{\"mode\":\"node-global\",\"required\":true,\"clientName\":\"nc01-host-proxy\",\"namespace\":\"platform-infra\",\"serviceName\":\"nc01-host-proxy\",\"port\":10808,\"proxyUrl\":\"http://10.42.0.1:10808\",\"noProxy\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"kubernetes\",\"kubernetes.default\",\"kubernetes.default.svc\",\"argocd-repo-server\",\"argocd-repo-server.argocd\",\"argocd-redis\",\"argocd-redis.argocd\",\"git-mirror-http\",\"git-mirror-http.devops-infra\",\"git-mirror-write\",\"git-mirror-write.devops-infra\",\"10.0.0.0/8\",\"10.42.0.0/16\",\"10.43.0.0/16\",\"172.16.0.0/12\",\"192.168.0.0/16\",\"152.53.229.148\",\"152.53.229.148\",\"hyueapi.com\",\".hyueapi.com\"]},\"githubTransport\":{\"mode\":\"https\",\"username\":\"x-access-token\",\"tokenSecretName\":\"git-mirror-github-token\",\"tokenSecretKey\":\"GITHUB_TOKEN\",\"tokenSourceRef\":\"/root/.unidesk/.env/gh_token.txt\",\"tokenSourceKey\":\"GH_TOKEN\"}},\"runtimeImageRewrites\":[{\"source\":\"fatedier/frpc:v0.68.1\",\"target\":\"127.0.0.1:5000/hwlab/frpc:v0.68.1\"},{\"source\":\"openfga/openfga:v1.17.0\",\"target\":\"127.0.0.1:5000/hwlab/openfga:v1.17.0\"},{\"source\":\"ghcr.io/anomalyco/opencode:1.17.7\",\"target\":\"127.0.0.1:5000/hwlab/opencode:1.17.7\"}],\"dockerProxyHttp\":\"http://127.0.0.1:10808\",\"dockerProxyHttps\":\"http://127.0.0.1:10808\",\"dockerNoProxyList\":[\"localhost\",\"127.0.0.1\",\"::1\",\"127.0.0.1:5000\",\"localhost:5000\",\".svc\",\".svc.cluster.local\",\".cluster.local\",\"hyueapi.com\",\".hyueapi.com\"],\"npmRegistry\":\"https://registry.npmmirror.com/\",\"npmFetchTimeoutMs\":120000,\"npmRetries\":3};\nconst file = 'deploy/deploy.yaml';\nif (!fs.existsSync(file)) {\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: false, reason: 'deploy-yaml-missing', file }));\n process.exit(45);\n}\nconst doc = YAML.parse(fs.readFileSync(file, 'utf8'));\ndoc.nodes = doc.nodes || {};\ndoc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\ndoc.lanes = doc.lanes || {};\nconst lane = doc.lanes[overlay.lane] || {};\nconst envRecipe = lane.envRecipe || {};\nconst downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n};\nif (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\nif (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\ndoc.lanes[overlay.lane] = {\n ...lane,\n node: overlay.nodeId,\n sourceBranch: overlay.sourceBranch,\n gitopsBranch: overlay.gitopsBranch,\n namespace: overlay.runtimeNamespace,\n endpoint: overlay.publicApiUrl,\n publicEndpoints: { frontend: overlay.publicWebUrl, api: overlay.publicApiUrl },\n artifactCatalog: overlay.catalogPath,\n runtimePath: overlay.runtimeRenderDir,\n imageTagMode: 'full',\n sourceRepo: overlay.gitUrl,\n observability: overlay.observability,\n envRecipe: { ...envRecipe, downloadStack },\n};\nif (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\nelse doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\nif (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\nif (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\nif (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\nfs.writeFileSync(file, YAML.stringify(doc));\nconsole.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\nNODE_UNIDESK_DEPLOY_YAML_OVERLAY\nnode scripts/run-bun.mjs scripts/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)\" --gitops-root \"deploy/gitops/node/nc01\" --out \"deploy/gitops/node/nc01\" --registry-prefix \"$(params.registry-prefix)\" --use-deploy-images\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-postprocess.mjs\nfeature_config_schema_started_ms=\"$(date +%s%3N 2>/dev/null || date +%s000)\"\nexport UNIDESK_FEATURE_CONFIG_REPO_DIR=\"$PWD\"\nexport UNIDESK_FEATURE_CONFIG_RENDERED_ROOT=\"deploy/gitops/node/nc01/runtime-production\"\nexport UNIDESK_PAC_CONSUMER='hwlab-nc01-production'\nexport OTEL_EXPORTER_OTLP_TRACES_ENDPOINT='http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces'\nexport OTEL_SERVICE_NAME='unidesk-cicd'\nexport OTEL_TRACES_SAMPLER_ARG='1'\nexport OTEL_EXPORTER_TIMEOUT_MS='300'\nfeature_config_validator_base=''\nfeature_config_validator=''\nif feature_config_validator_base=\"$(mktemp \"${TMPDIR:-/tmp}/unidesk-feature-config-validator.XXXXXX\" 2>/dev/null)\"; then\n feature_config_validator=\"${feature_config_validator_base}.mjs\"\n if ! printf '%s' 'H4sIAAAAAAACA80ba3PbyO17fgXDZjpkT6bttNNp5fo0iq3c+c6vkeS0HdeVaWplM6ZIlQ/bGp/+ewHsm6Rk5a4fmpkk2hcWr8ViAfB373erIt+9i9Ndlj45aTZl7+L5IstL59XJw3SazT8tS1Y4K2eWZ3PHxRndKF8uysw90FPZS1yUxWiZRh0nKcqw5D9zFk4/xwnTrWmcY8OGNys0rEVYPphj2DZ3igBKyYbsP1WcMxvMPJtWCTMn4+JxhhhcDU/tyVWewMx3UZYWJWDGwR3a4D0OKJizMgxggX8g5kfP0+GaJbhl8DWLU2+RZxErigAme37HcRdh9Bjes+BrkaWur2D9s392ClDuk+wuTMYPcRFUaTxlxeM/w3ni9HpOtijjLA0TuYO7hAHXXz/UMfBT23zpn54c98cXw8nVef9L/+S0/+l0APu6nwf98dVwMDm6OP988sOkdR5yir0QUzm02qLR0Y+Ds/7ksj/+EUHCnFl8vzsDvlQ52+HNoIge2Dzk5Gt4syqNkAjnKUziKTDyM191RItGtMbjdBYA+3XlO6/vHEfKbZEdxzn0E9tzVmTJE5PTAzkMnLKEAUyREDhSl6h0hxuo0gvCO9ijKtnIXKiFLrbsGIBpt3jmeO/1KfGaYHwfyAHKU/ivqJLSc23+7XCIO/O4KOL0HqTs8h5nBvrtxIWjR/Y7TlolqK+wdcIkmYhImS+JgY7oA+x/Gl2cB4swL5hnHtgWHGHPqpz9xeUcXDlRWEYPjsfyPMt9AXYrIuKUxA2o3go0EAmHkHBmIWAw7TofXgnwGcgNzo3YZXVrkgdIvFOiEVsNcB7qimgfsygJ8xBVgg95fEslF2tdIFALEpbeg3C/d/b8b6WpFeD13s2aEbGVQVQTq2m1SGJgNyu+AbGnMI/Du4TtqNUN5DTcFvwam9bVSp5ZW7G4NPpfnz7ufdwDOYT8l0ekqXP09QmGUvYsJ3qvTpgkfOcuQKsYHKIyj6NStpSFyPJ5WMKkWZiAuqwEXIAYwAXzM1s+Z/kUwD3yX13HfdkRNnVHEOjKAzpeLhhMwI3o5NAefEMFWO7LSQmibL4ABTW1aJuTQF2OQzN6AGQK8A4P19jlntOUJSGR5UBK+AQnBAXrOl3nLW0097WPkhzalz9QuPy3Ploo5ggcgTY5y25gjDS5ug+Mbs7SKcvZ9O9Z/phk4fRIDnqvYkc5Y5hlIGZtt3WvxA0M+ILl5fI8nDMQ/cXdVxaVAYhYHudAzIhZIShb/WorpZmNtojrSxsDa5aIH1yDLSBfHHZ++cV5HxdDFqFiqvEAtqkAWxw26WlMkEce4e1tR0GcLqrSvCskT51nIQxDfEUaLoqHrGxcImpKLyiq+TwE0YNYbdtLN5s8Ik3cfcsoMGmb+3keLoO4oP89uTzg4+DbOLUuUPTrm4PtRFcxJHyOUgcy7rIKKJ+ClSHbwkWH1s4XMlUWFcRg0ixJVsLd6haQdwCdJWevHd6q6f6weVy2uD5fOBtgxlon6ElNgaEtvCiiR/pDRQlakQfPeQzCu/3wSr4At4bxTAkGlvmrf6W3tFSwQQ+Z9IQFeveKqrxKv5Uo414h2KbFsUjdmmMbzIAF8H9pDH4Th1ct1ykc3wU4mKVha7OSJQPZbfi48JILLsaD08ngH5cXw/FgOLkYn15OxsP+0WA0GZwfX16cnI9xheuaV3IZz1lWlWcojUVWxGX8xE7Skt2z3DP3HKt5Gzcdn5wNLq7Gk7ORdfEX4Ry8ivR+iETDTlb7C55ea7ORNb1tQ0HXqH92eQrb9oc/iP3QMkm2tZpQxVvS3b+HeQobIYNhX9HyXMmvydnJaHRy/oM62+R9yIbindh7a/lbe2sVMAzdq6GlHcLtMlyiBe+KvQ10uzVaVhzWSvHDkLG4nL6JGUqmm3hhHIL/RzZwMg0A+qnA7ciFHvLMPSWfCpY/xRFDT6RrHcaRHmjV1NFg+OXkaDA5758N6PhJrzSKI+2pIX7VnOUatuypA706PzkejH6GF+oRvllHV2eDobygJTTreMnOMg8jBo8tlhpel9FZ34iO2GV/OOBmwxyqrXIVGWn2fFZo6NS09j+Z1vY+mSqkF2FqDvO26dWt09bwOYxl7+e6UJU1sPTHOLwdteOMwWVxAqxDoqhhGhVr0/fqIP1WXX9L09/W8fZbbhsrR1Z7AnZuMDzvn04+w0sEQiH1A05gO//j2+HbLsxfzb1t7cSqzT3bykCs89DE8Tjk8Y2xPjFey9kzglPiVBhXvuxB1tJss8eI13r7f/aDMhsR1zz3gb24ZtCLjpMBVnTUYPxlAwg6zgYE3gYAx+jpQssz9yvDvES9uErjl/MwxWtfwP0U34MSebTcd/7g7E/29vbwb2r6mvLFU2RVHrERYAuW5Vo/Inl/F2QflgD3DoJWaHpATH3VVm9OB5773FAHKVhq1zAztv1uN8+O6oeYWaRss9turk1TbKxE1AJpmicUnDLxqPs8a6DwJ9YEXnVhVLKpKwIlYiKo4EoZ1AjexnW+iW5kW0p3mQKMBAe2G4yxEZYXgB/Me9p3NWxurm3AysR3jC7TivM/QRB4Qo+lTsoncw/OkNOlGD6Oj8SNYM9e+SY0QYSJ/MSKPbvm7Mc4BXj7FoJ1PTUH4e4wh7qbZ2+lhyTMGqbP3BSBLLVlkZ2djSvvkix65Eu5td44GwNQ9ibYs3kN5+KEUjLWUh3s3gyArg9oVGlpA6CBI+w3AdjiZU8g+bqaOXR5bykUpSIbnu6Ej2uv2lqYv0Wcv05EzVWzOC/KCTHUXkwDFAOxQdhcXt3UTkRZFXgMcWs4Luax11PlL/7/igICtUjA2y7ZosUdU26YeZ3Cv2WeJQnLZQT5DkAfqV7z6hGw8J3JSuGaeJ7vHH5vgAlCBIBJOvsZV3+Dw0WzgB9M+ZkKPYMMqRGQNHzIMPh8eTEaK4V6gAQLy4mjmCYrQaV3SohBg6jccMHD7cCuXcqSaWbfZdNl16l5QoJhSnxFfA+5wK5JGO+yHWf0YCUlQfaokghodi1PynQQb38cjy8nH17VQq4ZmI2pdW18GG8Mxpr70bgTA9fDNGLZzCHFdX7/e+6C0s1Nl4VLwqdRF8Pmwq+kyLhwatGXHRwrV/ZXgVaub52eWQwMTpSeJCzMpZqJqb5yKuve5NrMFEGjwKqKGssRCFKKeDeqDT0+3IyCx64dZm5GxWvsfnVEjgBMqkwl5hBxd+YV6Podc0IQCUGWqcLnGCIoGqALCSOdIsIArXSg5YERG8B54cFbGbDkaani01LYA3GOz8IFP7szkIfHJ1+bgf+OSgPwWN+NA+IToXM4STkgtYFum6E2JF+HQWRWblEVD96tnAbZSBOTleRJg1k6cIHnME4r1gw+zBTZNhbXLZmqG31qUeJAsFxNsheZK5S96IcnQTz3/DURry2pI7KmpJ/MaSC1DY1KyhSVrks8uGelZyNMFQ0yyK9WcERN5PzaDA2zaMDs6Gm+rZpacVE7wRFtgSdVyr+hHeEGTUq4XrxrA+wN3SUaXZWd3ff5ojkotXct0GosvJXC/PAqfgHvIRvjlBnIICnjBeT2tS5jUlzvRRUHbseFpLj1WpJCNo+nuJV1ZH59bs7KynXszJuz0sZJqKM5u6GT5qClkWSsjIIIcyJZOV4Xo2IrtUuKC/E5hBt0KqzHCKRv4eqbJgdfSvQwPxA5BqyaII8Tevdkp0yONQYom8SmFJ+2Bg1TRXUYwJAEiILnbRWhOKmcorDJk6dRo/DdobPP1dqAN80ieEbCKMCkyMGx6Cg8XGmYLGMRBE3muOCxumN5ykDyJ9BTeBKYsUqFJ7LpaMEioAgXQ6oPGr0Afi4SnvqD9oFaU7OifC1AtS0B/rGZqUmsYXyfZ9UCUb4mjyiMU3CQMGUZpzF5daLnxsTcgqCWIRQ7syjwu6ZNbjCxaPdQXtEGbIHGqrgGULVfDwNbCFT1YEcrTME4rrHBQ1iAy/jUI2eD1F0cJuokbbWOUht7+R+u2dcYYENYNzLKNBQKxzMqOEwz/fr6pmLbgtJmvf5b/lop07+qR2w4ctz9FNnPrg6ji6CNSk3vSIXZAXSVx2yXCIg6L1BMCD/WS+ysIrDaiQMWu4ECqk6e7LB0VXY2eFMvRqDOrm0hZanMO1M+lyDFEmuajJf5Sr+VlFVuNR1kEfXzZx6+0ABI+iNGyg7MEag9Ax8kw2urOT4Ty0xnbAFs4vHga5t9uOuNNH9TAXZp2b7nB7R4ngBhFSaZz6aoymX8U8xcZAtZDVSDrJUPj0tt8Psahb5TPuTZM1l/nty/bZY5KBjA3DnlBiLGpry+zAa3km6Ncf7pMUCXnKpd9QQ9fj1zRvbfLAYUEzsExXSZeEwUOaKKZblJl3OQeBwBkzNazu8yuMRP4/TRa7GzxlRJyxLnKV6j/2QBZ1g1hct2/xV4ve4y7M2TX/DB6X/YjSsw/AUEZBFj04aR7rQAE8VqseX7SBXdSkDEtlbZEIyVdjZbLQ3fuqAHvH2Y2u5M4xyV7KWkrL9R8ohzVJGjrMQDNiF3NHOs25eCBBKZa6OWEuHDCVJvXj0JO800vxxAl4cv7ieJRpwAcT9S3eLoO8oGROl/GoHIlX/aOkvnqdaiZDJvrf9AZItlsrMXYDSVP5pPwYS5+Jq270yFLPoYVOFj9+ClKXtuWuRo32f8LiNULA4a7BfXXZNWGqiT21ol1PYO5wPKH3V1FdU0A+OaZsJXFO9D98B4DPOIg6gZlhc+xRzsMdOH7rXN6IrKDQ5Y1DnWYaruNnByEIP5OuxpXCW8tqUOU3W3wZSDGGClEl7XeJRAIs8iAe/jXXcFB12ggj8FhNVt/a1ChTkU/XSMMKZjhI11nZV5xp9VtlNPxQtKEyoEz2NAilq9SZ1UPYLl5QkkOcA+FN/t3ldgNxzXl6/vAkw286D+6+Of4DrsqoeL5R9RSHv7eLQVN5bhfsun0AFiHZLvbqhr53Pr0XdNY9fiT8cu++TNeVUSjhYe670eDPIZPiDkcQaCCVvkbcxAvJHOWxtzbwTa14TY7bj65rTHW8x8O+/RluzYNpmzWu871j8GSSlghlcsBcrFNy4tRpNP4Vd+i70kza2ZS1XSrSxk8/sVUdetTOXaGQd2PKGtwqX/0xecOfl0dX6Mn62YZxKumreXtDinAq070O+ELL/3NpiW8jxVFSo+wQHW7EzhDtylfwSjAr4L+BGuFQcXLgQ5SarO8EoXeHsu4OVQHb0ahrAc+nYsjZZYsGuWg7eEmg3ytANU/xpFODTfiAcHbRYNt2ZN+GdZGHsRSSA8vCtZk/VZIOq54vMtMKNimtvZ4Jz5Hp/fEfADsailOogiRPasYMpmIdwpGHK0RxrRVgWD3uOSr67p2uJHBNxtcAVPRPlIzB2CkKODzzpIJdjlKhL8+uwIVRxt89WARIkmH7ylWbcbJQq+Tl4tSscTH+E08yU9M12CyRbKk6z82xYlbEVAXPbmdc3ELazfC3ISwuT7cR60c8C4YAUTTCyUD2d4j2K2kDUPvByaORWwLjocQ5UJ0PO+Ub5OkRVzs5YKYXPDN5iqXSlRJ8Mh1PziWhWWQZaqOypI+c+r+Z2aYDCJ9wMdEgRf4ZNN5YvBYDo92ZBujIlFS/3ub0DkMwb9WBseGDPWzb9BnGAzXo1aqwZW3E8/dHb/vbe3413v7fw13JndvP7x48o3mvt/hqZqfVzRM5C9sMgTouFw7QC8KnCkPXrB9f4NRFSz0+yZ5UchvE58Vdajih3l1I/rpjYufLsUQX5NYmhZLTUmPyex8hjQJXMYdTXXPegDQqYTRCLyGeC0W4tFIQR2awew23Ku7rIMcqSpK/wZKPNxsOsLn88nyshi2/qUNMVVjlOP0h2lWG+JxIhVYr6d+wvtE8md8v0mf2U+OgqrAmKA+AB5KMvFSKS6McU7vajKlvSwKQfb2ydHlyrAuPG3vXuziKvdx1eovJM1VBqllgIqY1CWeii0tfvPP4TbztbrcjRdqmG4/UNRj6Yp4YqLF1nti2pC1/pKWzlhYX7/BMeGFxX7wUPOZvanvzq8ewI/X3gyVS2FBzN0Xsw8d2dHBZgxqMnvX16/8ebnKa+18HPLrkDADpoiYnrXwuG6Of07B0haWZ/ggB9WHvFLDVzh1X8BgBIs15M/AAA=' | base64 -d | gzip -d >\"$feature_config_validator\"; then\n rm -f \"$feature_config_validator_base\" \"$feature_config_validator\" || true\n feature_config_validator=''\n fi\n rm -f \"$feature_config_validator_base\" || true\nfi\nexport UNIDESK_AJV2020_BUNDLE='/etc/unidesk-cicd-runtime-gitops/ajv2020.min.js'\nexport UNIDESK_FEATURE_CONFIG_VALIDATOR=\"${feature_config_validator:-${TMPDIR:-/tmp}/unidesk-feature-config-validator-unavailable.mjs}\"\nif ! node --input-type=module <<'NODE_UNIDESK_FEATURE_CONFIG_SCHEMA'\nimport { pathToFileURL } from 'node:url';\nconst validator = await import(pathToFileURL(process.env.UNIDESK_FEATURE_CONFIG_VALIDATOR).href);\nawait validator.runFeatureConfigSchemaValidation({ repoDir: process.env.UNIDESK_FEATURE_CONFIG_REPO_DIR, renderedRoot: process.env.UNIDESK_FEATURE_CONFIG_RENDERED_ROOT, consumer: process.env.UNIDESK_PAC_CONSUMER });\nNODE_UNIDESK_FEATURE_CONFIG_SCHEMA\nthen\n printf '{\"event\":\"feature-config-schema-validation\",\"warning\":true,\"blocking\":false,\"code\":\"feature-config-validator-process-failure\",\"mutation\":false,\"valuesPrinted\":false}\\n' >&2\nfi\nif [ -n \"$feature_config_validator\" ]; then rm -f \"$feature_config_validator\" || true; fi\nif command -v ci_timing_emit >/dev/null 2>&1; then\n ci_timing_emit feature-config-schema-validation succeeded \"$feature_config_schema_started_ms\"\nelse\n printf '{\"event\":\"ci.stage.timing\",\"stage\":\"feature-config-schema-validation\",\"status\":\"succeeded\",\"blocking\":false}\\n' >&2\nfi\n\nUNIDESK_RUNTIME_GITOPS_OVERLAY_FILE=/etc/unidesk-cicd-runtime-gitops/runtime-gitops-overlay.json node /etc/unidesk-cicd-runtime-gitops/runtime-gitops-verify.mjs\nci_timing_emit gitops-render succeeded \"$gitops_render_started_ms\"\ncheck_source_head before-push\nworkdir=\"$(mktemp -d)\"\ngitops_read_url=\"$(params.git-url)\"\ngitops_write_url=\"$(params.git-write-url)\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n gitops_read_url=\"$(params.git-read-url)\"\nfi\ngitops_clone_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-clone 180 git clone --no-checkout \"$gitops_read_url\" \"$workdir/gitops\"\ncd \"$workdir/gitops\"\ngit remote set-url origin \"$gitops_write_url\"\nold_runtime_snapshot=\"$workdir/old-runtime-snapshot\"\nif git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin \"$(params.gitops-branch)\" >/dev/null; then\n git_timed gitops-fetch 90 git fetch origin \"$(params.gitops-branch)\"\n git checkout -B \"$(params.gitops-branch)\" \"origin/$(params.gitops-branch)\"\n if [ \"$runtime_lane\" = \"true\" ] && [ -d \"$runtime_path\" ]; then\n mkdir -p \"$old_runtime_snapshot\"\n cp -a \"$runtime_path\"/. \"$old_runtime_snapshot\"/\n fi\n if [ \"$runtime_lane\" = \"true\" ]; then rm -rf \"$runtime_path\" \"$catalog_path\" \"$unidesk_legacy_runtime_path\"; else rm -rf deploy/gitops/node \"$catalog_path\"; fi\nelse\n git checkout --orphan \"$(params.gitops-branch)\"\n git rm -rf . >/dev/null 2>&1 || true\nfi\nci_timing_emit gitops-clone succeeded \"$gitops_clone_started_ms\"\nmkdir -p \"$(dirname \"$runtime_path\")\" \"$(dirname \"$catalog_path\")\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n cp -a \"/workspace/source/repo/$runtime_path\" \"$runtime_path\"\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" \"$runtime_path\"\n if [ -n \"${unidesk_legacy_runtime_path:-}\" ]; then git add -A \"$unidesk_legacy_runtime_path\" || true; fi\nelse\n mkdir -p deploy/gitops\n cp -a /workspace/source/repo/deploy/gitops/node deploy/gitops/node\n cp \"/workspace/source/repo/$catalog_path\" \"$catalog_path\"\n git add \"$catalog_path\" deploy/gitops/node\nfi\nif [ \"$runtime_lane\" = \"true\" ] && [ -s /workspace/source/affected-services.json ]; then\n runtime_noop_decision=\"$(node - \"$runtime_path\" \"$old_runtime_snapshot\" /workspace/source/affected-services.json <<'NODE'\nconst fs = require(\"node:fs\");\nconst path = require(\"node:path\");\n\nconst [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2);\n\nfunction readJson(filePath) {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n}\n\nfunction stable(value) {\n if (Array.isArray(value)) return \"[\" + value.map(stable).join(\",\") + \"]\";\n if (value && typeof value === \"object\") {\n return \"{\" + Object.keys(value).sort().map((key) => JSON.stringify(key) + \":\" + stable(value[key])).join(\",\") + \"}\";\n }\n return JSON.stringify(value);\n}\n\nfunction scrub(value) {\n if (Array.isArray(value)) return value.map(scrub);\n if (!value || typeof value !== \"object\") return value;\n const result = {};\n for (const [key, child] of Object.entries(value)) {\n if (key === \"hwlab.pikastech.local/source-commit\" ||\n key === \"hwlab.pikastech.local/artifact-source-commit\" ||\n key === \"hwlab.pikastech.local/boot-commit\" ||\n key === \"sourceCommitId\" ||\n key === \"bootCommit\") {\n result[key] = \"\";\n continue;\n }\n const envName = String(value.name || \"\");\n if (key === \"value\" && /^HWLAB_.*COMMIT/.test(envName)) {\n result[key] = \"\";\n continue;\n }\n result[key] = scrub(child);\n }\n return result;\n}\n\nfunction listFiles(rootDir) {\n if (!rootDir || !fs.existsSync(rootDir)) return null;\n const files = [];\n function walk(current) {\n for (const entry of fs.readdirSync(current, { withFileTypes: true })) {\n const fullPath = path.join(current, entry.name);\n if (entry.isDirectory()) walk(fullPath);\n else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, \"/\"));\n }\n }\n walk(rootDir);\n return files.sort();\n}\n\nfunction normalizedTree(rootDir) {\n const files = listFiles(rootDir);\n if (!files) return null;\n const entries = {};\n for (const relativePath of files) {\n const filePath = path.join(rootDir, relativePath);\n const text = fs.readFileSync(filePath, \"utf8\");\n try {\n entries[relativePath] = stable(scrub(JSON.parse(text)));\n } catch {\n entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, \"\");\n }\n }\n return stable(entries);\n}\n\nconst plan = readJson(planPath);\nconst buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];\nconst rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : [];\nif (buildServices.length > 0 || rolloutServices.length > 0) {\n process.stdout.write(\"runtime-required\");\n process.exit(0);\n}\n\nconst oldTree = normalizedTree(oldRuntimePath);\nconst newTree = normalizedTree(runtimePath);\nprocess.stdout.write(oldTree && newTree && oldTree === newTree ? \"runtime-identity-only\" : \"runtime-required\");\nNODE\n)\"\n if [ \"$runtime_noop_decision\" = \"runtime-identity-only\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"skipped-runtime-unchanged\",\"reason\":\"runtime-identity-only\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit skipped \"$(ci_now_ms)\"\n ci_timing_emit gitops-push skipped \"$(ci_now_ms)\"\n exit 0\n fi\nfi\nif git diff --cached --quiet; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"status\":\"unchanged\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\n ci_timing_emit gitops-commit unchanged \"$(ci_now_ms)\"\n ci_timing_emit gitops-push unchanged \"$(ci_now_ms)\"\n exit 0\nfi\nshort=\"$(printf '%.7s' \"$(params.revision)\")\"\ngitops_commit_started_ms=\"$(ci_now_ms)\"\ngit commit -m \"chore: promote node GitOps source $short\"\nci_timing_emit gitops-commit succeeded \"$gitops_commit_started_ms\"\ngitops_push_started_ms=\"$(ci_now_ms)\"\ngit_timed gitops-push 120 git push origin \"HEAD:$(params.gitops-branch)\"\nci_timing_emit gitops-push succeeded \"$gitops_push_started_ms\"\nif [ \"$runtime_lane\" = \"true\" ]; then\n printf 'false' > /tekton/results/runtime-ready-required\n echo '{\"event\":\"runtime-ready\",\"phase\":\"gitops-promote\",\"status\":\"delegated-post-flush-closeout\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\"}'\nfi\nargo_hard_refresh_runtime_lane\necho '{\"status\":\"pushed\",\"gitopsBranch\":\"'\"$(params.gitops-branch)\"'\",\"sourceRevision\":\"'\"$(params.revision)\"'\",\"gitopsWriteUrl\":\"'\"$gitops_write_url\"'\"}'\n" imagePullPolicy: Always volumeMounts: - name: unidesk-runtime-gitops-scripts