From 633a49f3dd45dd21cf964303cbd34e87496234b5 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 21 Jul 2026 05:29:37 +0200 Subject: [PATCH] fix(ci): bound NC01 service build workers (#2721) --- .tekton/hwlab-nc01-v03-pac.yaml | 6 +- .../hwlab-nc01-v03-ci-image-publish.yaml | 102 ++++++------------ 2 files changed, 38 insertions(+), 70 deletions(-) diff --git a/.tekton/hwlab-nc01-v03-pac.yaml b/.tekton/hwlab-nc01-v03-pac.yaml index 14140df7..e3528604 100644 --- a/.tekton/hwlab-nc01-v03-pac.yaml +++ b/.tekton/hwlab-nc01-v03-pac.yaml @@ -1,7 +1,7 @@ apiVersion: tekton.dev/v1 kind: PipelineRun metadata: - generateName: hwlab-nc01-v03-ci-poll- + name: "hwlab-nc01-v03-ci-poll-{{ revision }}" namespace: hwlab-ci annotations: pipelinesascode.tekton.dev/on-event: "[push]" @@ -9,10 +9,11 @@ metadata: pipelinesascode.tekton.dev/on-cel-expression: "event == 'push' && target_branch == 'v0.3' && node == 'NC01'" pipelinesascode.tekton.dev/max-keep-runs: "8" unidesk.ai/owning-config-ref: "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01" - unidesk.ai/effective-config-sha256: sha256:e6f14d619ce11e215530100670a08b093833853d435dd2d5cc95937eea553a9f + unidesk.ai/effective-config-sha256: sha256:b132b19c115bb457e6ed7fc02d281aac0f6758208afa37977e114adaf1673e36 unidesk.ai/source-artifact-renderer: hwlab-runtime-lane unidesk.ai/source-artifact-mode: remote-pipeline-annotation pipelinesascode.tekton.dev/pipeline: ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml + unidesk.ai/ci-dedupe-mode: deterministic-kubernetes-name hwlab.pikastech.local/ci-contract: tekton-native-primitive-tasks hwlab.pikastech.local/download-profile: jd01-node-default hwlab.pikastech.local/gitops-branch: v0.3-gitops @@ -39,6 +40,7 @@ spec: dnsPolicy: ClusterFirstWithHostNet securityContext: fsGroup: 1000 + priorityClassName: unidesk-ci-batch pipelineRef: name: hwlab-nc01-v03-ci-image-publish workspaces: diff --git a/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml b/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml index 4ab225f6..1df7da10 100644 --- a/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml +++ b/ci/pipelines/hwlab-nc01-v03-ci-image-publish.yaml @@ -12,8 +12,14 @@ metadata: hwlab.pikastech.local/policy: native-per-service-taskrun-image-publish hwlab.pikastech.local/download-profile: jd01-node-default hwlab.pikastech.local/network-profile: jd01-node-ci-egress + unidesk.ai/ci-resource-governance-config-ref: "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01.ciResourceGovernance" + unidesk.ai/ci-dedupe-identity: repository+lane+sourceCommit+pipelineIntent + unidesk.ai/ci-build-queue-mode: single-taskrun-bounded-workers + unidesk.ai/ci-build-service-count: "17" + unidesk.ai/ci-build-batch-count: "9" + unidesk.ai/ci-build-max-parallel: "2" unidesk.ai/owning-config-ref: "config/hwlab-node-lanes.yaml#lanes.v03.targets.NC01" - unidesk.ai/effective-config-sha256: sha256:e6f14d619ce11e215530100670a08b093833853d435dd2d5cc95937eea553a9f + unidesk.ai/effective-config-sha256: sha256:b132b19c115bb457e6ed7fc02d281aac0f6758208afa37977e114adaf1673e36 unidesk.ai/source-artifact-renderer: hwlab-runtime-lane unidesk.ai/source-artifact-mode: remote-pipeline-annotation spec: @@ -481,54 +487,11 @@ spec: - name: catalog-path - name: image-tag-mode - name: registry-prefix - - name: service-id + - name: services - name: base-image - name: build-cache-mode results: - - name: status - description: hwlab-cloud-api status - - name: service-id - description: hwlab-cloud-api service-id - - name: image - description: hwlab-cloud-api image - - name: image-tag - description: hwlab-cloud-api image-tag - - name: digest - description: hwlab-cloud-api digest - - name: repository-digest - description: hwlab-cloud-api repository-digest - - name: source-commit-id - description: hwlab-cloud-api source-commit-id - - name: component-input-hash - description: hwlab-cloud-api component-input-hash - - name: environment-input-hash - description: hwlab-cloud-api environment-input-hash - - name: code-input-hash - description: hwlab-cloud-api code-input-hash - - name: runtime-mode - description: hwlab-cloud-api runtime-mode - - name: boot-repo - description: hwlab-cloud-api boot-repo - - name: boot-commit - description: hwlab-cloud-api boot-commit - - name: boot-sh - description: hwlab-cloud-api boot-sh - - name: build-created-at - description: hwlab-cloud-api build-created-at - - name: build-backend - description: hwlab-cloud-api build-backend - - name: reused-from - description: hwlab-cloud-api reused-from - - name: go-base-image - description: hwlab-cloud-api go-base-image - - name: go-base-image-status - description: hwlab-cloud-api go-base-image-status - - name: go-base-digest - description: hwlab-cloud-api go-base-digest - - name: go-base-build-duration-ms - description: hwlab-cloud-api go-base-build-duration-ms - - name: go-base-buildkit-cache-ref - description: hwlab-cloud-api go-base-buildkit-cache-ref + [] workspaces: - name: source volumes: @@ -576,6 +539,13 @@ spec: type: Unconfined seccompProfile: type: Unconfined + computeResources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 1500m + memory: 1536Mi steps: - name: prepare-buildkit-client image: 127.0.0.1:5000/hwlab/buildkit:rootless @@ -586,6 +556,13 @@ spec: - name: buildkit-bin mountPath: /workspace/buildkit-bin 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\nmkdir -p /workspace/buildkit-bin\ncp /usr/bin/buildctl /workspace/buildkit-bin/\nchmod +x /workspace/buildkit-bin/*\n" + computeResources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 750m + memory: 768Mi env: - name: HTTP_PROXY value: http://127.0.0.1:10808 @@ -652,8 +629,15 @@ 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\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.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\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"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-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-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-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-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-v03\",\"node\":\"NC01\",\"lane\":\"v03\",\"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\":\"v0.3\",\"gitopsBranch\":\"v0.3-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: "set -eu\nbuild_service() {\n service_id=\"$1\"\n mkdir -p \"/workspace/service-work/$service_id\" \"/workspace/service-results-tekton/$service_id\"\n # unidesk-step-env-bootstrap\n export HOME='/tekton/home'\n export XDG_CONFIG_HOME='/tekton/home/.config'\n mkdir -p \"$HOME\"\n mkdir -p \"$XDG_CONFIG_HOME\"\n ci_node_deps=\"${HWLAB_CI_NODE_DEPS:-/opt/hwlab-ci-node-deps/node_modules}\"\n if [ -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\n fi\n #!/bin/sh\n set -eu\n ci_now_ms() {\n node -e 'console.log(Date.now())'\n }\n \n ci_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 \n export HWLAB_TEKTON_PIPELINERUN=\"$(context.pipelineRun.name)\"\n export HWLAB_TEKTON_TASKRUN=\"$(context.taskRun.name)\"\n export HWLAB_TEKTON_TASK=\"build-$service_id\"\n export HWLAB_SOURCE_REVISION=\"$(params.revision)\"\n export HWLAB_TIMING_SERVICE_ID=\"$service_id\"\n HWLAB_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\"}'\n const net = require(\"node:net\");\n \n const phase = process.env.HWLAB_PROXY_PROBE_PHASE || \"unknown\";\n const target = new URL(process.env.HWLAB_PROXY_PROBE_URL || \"http://deb.debian.org/debian/dists/bookworm/InRelease\");\n const proxyRaw = process.env.HTTP_PROXY || process.env.http_proxy || \"\";\n \n function 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 \n function 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 \n if (!proxyRaw) emit({ ok: false, reason: \"proxy-env-missing\" }, 21);\n \n let proxy;\n try {\n proxy = new URL(proxyRaw);\n } catch (error) {\n emit({ ok: false, reason: \"proxy-url-invalid\", error: error.message }, 22);\n }\n \n if (proxy.protocol !== \"http:\" && proxy.protocol !== \"https:\") {\n emit({ ok: false, reason: \"http-proxy-required\", protocol: proxy.protocol }, 23);\n }\n \n const startedAt = Date.now();\n const socket = net.createConnection({ host: proxy.hostname, port: Number(proxy.port || (proxy.protocol === \"https:\" ? 443 : 80)) });\n let bytes = 0;\n let firstByteMs = null;\n let responseHead = \"\";\n let finished = false;\n \n const timeout = setTimeout(() => {\n if (finished) return;\n finished = true;\n socket.destroy();\n emit({ ok: false, reason: \"timeout\", timeoutMs: 15000 }, 24);\n }, 15000);\n \n socket.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 \n socket.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 \n socket.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 \n socket.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 });\n NODE\n echo '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"serviceId\":\"'\"$service_id\"'\",\"image\":\"127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1\",\"policy\":\"no-runtime-apk\"}'\n for tool in node npm git python3 ssh curl; do command -v \"$tool\" >/dev/null 2>&1 || { echo '{\"event\":\"ci-base-image\",\"phase\":\"service-image-publish\",\"ok\":false,\"reason\":\"missing-tool\",\"tool\":\"'\"$tool\"'\"}'; exit 31; }; done\n mkdir -p /workspace/service-work/$service_id/home\n export HOME=/workspace/service-work/$service_id/home\n git config --global --add safe.directory /workspace/source/repo\n cd /workspace/source/repo\n test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\n export HWLAB_ARTIFACT_LANE=\"$(params.lane)\"\n export HWLAB_ARTIFACT_CATALOG_PATH=\"$(params.catalog-path)\"\n export HWLAB_DEPLOY_MANIFEST_PATH=\"deploy/deploy.yaml\"\n export HWLAB_ARTIFACT_IMAGE_TAG_MODE=\"$(params.image-tag-mode)\"\n mkdir -p /workspace/source/service-results\n if [ -s /workspace/source/affected-services.json ] && ! node -e 'const fs=require(\"node:fs\"); const p=JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\",\"utf8\")); const service=process.argv[1]; const item=(p.entries||[]).find((entry)=>entry.serviceId===service); process.exit(item && item.buildRequired ? 0 : 1);' \"$service_id\"; then\n node - \"$service_id\" <<'NODE'\n const fs = require(\"node:fs\");\n const serviceId = process.argv[2];\n const catalog = JSON.parse(fs.readFileSync(process.env.HWLAB_ARTIFACT_CATALOG_PATH, \"utf8\"));\n const plan = fs.existsSync(\"/workspace/source/affected-services.json\") ? JSON.parse(fs.readFileSync(\"/workspace/source/affected-services.json\", \"utf8\")) : {};\n const service = (catalog.services || []).find((item) => item.serviceId === serviceId) || {};\n const planned = (plan.services || []).find((item) => item.serviceId === serviceId) || {};\n const envReuse = planned.runtimeMode === \"env-reuse-git-mirror-checkout\" || service.runtimeMode === \"env-reuse-git-mirror-checkout\" || planned.envReuse === true || service.envReuse === true;\n const image = envReuse ? (service.environmentImage || service.image || \"\") : (service.image || \"\");\n const digest = envReuse ? (service.environmentDigest || service.digest || \"not_published\") : (service.digest || \"not_published\");\n const imageTag = service.imageTag || (image.includes(\":\") ? image.slice(image.lastIndexOf(\":\") + 1) : \"\");\n const repository = image.includes(\":\") ? image.slice(0, image.lastIndexOf(\":\")) : image;\n const revision = process.env.HWLAB_SOURCE_REVISION || planned.bootCommit || service.bootCommit || service.sourceCommitId || service.commitId || imageTag;\n const componentInputHash = envReuse ? (planned.componentInputHash || service.componentInputHash || \"\") : (service.componentInputHash || \"\");\n const environmentInputHash = envReuse ? (service.environmentInputHash || planned.environmentInputHash || \"\") : (service.environmentInputHash || \"\");\n const codeInputHash = envReuse ? (planned.codeInputHash || service.codeInputHash || \"\") : (service.codeInputHash || \"\");\n const 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 };\n for (const [name, value] of Object.entries(values)) fs.writeFileSync(\"/workspace/service-results-tekton/$service_id/\" + name, String(value || \"\"));\n NODE\n echo '{\"event\":\"service-build-skip\",\"serviceId\":\"'\"$service_id\"'\",\"reason\":\"component-inputs-unchanged\"}'\n exit 0\n fi\n rm -rf /workspace/service-work/$service_id/repo\n mkdir -p /workspace/service-work/$service_id/repo\n tar -C /workspace/source/repo -cf - . | tar -C /workspace/service-work/$service_id/repo -xo -f -\n cd /workspace/service-work/$service_id/repo\n export HWLAB_CI_ARTIFACT_RUN_ID=\"$(context.pipelineRun.name)-$service_id\"\n export HWLAB_CI_ARTIFACT_RUN_OWNER=\"tekton\"\n export HWLAB_DEV_REGISTRY_PREFIX=\"$(params.registry-prefix)\"\n export HWLAB_DEV_REGISTRY_K8S_NATIVE=1\n export HWLAB_NODE_CICD_TIMING=1\n export HWLAB_TEKTON_PIPELINERUN\n export HWLAB_TEKTON_TASKRUN\n export HWLAB_TEKTON_TASK\n export HWLAB_SOURCE_REVISION\n export PATH=\"/workspace/buildkit-bin:$PATH\"\n export HWLAB_BUILDKIT_ADDR=\"unix:///workspace/buildkit-run/buildkitd.sock\"\n if [ -n \"$(params.base-image)\" ]; then export HWLAB_DEV_BASE_IMAGE=\"$(params.base-image)\"; fi\n if [ -n \"${HWLAB_DEV_BASE_IMAGE:-}\" ]; then\n echo '{\"event\":\"dependency-local-registry-probe-start\",\"phase\":\"service-image-publish-pre-base-image\",\"serviceId\":\"'\"$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\":\"'\"$service_id\"'\",\"ok\":true,\"durationSeconds\":'\"$((registry_finished_at - registry_started_at))\"'}'\n fi\n buildkit_ready=0\n for 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\n done\n if [ \"$buildkit_ready\" != \"1\" ]; then\n echo '{\"event\":\"buildkit-sidecar-not-ready\",\"serviceId\":\"'\"$service_id\"'\",\"addr\":\"'\"$HWLAB_BUILDKIT_ADDR\"'\"}' >&2\n exit 1\n fi\n HWLAB_CI_PLAN_VERIFY_REUSE_REGISTRY=1 node - <<'NODE_UNIDESK_DEPLOY_YAML_OVERLAY'\n const fs = require('fs');\n const YAML = require('yaml');\n const overlay = {\"nodeId\":\"NC01\",\"lane\":\"v03\",\"sourceBranch\":\"v0.3\",\"gitopsBranch\":\"v0.3-gitops\",\"gitopsRoot\":\"deploy/gitops/node/nc01\",\"runtimePath\":\"deploy/gitops/node/nc01/runtime-v03\",\"runtimeRenderDir\":\"runtime-v03\",\"runtimeNamespace\":\"hwlab-v03\",\"environment\":\"development\",\"catalogPath\":\"deploy/artifact-catalog.nc01-v03.json\",\"gitUrl\":\"git@github.com:pikasTech/HWLAB.git\",\"publicWebUrl\":\"https://lab-dev.hwpod.com\",\"publicApiUrl\":\"https://lab-dev.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\"},\"runtimeStore\":{\"postgres\":{\"mode\":\"platform-service\",\"serviceName\":\"nc01-host-postgres\",\"poolMax\":16}},\"codeAgentRuntime\":{\"enabled\":true,\"adapter\":\"agentrun-v02\",\"managerUrl\":\"http://agentrun-mgr.agentrun-v02.svc.cluster.local:8080\",\"apiKeySecretName\":\"hwlab-v03-master-server-admin-api-key\",\"apiKeySecretKey\":\"api-key\",\"runnerNamespace\":\"agentrun-v02\",\"secretNamespace\":\"agentrun-v02\",\"providerSecretNames\":{\"codex\":\"agentrun-v01-provider-codex\",\"gpt-pika\":\"agentrun-v02-provider-gpt-pika\",\"grok\":\"agentrun-v02-provider-grok\",\"dsflash-go\":\"agentrun-v01-provider-dsflash-go\"},\"toolSecretNames\":{\"githubPr\":\"agentrun-v01-tool-github-pr\",\"unideskSsh\":\"agentrun-v01-tool-unidesk-ssh\"},\"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-v03-cloud-api-sse\",\"timeoutMs\":30000,\"scanLimit\":1000000,\"matchedEventLimit\":2000,\"liveBufferLimit\":2000},\"configRef\":\"config/platform-infra/kafka.yaml#clients.hwlab-v03-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-v03-cloud-api\",\"directPublishConsumerGroupId\":\"hwlab-agentrun-event-direct-publish\",\"transactionalProjectorConsumerGroupId\":\"hwlab-v03-agentrun-event-projector\",\"hwlabEventConsumerGroupId\":\"hwlab-v03-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-v03\",\"node\":\"NC01\",\"lane\":\"v03\",\"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\":\"v0.3\",\"gitopsBranch\":\"v0.3-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};\n const file = 'deploy/deploy.yaml';\n if (!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 }\n const doc = YAML.parse(fs.readFileSync(file, 'utf8'));\n doc.nodes = doc.nodes || {};\n doc.nodes[overlay.nodeId] = { ...(doc.nodes[overlay.nodeId] || {}), gitopsRoot: overlay.gitopsRoot, sourceRepo: overlay.gitUrl };\n doc.lanes = doc.lanes || {};\n const lane = doc.lanes[overlay.lane] || {};\n const envRecipe = lane.envRecipe || {};\n const downloadStack = {\n ...(envRecipe.downloadStack || {}),\n httpProxy: overlay.dockerProxyHttp,\n httpsProxy: overlay.dockerProxyHttps,\n noProxy: overlay.dockerNoProxyList,\n };\n if (overlay.npmRegistry) downloadStack.npmRegistry = overlay.npmRegistry;\n if (overlay.npmFetchTimeoutMs) downloadStack.npmFetchTimeoutMs = overlay.npmFetchTimeoutMs;\n doc.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 };\n if (overlay.externalPostgres === undefined || overlay.externalPostgres === null) delete doc.lanes[overlay.lane].externalPostgres;\n else doc.lanes[overlay.lane].externalPostgres = overlay.externalPostgres;\n if (overlay.runtimeStore !== undefined) doc.lanes[overlay.lane].runtimeStore = overlay.runtimeStore;\n if (overlay.codeAgentRuntime !== undefined) doc.lanes[overlay.lane].codeAgentRuntime = overlay.codeAgentRuntime;\n if (overlay.deployYamlGitMirror !== undefined) doc.lanes[overlay.lane].gitMirror = overlay.deployYamlGitMirror;\n fs.writeFileSync(file, YAML.stringify(doc));\n console.error(JSON.stringify({ event: 'unidesk-deploy-yaml-overlay', ok: true, lane: overlay.lane, httpProxy: overlay.dockerProxyHttp, noProxyCount: overlay.dockerNoProxyList.length }));\n NODE_UNIDESK_DEPLOY_YAML_OVERLAY\n node 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/$service_id.json\" --tekton-results-dir /workspace/service-results-tekton/$service_id --quiet-build --concurrency 1 --services \"$service_id\" --buildkit-command /workspace/buildkit-bin/buildctl --buildkit-addr \"$HWLAB_BUILDKIT_ADDR\" --build-cache-mode \"$(params.build-cache-mode)\"\n\n\n \n}\nmax_parallel=2\nservice_csv=\"$(params.services)\"\nold_ifs=\"$IFS\"\nIFS=','\nset -- $service_csv\nIFS=\"$old_ifs\"\nfailed=0\nwhile [ \"$#\" -gt 0 ]; do\n pids=\"\"\n launched=0\n while [ \"$#\" -gt 0 ] && [ \"$launched\" -lt \"$max_parallel\" ]; do\n service_id=\"$1\"\n shift\n (build_service \"$service_id\") &\n pids=\"$pids $!\"\n launched=$((launched + 1))\n done\n for pid in $pids; do if ! wait \"$pid\"; then failed=1; fi; done\n if [ \"$failed\" -ne 0 ]; then exit \"$failed\"; fi\ndone" imagePullPolicy: Always + computeResources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 750m + memory: 768Mi params: - name: revision value: $(params.revision) @@ -669,27 +653,9 @@ spec: value: $(params.base-image) - name: build-cache-mode value: $(params.build-cache-mode) - matrix: - params: - - name: service-id - value: - - hwlab-cloud-api - - hwlab-workbench-runtime - - hwlab-user-billing - - hwlab-project-management - - hwlab-tasktree-api - - hwlab-tasktree-worker - - hwlab-harnessrl-api - - hwlab-harnessrl-worker - - hwlab-workbench-api - - hwlab-workbench-worker - - hwlab-hwpod-api - - hwlab-hwpod-worker - - hwlab-hwpod-web - - hwlab-cloud-web - - hwlab-gateway - - hwlab-edge-proxy - - hwlab-agent-skills + - name: services + value: $(params.services) + timeout: 20m - name: collect-artifacts runAfter: - build-services