1421 lines
63 KiB
JavaScript
1421 lines
63 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createHash } from "node:crypto";
|
|
import path from "node:path";
|
|
|
|
import {
|
|
applyEnvReuseBootEnv,
|
|
argoApplicationName,
|
|
bootMetadataForService,
|
|
cloneJson,
|
|
configString,
|
|
deepSeekProfileModel,
|
|
defaultBranch,
|
|
defaultRegistryPrefix,
|
|
defaultSourceRepo,
|
|
defaultV02GitReadUrl,
|
|
deployServiceForBoot,
|
|
effectiveSecretPlaneNodeId,
|
|
envReuseServiceIdsForLane,
|
|
gitopsPathForProfile,
|
|
gitopsRootNodeId,
|
|
gitopsTargetForProfile,
|
|
isRuntimeLane,
|
|
k8sLabelHash,
|
|
label,
|
|
moonBridgeImage,
|
|
moonBridgeSourceRef,
|
|
namespaceNameForProfile,
|
|
optionalObject,
|
|
readConfigRef,
|
|
runtimeCommitForService,
|
|
runtimeFrpConfigForProfile,
|
|
runtimeImageForService,
|
|
runtimeImageTagForService,
|
|
runtimeLabelForProfile,
|
|
runtimeLaneConfig,
|
|
runtimePathForProfile,
|
|
secretNameForProfile,
|
|
secretRefObjectForProfile,
|
|
v02EnvReuseEnabled,
|
|
v02MetricsLabels,
|
|
v02MetricsSidecarAnnotations,
|
|
v02MetricsSidecarContainer,
|
|
v02MetricsSidecarVolume,
|
|
versionNameForRuntimeLane
|
|
} from "./core.mjs";
|
|
|
|
function argoProject(args = { lane: "node" }, deploy = null) {
|
|
if (isRuntimeLane(args.lane)) {
|
|
const namespace = namespaceNameForProfile(args.lane, deploy);
|
|
const laneName = runtimeLaneConfig(deploy, args.lane).name ?? versionNameForRuntimeLane(args.lane);
|
|
return {
|
|
apiVersion: "argoproj.io/v1alpha1",
|
|
kind: "AppProject",
|
|
metadata: { name: namespace, namespace: "argocd" },
|
|
spec: {
|
|
description: `HWLAB ${laneName} additive GitOps project on target node; DEV/PROD remain outside this lane.`,
|
|
sourceRepos: [args.gitReadUrl],
|
|
destinations: [{ server: "https://kubernetes.default.svc", namespace }],
|
|
clusterResourceWhitelist: [{ group: "", kind: "Namespace" }],
|
|
namespaceResourceWhitelist: [{ group: "*", kind: "*" }]
|
|
}
|
|
};
|
|
}
|
|
return {
|
|
apiVersion: "argoproj.io/v1alpha1",
|
|
kind: "AppProject",
|
|
metadata: { name: "hwlab-node", namespace: "argocd" },
|
|
spec: {
|
|
description: "HWLAB node GitOps project; D601 remains outside this project.",
|
|
sourceRepos: [defaultSourceRepo],
|
|
destinations: [
|
|
{ server: "https://kubernetes.default.svc", namespace: "hwlab-dev" },
|
|
{ server: "https://kubernetes.default.svc", namespace: "hwlab-prod" }
|
|
],
|
|
clusterResourceWhitelist: [{ group: "", kind: "Namespace" }],
|
|
namespaceResourceWhitelist: [{ group: "*", kind: "*" }]
|
|
}
|
|
};
|
|
}
|
|
|
|
function argoApplication(args, profile = "dev", deploy = null) {
|
|
const namespace = namespaceNameForProfile(profile, deploy);
|
|
const runtimePath = runtimePathForProfile(profile, deploy);
|
|
const gitopsTarget = gitopsTargetForProfile(profile);
|
|
const project = isRuntimeLane(profile) ? namespace : "hwlab-node";
|
|
const repoURL = isRuntimeLane(profile) ? args.gitReadUrl : args.sourceRepo;
|
|
return {
|
|
apiVersion: "argoproj.io/v1alpha1",
|
|
kind: "Application",
|
|
metadata: {
|
|
name: argoApplicationName(profile),
|
|
namespace: "argocd",
|
|
labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": gitopsTarget }
|
|
},
|
|
spec: {
|
|
project,
|
|
source: { repoURL, targetRevision: args.gitopsBranch, path: gitopsPathForProfile(args, profile, deploy) },
|
|
destination: { server: "https://kubernetes.default.svc", namespace },
|
|
syncPolicy: { automated: { prune: isRuntimeLane(profile), selfHeal: true }, syncOptions: ["CreateNamespace=true", "ApplyOutOfSyncOnly=true"] }
|
|
}
|
|
};
|
|
}
|
|
|
|
function nodeFrpcManifest({ profile = "dev", source = null, deploy = null, args = {} } = {}) {
|
|
const namespace = namespaceNameForProfile(profile, deploy);
|
|
const profileLabel = runtimeLabelForProfile(profile);
|
|
const deploymentName = isRuntimeLane(profile) ? `hwlab-${profile}-frpc` : profile === "prod" ? "hwlab-node-prod-frpc" : "hwlab-node-frpc";
|
|
const configName = `${deploymentName}-config`;
|
|
const proxyPrefix = isRuntimeLane(profile) ? `hwlab-${profile}` : profile === "prod" ? "hwlab-node-prod" : "hwlab-node";
|
|
const frpConfig = runtimeFrpConfigForProfile(deploy, profile, args);
|
|
if (frpConfig.enabled === false) return { apiVersion: "v1", kind: "List", items: [] };
|
|
const webProxyName = frpConfig.webProxyName || `${proxyPrefix}-cloud-web`;
|
|
const edgeProxyName = frpConfig.edgeProxyName || `${proxyPrefix}-edge-proxy`;
|
|
const labels = {
|
|
"app.kubernetes.io/name": deploymentName,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/environment": profileLabel,
|
|
"hwlab.pikastech.local/gitops-target": gitopsTargetForProfile(profile),
|
|
"hwlab.pikastech.local/profile": profileLabel
|
|
};
|
|
const sourceCommitLabel = source?.full ? { "hwlab.pikastech.local/source-commit": source.full } : {};
|
|
const renderedLabels = { ...labels, ...sourceCommitLabel };
|
|
const podNetwork = isRuntimeLane(profile) ? {
|
|
hostNetwork: true,
|
|
dnsPolicy: "ClusterFirstWithHostNet"
|
|
} : {};
|
|
const authConfigText = frpConfig.authSecretName && frpConfig.authSecretKey ? `auth.token = "{{ .Envs.FRPC_AUTH_TOKEN }}"\n` : "";
|
|
const configText = `serverAddr = "${frpConfig.serverAddr}"
|
|
serverPort = ${frpConfig.serverPort}
|
|
loginFailExit = true
|
|
${authConfigText}
|
|
|
|
[[proxies]]
|
|
name = "${webProxyName}"
|
|
type = "tcp"
|
|
localIP = "${frpConfig.webLocalHost || `hwlab-cloud-web.${namespace}.svc.cluster.local`}"
|
|
localPort = ${frpConfig.webLocalPort}
|
|
remotePort = ${frpConfig.webRemotePort}
|
|
|
|
[[proxies]]
|
|
name = "${edgeProxyName}"
|
|
type = "tcp"
|
|
localIP = "${frpConfig.edgeLocalHost || `hwlab-edge-proxy.${namespace}.svc.cluster.local`}"
|
|
localPort = ${frpConfig.edgeLocalPort}
|
|
remotePort = ${frpConfig.edgeRemotePort}
|
|
`;
|
|
const configSha256 = createHash("sha256").update(configText).digest("hex");
|
|
const templateLabels = { ...labels, "hwlab.pikastech.local/config-sha256": k8sLabelHash(configSha256) };
|
|
const templateAnnotations = { "hwlab.pikastech.local/config-sha256": configSha256 };
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "ConfigMap",
|
|
metadata: {
|
|
name: configName,
|
|
namespace,
|
|
labels: renderedLabels
|
|
},
|
|
data: {
|
|
"frpc.toml": configText
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "apps/v1",
|
|
kind: "Deployment",
|
|
metadata: {
|
|
name: deploymentName,
|
|
namespace,
|
|
labels: renderedLabels
|
|
},
|
|
spec: {
|
|
replicas: 1,
|
|
selector: { matchLabels: { "app.kubernetes.io/name": deploymentName } },
|
|
template: {
|
|
metadata: { labels: templateLabels, annotations: templateAnnotations },
|
|
spec: {
|
|
...podNetwork,
|
|
containers: [{
|
|
name: "frpc",
|
|
image: "fatedier/frpc:v0.68.1",
|
|
imagePullPolicy: "IfNotPresent",
|
|
...(frpConfig.authSecretName && frpConfig.authSecretKey ? { env: [{ name: "FRPC_AUTH_TOKEN", valueFrom: { secretKeyRef: { name: frpConfig.authSecretName, key: frpConfig.authSecretKey } } }] } : {}),
|
|
args: ["-c", "/etc/frp/frpc.toml"],
|
|
volumeMounts: [{ name: "config", mountPath: "/etc/frp", readOnly: true }]
|
|
}],
|
|
volumes: [{ name: "config", configMap: { name: configName } }]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function deepSeekProxyManifest({ profile = "dev", source, sourceBranch = defaultBranch, sourceRepo = defaultSourceRepo, gitReadUrl, deploy = null, registryPrefix = defaultRegistryPrefix, catalog = null, useDeployImages = false, metricsSidecarSha256 = null } = {}) {
|
|
const namespace = namespaceNameForProfile(profile, deploy);
|
|
const bridgeServiceId = "hwlab-cloud-api";
|
|
const runtimeLane = isRuntimeLane(profile);
|
|
const digestPin = runtimeLane;
|
|
const envReuseServiceIds = runtimeLane ? envReuseServiceIdsForLane(deploy, profile) : null;
|
|
const bridgeImage = runtimeImageForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
|
const bridgeCommit = runtimeCommitForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
|
const bridgeImageTag = runtimeImageTagForService({ catalog, deploy, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages, digestPin, envReuseServiceIds });
|
|
const bridgeBootMetadata = runtimeLane && v02EnvReuseEnabled(catalog, bridgeServiceId, envReuseServiceIds)
|
|
? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.[profile]?.sourceRepo || sourceRepo, registryPrefix }, catalog, deployService: deployServiceForBoot(deploy, bridgeServiceId, profile), serviceId: bridgeServiceId, source })
|
|
: null;
|
|
const labels = {
|
|
"app.kubernetes.io/name": "hwlab-deepseek-proxy",
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/environment": runtimeLabelForProfile(profile),
|
|
"hwlab.pikastech.local/gitops-target": gitopsTargetForProfile(profile),
|
|
"hwlab.pikastech.local/service-id": "hwlab-deepseek-proxy",
|
|
"hwlab.pikastech.local/source-commit": source.full
|
|
};
|
|
if (runtimeLane) Object.assign(labels, v02MetricsLabels("hwlab-deepseek-proxy"));
|
|
const templateLabels = {
|
|
...labels,
|
|
"hwlab.pikastech.local/source-commit": bridgeCommit,
|
|
"hwlab.pikastech.local/bridge-source-commit": bridgeCommit
|
|
};
|
|
const templateAnnotations = {
|
|
"hwlab.pikastech.local/bridge-service-id": bridgeServiceId,
|
|
"hwlab.pikastech.local/source-commit": bridgeCommit,
|
|
"hwlab.pikastech.local/bridge-source-commit": bridgeCommit,
|
|
"hwlab.pikastech.local/bridge-image-tag": bridgeImageTag,
|
|
"hwlab.pikastech.local/bridge-image": bridgeImage,
|
|
...v02MetricsSidecarAnnotations(runtimeLane ? metricsSidecarSha256 : null)
|
|
};
|
|
const responsesBridgeContainer = {
|
|
name: "responses-bridge",
|
|
image: bridgeImage,
|
|
imagePullPolicy: "IfNotPresent",
|
|
command: ["/usr/local/bin/bun", "run", "/app/cmd/hwlab-deepseek-responses-bridge/main.ts"],
|
|
env: [
|
|
{ name: "PORT", value: "4000" },
|
|
{ name: "HWLAB_COMMIT_ID", value: bridgeCommit },
|
|
{ name: "HWLAB_IMAGE", value: bridgeImage },
|
|
{ name: "HWLAB_IMAGE_TAG", value: bridgeImageTag },
|
|
{ name: "HWLAB_DEEPSEEK_BRIDGE_UPSTREAM", value: "http://127.0.0.1:4001" },
|
|
{ name: "HWLAB_DEEPSEEK_BRIDGE_MODEL", value: deepSeekProfileModel }
|
|
],
|
|
ports: [{ name: "http", containerPort: 4000 }],
|
|
readinessProbe: { httpGet: { path: "/health/readiness", port: "http" }, initialDelaySeconds: 10, periodSeconds: 10 },
|
|
livenessProbe: { httpGet: { path: "/health/liveliness", port: "http" }, initialDelaySeconds: 20, periodSeconds: 20 }
|
|
};
|
|
if (bridgeBootMetadata) {
|
|
applyEnvReuseBootEnv(responsesBridgeContainer, bridgeBootMetadata, { sourceBranch, gitReadUrl, bootSh: "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh" });
|
|
Object.assign(templateAnnotations, {
|
|
"hwlab.pikastech.local/bridge-runtime-mode": bridgeBootMetadata.runtimeMode,
|
|
"hwlab.pikastech.local/bridge-boot-repo": bridgeBootMetadata.bootRepo,
|
|
"hwlab.pikastech.local/bridge-boot-commit": bridgeBootMetadata.bootCommit,
|
|
"hwlab.pikastech.local/bridge-boot-sh": "deploy/runtime/boot/hwlab-deepseek-responses-bridge.sh",
|
|
"hwlab.pikastech.local/bridge-environment-digest": bridgeBootMetadata.environmentDigest ?? "not_published"
|
|
});
|
|
Object.assign(templateLabels, {
|
|
"hwlab.pikastech.local/bridge-runtime-mode": bridgeBootMetadata.runtimeMode,
|
|
"hwlab.pikastech.local/bridge-boot-commit": bridgeBootMetadata.bootCommit
|
|
});
|
|
}
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "ConfigMap",
|
|
metadata: { name: "hwlab-deepseek-proxy-config", namespace, labels },
|
|
data: {
|
|
"render-config.sh": moonBridgeConfigRenderScript()
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "apps/v1",
|
|
kind: "Deployment",
|
|
metadata: { name: "hwlab-deepseek-proxy", namespace, labels, annotations: { "hwlab.pikastech.local/moonbridge-source-ref": moonBridgeSourceRef, ...templateAnnotations } },
|
|
spec: {
|
|
replicas: 1,
|
|
selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" } },
|
|
template: {
|
|
metadata: { labels: templateLabels, annotations: templateAnnotations },
|
|
spec: {
|
|
initContainers: [{
|
|
name: "moonbridge-config",
|
|
image: bridgeImage,
|
|
imagePullPolicy: "IfNotPresent",
|
|
command: ["/bin/sh", "-eu", "/scripts/render-config.sh"],
|
|
env: [
|
|
{ name: "DEEPSEEK_API_KEY", valueFrom: { secretKeyRef: { name: secretNameForProfile("hwlab-code-agent-provider", profile), key: "openai-api-key", optional: true } } },
|
|
{ name: "OPENCODE_API_KEY", valueFrom: { secretKeyRef: { name: secretNameForProfile("hwlab-code-agent-provider", profile), key: "opencode-api-key", optional: true } } }
|
|
],
|
|
volumeMounts: [
|
|
{ name: "moonbridge-scripts", mountPath: "/scripts", readOnly: true },
|
|
{ name: "moonbridge-config", mountPath: "/config" }
|
|
]
|
|
}],
|
|
containers: [responsesBridgeContainer, {
|
|
name: "moonbridge",
|
|
image: moonBridgeImage,
|
|
imagePullPolicy: "IfNotPresent",
|
|
args: ["-config", "/config/config.yml"],
|
|
ports: [{ name: "moonbridge-http", containerPort: 4001 }],
|
|
readinessProbe: { httpGet: { path: "/v1/models", port: "moonbridge-http" }, initialDelaySeconds: 10, periodSeconds: 10 },
|
|
livenessProbe: { httpGet: { path: "/v1/models", port: "moonbridge-http" }, initialDelaySeconds: 20, periodSeconds: 20 },
|
|
volumeMounts: [
|
|
{ name: "moonbridge-config", mountPath: "/config", readOnly: true },
|
|
{ name: "moonbridge-data", mountPath: "/data" }
|
|
]
|
|
}, ...(runtimeLane ? [v02MetricsSidecarContainer({ deploy, serviceId: "hwlab-deepseek-proxy", namespace, gitopsTarget: gitopsTargetForProfile(profile) })] : [])],
|
|
volumes: [
|
|
{ name: "moonbridge-scripts", configMap: { name: "hwlab-deepseek-proxy-config" } },
|
|
{ name: "moonbridge-config", emptyDir: {} },
|
|
{ name: "moonbridge-data", emptyDir: {} },
|
|
...(runtimeLane ? [v02MetricsSidecarVolume(profile)] : [])
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "Service",
|
|
metadata: { name: "hwlab-deepseek-proxy", namespace, labels },
|
|
spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": "hwlab-deepseek-proxy" }, ports: [{ name: "http", port: 4000, targetPort: "http" }, ...(runtimeLane ? [{ name: "metrics", port: 9100, targetPort: "metrics" }] : [])] }
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function moonBridgeConfigRenderScript() {
|
|
return `#!/bin/sh
|
|
if [ -z "\${DEEPSEEK_API_KEY:-}" ] && [ -z "\${OPENCODE_API_KEY:-}" ]; then
|
|
echo "at least one of DEEPSEEK_API_KEY or OPENCODE_API_KEY is required" >&2
|
|
exit 1
|
|
fi
|
|
cat > /config/config.yml <<EOF
|
|
mode: "Transform"
|
|
log:
|
|
level: "info"
|
|
format: "text"
|
|
server:
|
|
addr: "0.0.0.0:4001"
|
|
persistence:
|
|
active_provider: db_sqlite
|
|
extensions:
|
|
db_sqlite:
|
|
enabled: true
|
|
config:
|
|
path: /data/moonbridge.db
|
|
wal: true
|
|
busy_timeout_ms: 5000
|
|
max_open_conns: 1
|
|
metrics:
|
|
enabled: true
|
|
config:
|
|
default_limit: 100
|
|
max_limit: 1000
|
|
deepseek_v4:
|
|
config:
|
|
reinforce_instructions: true
|
|
reinforce_prompt: "[System Reminder]: Please pay close attention to the system instructions, AGENTS.md files, and any other context provided. Follow them carefully and completely in your response.\\n[User]:"
|
|
cache:
|
|
mode: "explicit"
|
|
ttl: "5m"
|
|
prompt_caching: true
|
|
automatic_prompt_cache: false
|
|
explicit_cache_breakpoints: true
|
|
allow_retention_downgrade: false
|
|
max_breakpoints: 4
|
|
min_cache_tokens: 1024
|
|
expected_reuse: 2
|
|
minimum_value_score: 2048
|
|
min_breakpoint_tokens: 1024
|
|
defaults:
|
|
model: "${deepSeekProfileModel}"
|
|
max_tokens: 8192
|
|
trace:
|
|
enabled: false
|
|
models:
|
|
${deepSeekProfileModel}:
|
|
context_window: 128000
|
|
max_output_tokens: 8192
|
|
display_name: "${deepSeekProfileModel}"
|
|
description: "DeepSeek via Moon Bridge"
|
|
default_reasoning_level: null
|
|
supported_reasoning_levels: []
|
|
extensions:
|
|
deepseek_v4:
|
|
enabled: true
|
|
deepseek-v4-flash:
|
|
context_window: 1000000
|
|
max_output_tokens: 384000
|
|
display_name: "DeepSeek V4 Flash via OpenCode Zen Go"
|
|
description: "DeepSeek V4 Flash through OpenCode Zen Go Chat Completions via Moon Bridge"
|
|
default_reasoning_level: "xhigh"
|
|
supported_reasoning_levels:
|
|
- effort: "high"
|
|
description: "High reasoning effort"
|
|
- effort: "xhigh"
|
|
description: "Maximum reasoning effort"
|
|
supports_reasoning_summaries: true
|
|
default_reasoning_summary: "auto"
|
|
extensions:
|
|
deepseek_v4:
|
|
enabled: true
|
|
providers:
|
|
deepseek:
|
|
base_url: "https://api.deepseek.com/anthropic"
|
|
api_key: "\${DEEPSEEK_API_KEY}"
|
|
version: "2023-06-01"
|
|
user_agent: "moonbridge/1.0"
|
|
offers:
|
|
- model: ${deepSeekProfileModel}
|
|
opencode:
|
|
base_url: "https://opencode.ai/zen/go/v1"
|
|
api_key: "\${OPENCODE_API_KEY:-}"
|
|
protocol: "openai-chat"
|
|
user_agent: "moonbridge-dsflash-go/1.0"
|
|
web_search:
|
|
support: "disabled"
|
|
offers:
|
|
- model: deepseek-v4-flash
|
|
routes:
|
|
${deepSeekProfileModel}:
|
|
model: ${deepSeekProfileModel}
|
|
provider: deepseek
|
|
deepseek-v4-flash:
|
|
model: deepseek-v4-flash
|
|
provider: opencode
|
|
EOF
|
|
`;
|
|
}
|
|
|
|
function deviceAgent71FreqServerScript() {
|
|
return [
|
|
"import http from \"node:http\";",
|
|
"",
|
|
"const deviceId = process.env.DEVICE_ID || \"71-freq\";",
|
|
"const port = Number(process.env.PORT || 7601);",
|
|
"const cloudApiUrl = (process.env.HWLAB_CLOUD_API_URL || \"http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667\").replace(/\\/+$/u, \"\");",
|
|
"const gatewaySessionId = process.env.HWLAB_GATEWAY_SESSION_ID || \"gws_d601_win_71_freq\";",
|
|
"const resourceId = process.env.HWLAB_GATEWAY_RESOURCE_ID || \"res_d601_windows_host\";",
|
|
"const capabilityId = process.env.HWLAB_GATEWAY_CAPABILITY_ID || \"cap_d601_windows_cmd_exec\";",
|
|
"const workspaceRoot = process.env.DEVICE_WORKSPACE_ROOT || \"F:\\\\Work\\\\ConStart\";",
|
|
"",
|
|
"function sendJson(res, statusCode, payload) {",
|
|
" const body = JSON.stringify(payload, null, 2);",
|
|
" res.writeHead(statusCode, { \"content-type\": \"application/json; charset=utf-8\" });",
|
|
" res.end(body);",
|
|
"}",
|
|
"",
|
|
"function readBody(req) {",
|
|
" return new Promise((resolve, reject) => {",
|
|
" let body = \"\";",
|
|
" req.setEncoding(\"utf8\");",
|
|
" req.on(\"data\", (chunk) => {",
|
|
" body += chunk;",
|
|
" if (body.length > 1024 * 1024) req.destroy(new Error(\"request body too large\"));",
|
|
" });",
|
|
" req.on(\"end\", () => resolve(body ? JSON.parse(body) : {}));",
|
|
" req.on(\"error\", reject);",
|
|
" });",
|
|
"}",
|
|
"",
|
|
"function postJson(url, payload, timeoutMs) {",
|
|
" return new Promise((resolve, reject) => {",
|
|
" const parsed = new URL(url);",
|
|
" const body = JSON.stringify(payload);",
|
|
" const request = http.request({",
|
|
" hostname: parsed.hostname,",
|
|
" port: parsed.port || 80,",
|
|
" path: parsed.pathname + parsed.search,",
|
|
" method: \"POST\",",
|
|
" headers: { \"content-type\": \"application/json\", \"content-length\": Buffer.byteLength(body) },",
|
|
" timeout: timeoutMs",
|
|
" }, (response) => {",
|
|
" let responseBody = \"\";",
|
|
" response.setEncoding(\"utf8\");",
|
|
" response.on(\"data\", (chunk) => { responseBody += chunk; });",
|
|
" response.on(\"end\", () => {",
|
|
" try {",
|
|
" const parsedBody = responseBody ? JSON.parse(responseBody) : null;",
|
|
" resolve({ statusCode: response.statusCode, body: parsedBody });",
|
|
" } catch (error) {",
|
|
" reject(error);",
|
|
" }",
|
|
" });",
|
|
" });",
|
|
" request.on(\"timeout\", () => request.destroy(new Error(\"request timeout after \" + timeoutMs + \"ms\")));",
|
|
" request.on(\"error\", reject);",
|
|
" request.end(body);",
|
|
" });",
|
|
"}",
|
|
"",
|
|
"function gatewayPayload(input) {",
|
|
" const traceId = input.traceId || \"trc_device_agent_71_freq_\" + Date.now();",
|
|
" return {",
|
|
" jsonrpc: \"2.0\",",
|
|
" id: input.id || \"req_device_agent_71_freq_\" + Date.now(),",
|
|
" method: \"hardware.invoke.shell\",",
|
|
" meta: { serviceId: \"hwlab-cloud-api\", actorId: \"device-agent-71-freq\", environment: \"dev\", traceId, deviceId, workspaceRoot },",
|
|
" params: {",
|
|
" gatewaySessionId,",
|
|
" resourceId,",
|
|
" capabilityId,",
|
|
" input: {",
|
|
" command: input.command,",
|
|
" cwd: input.cwd || workspaceRoot,",
|
|
" timeoutMs: input.timeoutMs || 30000",
|
|
" }",
|
|
" }",
|
|
" };",
|
|
"}",
|
|
"",
|
|
"const server = http.createServer(async (req, res) => {",
|
|
" try {",
|
|
" const url = new URL(req.url || \"/\", \"http://device-agent-71-freq\");",
|
|
" if (req.method === \"GET\" && url.pathname === \"/health\") {",
|
|
" return sendJson(res, 200, { ok: true, deviceId, workspaceRoot, gatewaySessionId, resourceId, capabilityId, cloudApiUrl });",
|
|
" }",
|
|
" if (req.method === \"GET\" && url.pathname === \"/skills\") {",
|
|
" return sendJson(res, 200, { ok: true, deviceId, skills: [{ name: \"cmd\", description: \"Run a bounded Windows cmd command through hwlab-gateway.\" }] });",
|
|
" }",
|
|
" if (req.method === \"POST\" && url.pathname === \"/run\") {",
|
|
" const input = await readBody(req);",
|
|
" if (!input.command || typeof input.command !== \"string\") return sendJson(res, 400, { ok: false, error: \"command is required\" });",
|
|
" const timeoutMs = Number(input.timeoutMs || 30000);",
|
|
" const upstream = await postJson(cloudApiUrl + \"/json-rpc\", gatewayPayload({ ...input, timeoutMs }), timeoutMs + 5000);",
|
|
" return sendJson(res, upstream.statusCode || 502, { ok: upstream.statusCode >= 200 && upstream.statusCode < 300, deviceId, gatewaySessionId, upstream: upstream.body });",
|
|
" }",
|
|
" return sendJson(res, 404, { ok: false, error: \"not found\" });",
|
|
" } catch (error) {",
|
|
" return sendJson(res, 500, { ok: false, error: error.message });",
|
|
" }",
|
|
"});",
|
|
"",
|
|
"server.listen(port, \"0.0.0.0\", () => {",
|
|
" console.log(JSON.stringify({ ok: true, service: \"device-agent-71-freq\", port, deviceId, workspaceRoot, cloudApiUrl }));",
|
|
"});"
|
|
].join("\n") + "\n";
|
|
}
|
|
|
|
function deviceAgent71FreqManifest({ profile = "dev", source, registryPrefix, catalog = null, useDeployImages = false }) {
|
|
assert.equal(profile, "dev", "71-freq device-agent is dev-only");
|
|
const namespace = namespaceNameForProfile(profile);
|
|
const name = "device-agent-71-freq";
|
|
const bridgeServiceId = "hwlab-cloud-api";
|
|
const bridgeImage = runtimeImageForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages });
|
|
const bridgeCommit = runtimeCommitForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages });
|
|
const bridgeImageTag = runtimeImageTagForService({ catalog, serviceId: bridgeServiceId, source, registryPrefix, useDeployImages });
|
|
const labels = {
|
|
"app.kubernetes.io/name": name,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/component": "device-agent",
|
|
"hwlab.pikastech.local/device-id": "71-freq",
|
|
"hwlab.pikastech.local/environment": runtimeLabelForProfile(profile),
|
|
"hwlab.pikastech.local/gitops-target": "node",
|
|
"hwlab.pikastech.local/profile": runtimeLabelForProfile(profile),
|
|
"hwlab.pikastech.local/service-id": name,
|
|
"hwlab.pikastech.local/source-commit": source.full
|
|
};
|
|
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
|
|
const selector = { "app.kubernetes.io/name": name };
|
|
const script = deviceAgent71FreqServerScript();
|
|
const scriptSha256 = createHash("sha256").update(script).digest("hex");
|
|
const templateLabels = { ...labels, ...selector, "hwlab.pikastech.local/source-commit": bridgeCommit };
|
|
const templateAnnotations = {
|
|
...annotations,
|
|
"hwlab.pikastech.local/script-sha256": scriptSha256,
|
|
"hwlab.pikastech.local/bridge-service-id": bridgeServiceId,
|
|
"hwlab.pikastech.local/bridge-source-commit": bridgeCommit,
|
|
"hwlab.pikastech.local/bridge-image-tag": bridgeImageTag,
|
|
"hwlab.pikastech.local/bridge-image": bridgeImage
|
|
};
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "ConfigMap",
|
|
metadata: { name: `${name}-script`, namespace, labels, annotations: templateAnnotations },
|
|
data: { "server.mjs": script }
|
|
},
|
|
{
|
|
apiVersion: "apps/v1",
|
|
kind: "Deployment",
|
|
metadata: { name, namespace, labels, annotations },
|
|
spec: {
|
|
replicas: 1,
|
|
selector: { matchLabels: selector },
|
|
template: {
|
|
metadata: { labels: templateLabels, annotations: templateAnnotations },
|
|
spec: {
|
|
containers: [{
|
|
name: "device-agent",
|
|
image: bridgeImage,
|
|
imagePullPolicy: "IfNotPresent",
|
|
command: ["node", "/opt/device-agent/server.mjs"],
|
|
env: [
|
|
{ name: "PORT", value: "7601" },
|
|
{ name: "HWLAB_COMMIT_ID", value: bridgeCommit },
|
|
{ name: "HWLAB_IMAGE", value: bridgeImage },
|
|
{ name: "HWLAB_IMAGE_TAG", value: bridgeImageTag },
|
|
{ name: "DEVICE_ID", value: "71-freq" },
|
|
{ name: "DEVICE_WORKSPACE_ROOT", value: "F:\\Work\\ConStart" },
|
|
{ name: "HWLAB_CLOUD_API_URL", value: `http://hwlab-cloud-api.${namespace}.svc.cluster.local:6667` },
|
|
{ name: "HWLAB_GATEWAY_SESSION_ID", value: "gws_d601_win_71_freq" },
|
|
{ name: "HWLAB_GATEWAY_RESOURCE_ID", value: "res_d601_windows_host" },
|
|
{ name: "HWLAB_GATEWAY_CAPABILITY_ID", value: "cap_d601_windows_cmd_exec" }
|
|
],
|
|
ports: [{ name: "http", containerPort: 7601 }],
|
|
readinessProbe: { httpGet: { path: "/health", port: "http" }, initialDelaySeconds: 3, periodSeconds: 10 },
|
|
livenessProbe: { httpGet: { path: "/health", port: "http" }, initialDelaySeconds: 10, periodSeconds: 20 },
|
|
resources: { requests: { cpu: "10m", memory: "64Mi" }, limits: { cpu: "200m", memory: "256Mi" } },
|
|
volumeMounts: [
|
|
{ name: "script", mountPath: "/opt/device-agent", readOnly: true },
|
|
{ name: "hwlab-code-agent-workspace", mountPath: "/workspace" }
|
|
]
|
|
}],
|
|
volumes: [
|
|
{ name: "script", configMap: { name: `${name}-script` } },
|
|
{ name: "hwlab-code-agent-workspace", persistentVolumeClaim: { claimName: "hwlab-code-agent-workspace" } }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "Service",
|
|
metadata: { name, namespace, labels, annotations },
|
|
spec: { type: "ClusterIP", selector, ports: [{ name: "http", port: 7601, targetPort: "http" }] }
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function runtimePostgresImageForProfile(deploy, profile) {
|
|
const postgres = runtimeLaneConfig(deploy, profile)?.runtimeStore?.postgres;
|
|
const image = postgres && typeof postgres === "object" && !Array.isArray(postgres) ? postgres.image : null;
|
|
return typeof image === "string" && image.trim().length > 0 ? image.trim() : "postgres:16-alpine";
|
|
}
|
|
|
|
function v02PostgresManifest({ profile = "v02", migrationSources, source, image = "postgres:16-alpine", deploy = null }) {
|
|
const namespace = namespaceNameForProfile(profile, deploy);
|
|
const name = `${namespace}-postgres`;
|
|
const dbName = `hwlab_${profile}`;
|
|
const labels = {
|
|
"app.kubernetes.io/name": name,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/environment": profile,
|
|
"hwlab.pikastech.local/gitops-target": profile,
|
|
"hwlab.pikastech.local/profile": profile,
|
|
"hwlab.pikastech.local/source-commit": source.full
|
|
};
|
|
assert.ok(Array.isArray(migrationSources) && migrationSources.length > 0, "runtime Postgres migration chain is required");
|
|
const migrationSql = migrationSources.map((migration) => migration.sql).join("\n");
|
|
const migrationData = Object.fromEntries(migrationSources.map((migration) => [path.basename(migration.path), migration.sql]));
|
|
const migrationSha256 = createHash("sha256").update(migrationSql).digest("hex");
|
|
const templateLabels = {
|
|
"app.kubernetes.io/name": name,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/environment": profile,
|
|
"hwlab.pikastech.local/gitops-target": profile,
|
|
"hwlab.pikastech.local/profile": profile,
|
|
"hwlab.pikastech.local/migration-sha256": k8sLabelHash(migrationSha256)
|
|
};
|
|
const templateAnnotations = { "hwlab.pikastech.local/migration-sha256": migrationSha256 };
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "ConfigMap",
|
|
metadata: { name: `${name}-init`, namespace, labels },
|
|
data: migrationData
|
|
},
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "Service",
|
|
metadata: { name, namespace, labels },
|
|
spec: { type: "ClusterIP", selector: { "app.kubernetes.io/name": name }, ports: [{ name: "postgres", port: 5432, targetPort: "postgres" }] }
|
|
},
|
|
{
|
|
apiVersion: "apps/v1",
|
|
kind: "StatefulSet",
|
|
metadata: { name, namespace, labels },
|
|
spec: {
|
|
serviceName: name,
|
|
replicas: 1,
|
|
selector: { matchLabels: { "app.kubernetes.io/name": name } },
|
|
template: {
|
|
metadata: { labels: templateLabels, annotations: templateAnnotations },
|
|
spec: {
|
|
containers: [{
|
|
name: "postgres",
|
|
image,
|
|
imagePullPolicy: "IfNotPresent",
|
|
env: [
|
|
{ name: "POSTGRES_DB", value: dbName },
|
|
{ name: "POSTGRES_USER", value: dbName },
|
|
{ name: "POSTGRES_PASSWORD", valueFrom: { secretKeyRef: { name, key: "POSTGRES_PASSWORD" } } }
|
|
],
|
|
ports: [{ name: "postgres", containerPort: 5432 }],
|
|
readinessProbe: { tcpSocket: { port: "postgres" }, initialDelaySeconds: 5, periodSeconds: 10 },
|
|
livenessProbe: { tcpSocket: { port: "postgres" }, initialDelaySeconds: 30, periodSeconds: 20 },
|
|
volumeMounts: [
|
|
{ name: "data", mountPath: "/var/lib/postgresql/data" },
|
|
{ name: "init", mountPath: "/docker-entrypoint-initdb.d", readOnly: true }
|
|
]
|
|
}],
|
|
volumes: [{ name: "init", configMap: { name: `${name}-init` } }]
|
|
}
|
|
},
|
|
volumeClaimTemplates: [{
|
|
metadata: { name: "data" },
|
|
spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } }
|
|
}]
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function externalPostgresConfigForLane(deploy, profile, args = {}) {
|
|
if (!isRuntimeLane(profile)) return null;
|
|
const config = deploy?.lanes?.[profile]?.externalPostgres;
|
|
if (!config || config.enabled !== true) return null;
|
|
const nodeId = effectiveSecretPlaneNodeId(args);
|
|
const nodeConfig = nodeId ? config?.nodes?.[nodeId] : null;
|
|
const effective = nodeConfig && typeof nodeConfig === "object" && !Array.isArray(nodeConfig) ? { ...config, ...nodeConfig } : config;
|
|
assert.ok(typeof effective.serviceName === "string" && effective.serviceName.length > 0, `deploy.lanes.${profile}.externalPostgres.serviceName is required`);
|
|
assert.ok(typeof effective.endpointAddress === "string" && effective.endpointAddress.length > 0, `deploy.lanes.${profile}.externalPostgres.endpointAddress is required`);
|
|
const port = Number(effective.port ?? 5432);
|
|
assert.ok(Number.isInteger(port) && port > 0 && port <= 65535, `deploy.lanes.${profile}.externalPostgres.port must be a valid TCP port`);
|
|
return { serviceName: effective.serviceName, endpointAddress: effective.endpointAddress, port };
|
|
}
|
|
|
|
function externalPostgresManifest({ profile = "v03", config, source, deploy = null }) {
|
|
const namespace = namespaceNameForProfile(profile, deploy);
|
|
const labels = {
|
|
"app.kubernetes.io/name": config.serviceName,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/component": "platform-db-bridge",
|
|
"hwlab.pikastech.local/environment": profile,
|
|
"hwlab.pikastech.local/gitops-target": profile,
|
|
"hwlab.pikastech.local/profile": profile,
|
|
"hwlab.pikastech.local/source-commit": source.full
|
|
};
|
|
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "Service",
|
|
metadata: { name: config.serviceName, namespace, labels, annotations },
|
|
spec: { type: "ClusterIP", ports: [{ name: "postgres", port: config.port, targetPort: config.port, protocol: "TCP" }] }
|
|
},
|
|
{
|
|
apiVersion: "discovery.k8s.io/v1",
|
|
kind: "EndpointSlice",
|
|
metadata: { name: `${config.serviceName}-host`, namespace, labels: { ...labels, "kubernetes.io/service-name": config.serviceName }, annotations },
|
|
addressType: "IPv4",
|
|
ports: [{ name: "postgres", port: config.port, protocol: "TCP" }],
|
|
endpoints: [{ addresses: [config.endpointAddress], conditions: { ready: true } }]
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function v02OpenFgaManifest({ profile = "v02", source, deploy = null }) {
|
|
const namespace = namespaceNameForProfile(profile, deploy);
|
|
const name = "hwlab-openfga";
|
|
const image = "127.0.0.1:5000/hwlab/openfga:v1.17.0";
|
|
const labels = {
|
|
"app.kubernetes.io/name": name,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/component": "authorization",
|
|
"hwlab.pikastech.local/environment": profile,
|
|
"hwlab.pikastech.local/gitops-target": profile,
|
|
"hwlab.pikastech.local/profile": profile,
|
|
"hwlab.pikastech.local/service-id": name,
|
|
"hwlab.pikastech.local/source-commit": source.full
|
|
};
|
|
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
|
|
const selector = { "app.kubernetes.io/name": name };
|
|
const env = [
|
|
{ name: "OPENFGA_DATASTORE_ENGINE", value: "postgres" },
|
|
{ name: "OPENFGA_DATASTORE_URI", valueFrom: { secretKeyRef: { name: `hwlab-${profile}-openfga`, key: "datastore-uri" } } },
|
|
{ name: "OPENFGA_AUTHN_METHOD", value: "preshared" },
|
|
{ name: "OPENFGA_AUTHN_PRESHARED_KEYS", valueFrom: { secretKeyRef: { name: `hwlab-${profile}-openfga`, key: "authn-preshared-key" } } },
|
|
{ name: "OPENFGA_PLAYGROUND_ENABLED", value: "false" },
|
|
{ name: "OPENFGA_HTTP_ADDR", value: "0.0.0.0:8080" },
|
|
{ name: "OPENFGA_GRPC_ADDR", value: "0.0.0.0:8081" }
|
|
];
|
|
const templateLabels = { ...labels, ...selector };
|
|
const runtimeAnnotations = { ...annotations, "argocd.argoproj.io/sync-wave": "2" };
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "batch/v1",
|
|
kind: "Job",
|
|
metadata: {
|
|
name: `${name}-migrate`,
|
|
namespace,
|
|
labels,
|
|
annotations: {
|
|
...annotations,
|
|
"argocd.argoproj.io/hook": "Sync",
|
|
"argocd.argoproj.io/sync-wave": "1",
|
|
"argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded"
|
|
}
|
|
},
|
|
spec: {
|
|
backoffLimit: 3,
|
|
template: {
|
|
metadata: { labels: templateLabels, annotations },
|
|
spec: {
|
|
restartPolicy: "OnFailure",
|
|
containers: [{ name: "openfga-migrate", image, imagePullPolicy: "IfNotPresent", args: ["migrate"], env }]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "apps/v1",
|
|
kind: "Deployment",
|
|
metadata: { name, namespace, labels, annotations: runtimeAnnotations },
|
|
spec: {
|
|
replicas: 1,
|
|
selector: { matchLabels: selector },
|
|
template: {
|
|
metadata: { labels: templateLabels, annotations },
|
|
spec: {
|
|
containers: [{
|
|
name: "openfga",
|
|
image,
|
|
imagePullPolicy: "IfNotPresent",
|
|
args: ["run"],
|
|
env,
|
|
ports: [{ name: "http", containerPort: 8080 }, { name: "grpc", containerPort: 8081 }],
|
|
readinessProbe: { httpGet: { path: "/healthz", port: "http" }, initialDelaySeconds: 3, periodSeconds: 10 },
|
|
livenessProbe: { httpGet: { path: "/healthz", port: "http" }, initialDelaySeconds: 15, periodSeconds: 20 },
|
|
resources: { requests: { cpu: "50m", memory: "128Mi" }, limits: { cpu: "500m", memory: "512Mi" } }
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "Service",
|
|
metadata: { name, namespace, labels, annotations: runtimeAnnotations },
|
|
spec: { type: "ClusterIP", selector, ports: [{ name: "http", port: 8080, targetPort: "http" }, { name: "grpc", port: 8081, targetPort: "grpc" }] }
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function workbenchRuntimeRedisConf(config) {
|
|
return [
|
|
"save \"\"",
|
|
"appendonly no",
|
|
`maxmemory ${config.memoryPolicy.maxMemory}`,
|
|
`maxmemory-policy ${config.memoryPolicy.eviction}`,
|
|
""
|
|
].join("\n");
|
|
}
|
|
|
|
function workbenchRuntimeRedisManifest({ profile = "v03", config, source }) {
|
|
const namespace = config.namespace;
|
|
const name = config.serviceName;
|
|
const port = config.port;
|
|
const selector = { "app.kubernetes.io/name": name };
|
|
const labels = {
|
|
...selector,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/cache-role": "workbench-derived-read",
|
|
"hwlab.pikastech.local/component": "workbench-runtime-cache",
|
|
"hwlab.pikastech.local/environment": profile,
|
|
"hwlab.pikastech.local/gitops-target": profile,
|
|
"hwlab.pikastech.local/profile": profile,
|
|
"hwlab.pikastech.local/service-id": name,
|
|
"hwlab.pikastech.local/source-commit": source.full
|
|
};
|
|
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs" };
|
|
const runtimeAnnotations = { ...annotations, "argocd.argoproj.io/sync-wave": "2" };
|
|
const templateLabels = { ...labels, ...selector };
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "ConfigMap",
|
|
metadata: { name: `${name}-config`, namespace, labels, annotations: runtimeAnnotations },
|
|
data: { "redis.conf": workbenchRuntimeRedisConf(config) }
|
|
},
|
|
{
|
|
apiVersion: "apps/v1",
|
|
kind: "Deployment",
|
|
metadata: { name, namespace, labels, annotations: runtimeAnnotations },
|
|
spec: {
|
|
replicas: 1,
|
|
selector: { matchLabels: selector },
|
|
template: {
|
|
metadata: { labels: templateLabels, annotations: runtimeAnnotations },
|
|
spec: {
|
|
containers: [{
|
|
name: "redis",
|
|
image: config.image,
|
|
imagePullPolicy: "IfNotPresent",
|
|
args: ["redis-server", "/usr/local/etc/redis/redis.conf"],
|
|
ports: [{ name: "redis", containerPort: port }],
|
|
readinessProbe: { exec: { command: ["redis-cli", "-p", String(port), "ping"] }, initialDelaySeconds: 3, periodSeconds: 10, timeoutSeconds: 2, failureThreshold: 3 },
|
|
livenessProbe: { exec: { command: ["redis-cli", "-p", String(port), "ping"] }, initialDelaySeconds: 15, periodSeconds: 20, timeoutSeconds: 3, failureThreshold: 3 },
|
|
resources: cloneJson(config.resources),
|
|
volumeMounts: [{ name: "config", mountPath: "/usr/local/etc/redis", readOnly: true }]
|
|
}],
|
|
volumes: [{ name: "config", configMap: { name: `${name}-config` } }]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "Service",
|
|
metadata: { name, namespace, labels, annotations: runtimeAnnotations },
|
|
spec: { type: "ClusterIP", selector, ports: [{ name: "redis", port, targetPort: "redis", protocol: "TCP" }] }
|
|
},
|
|
{
|
|
apiVersion: "networking.k8s.io/v1",
|
|
kind: "NetworkPolicy",
|
|
metadata: { name: `${name}-ingress`, namespace, labels, annotations: runtimeAnnotations },
|
|
spec: {
|
|
podSelector: { matchLabels: selector },
|
|
policyTypes: ["Ingress"],
|
|
ingress: [{
|
|
from: [{ podSelector: { matchLabels: { "app.kubernetes.io/name": "hwlab-workbench-runtime" } } }],
|
|
ports: [{ protocol: "TCP", port }]
|
|
}]
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
function runtimeConfigMapsForProfile(deploy, profile) {
|
|
if (!isRuntimeLane(profile)) return [];
|
|
const configMaps = deploy?.lanes?.[profile]?.configMaps;
|
|
if (!Array.isArray(configMaps)) return [];
|
|
return configMaps
|
|
.map((configMap) => cloneJson(configMap))
|
|
.filter((configMap) => typeof configMap?.name === "string" && configMap.name.trim() && configMap.data && typeof configMap.data === "object" && !Array.isArray(configMap.data));
|
|
}
|
|
|
|
function runtimeConfigMapsManifest({ configMaps, namespace, labels, annotations }) {
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: configMaps.map((configMap) => ({
|
|
apiVersion: "v1",
|
|
kind: "ConfigMap",
|
|
metadata: {
|
|
name: configMap.name,
|
|
namespace,
|
|
labels: { ...labels, ...(configMap.labels ?? {}) },
|
|
annotations: { ...annotations, ...(configMap.annotations ?? {}) }
|
|
},
|
|
data: Object.fromEntries(Object.entries(configMap.data).map(([key, value]) => [key, String(value)]))
|
|
}))
|
|
};
|
|
}
|
|
|
|
async function runtimeSecretPlaneConfigForProfile(deploy, profile, args = {}) {
|
|
if (!isRuntimeLane(profile)) return null;
|
|
const laneConfig = deploy?.lanes?.[profile];
|
|
const secretPlane = laneConfig?.secretPlaneRef
|
|
? await readConfigRef(laneConfig.secretPlaneRef, `${profile}.secretPlaneRef`)
|
|
: laneConfig?.secretPlane;
|
|
if (!secretPlane || typeof secretPlane !== "object" || Array.isArray(secretPlane)) return null;
|
|
if (secretPlane.enabled !== true) return null;
|
|
if (!runtimeSecretPlaneEnabledForNode(secretPlane, args)) return null;
|
|
return cloneJson(secretPlane);
|
|
}
|
|
|
|
function runtimeSecretPlaneEnabledForNode(secretPlane, args = {}) {
|
|
const enabledOnNodes = normalizeSecretPlaneNodeList(secretPlane.enabledOnNodes, "secretPlane.enabledOnNodes");
|
|
if (enabledOnNodes.length === 0) return true;
|
|
const nodeId = effectiveSecretPlaneNodeId(args);
|
|
assert.ok(nodeId, "secretPlane.enabledOnNodes requires --node or a node-scoped --gitops-root");
|
|
return enabledOnNodes.includes(nodeId);
|
|
}
|
|
|
|
function normalizeSecretPlaneNodeList(value, label) {
|
|
if (value === undefined) return [];
|
|
assert.ok(Array.isArray(value), `${label} must be an array when set`);
|
|
return value.map((item, index) => {
|
|
assert.equal(typeof item, "string", `${label}[${index}] must be a string`);
|
|
const nodeId = item.trim().toUpperCase();
|
|
assert.ok(/^[A-Z0-9][A-Z0-9-]*$/u.test(nodeId), `${label}[${index}] must be a node id`);
|
|
return nodeId;
|
|
});
|
|
}
|
|
|
|
function requiredSecretPlaneString(value, label) {
|
|
assert.equal(typeof value, "string", `${label} must be a string`);
|
|
const trimmed = value.trim();
|
|
assert.ok(trimmed, `${label} must not be empty`);
|
|
return trimmed;
|
|
}
|
|
|
|
function runtimeSecretPlaneManifest({ config, namespace, labels, annotations }) {
|
|
const store = config?.store;
|
|
assert.ok(store && typeof store === "object" && !Array.isArray(store), "secretPlane.store must be an object");
|
|
assert.equal(store.kind, "ClusterSecretStore", "secretPlane.store.kind must be ClusterSecretStore for node-scoped v0.3");
|
|
assert.ok(typeof store.name === "string" && store.name.trim(), "secretPlane.store.name must be set");
|
|
const secrets = Array.isArray(config.secrets) ? config.secrets : [];
|
|
assert.ok(secrets.length > 0, "secretPlane.secrets must not be empty");
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: secrets.map((secret, index) => runtimeSecretPlaneExternalSecret({ config, secret, index, namespace, labels, annotations, store }))
|
|
};
|
|
}
|
|
|
|
function runtimeSecretPlaneExternalSecret({ config, secret, index, namespace, labels, annotations, store }) {
|
|
const externalSecretName = requiredSecretPlaneString(secret?.externalSecretName, `secretPlane.secrets[${index}].externalSecretName`);
|
|
const targetSecretName = requiredSecretPlaneString(secret?.targetSecretName, `secretPlane.secrets[${index}].targetSecretName`);
|
|
const data = Array.isArray(secret?.data) ? secret.data : [];
|
|
assert.ok(data.length > 0, `secretPlane.secrets[${index}].data must not be empty`);
|
|
const sourceRefs = data.map((item) => requiredSecretPlaneString(item?.remoteRef, `secretPlane.secrets[${index}].data.remoteRef`));
|
|
const issueRef = String(config.issue ?? "pikasTech/HWLAB#2234");
|
|
return {
|
|
apiVersion: "external-secrets.io/v1",
|
|
kind: "ExternalSecret",
|
|
metadata: {
|
|
name: externalSecretName,
|
|
namespace,
|
|
labels: {
|
|
...labels,
|
|
"app.kubernetes.io/name": externalSecretName,
|
|
"app.kubernetes.io/component": "external-secret",
|
|
"hwlab.pikastech.local/secret-plane": secretPlaneIssueLabelValue(issueRef)
|
|
},
|
|
annotations: {
|
|
...annotations,
|
|
"hwlab.pikastech.local/secret-plane-issue": issueRef,
|
|
"hwlab.pikastech.local/source-ref": sourceRefs.join(","),
|
|
"hwlab.pikastech.local/values-printed": "false"
|
|
}
|
|
},
|
|
spec: {
|
|
refreshInterval: requiredSecretPlaneString(config.refreshInterval, "secretPlane.refreshInterval"),
|
|
secretStoreRef: {
|
|
name: requiredSecretPlaneString(store.name, "secretPlane.store.name"),
|
|
kind: store.kind
|
|
},
|
|
target: {
|
|
name: targetSecretName,
|
|
creationPolicy: "Owner"
|
|
},
|
|
data: data.map((item, itemIndex) => ({
|
|
secretKey: requiredSecretPlaneString(item?.targetKey, `secretPlane.secrets[${index}].data[${itemIndex}].targetKey`),
|
|
remoteRef: {
|
|
key: requiredSecretPlaneString(item?.remoteRef, `secretPlane.secrets[${index}].data[${itemIndex}].remoteRef`),
|
|
property: requiredSecretPlaneString(item?.property, `secretPlane.secrets[${index}].data[${itemIndex}].property`)
|
|
}
|
|
}))
|
|
}
|
|
};
|
|
}
|
|
|
|
function secretPlaneIssueLabelValue(issueRef) {
|
|
const text = String(issueRef ?? "").trim();
|
|
const issueNumber = text.match(/#([0-9]+)$/u)?.[1];
|
|
const raw = issueNumber ? `issue-${issueNumber}` : text;
|
|
const normalized = raw.replace(/[^A-Za-z0-9_.-]+/gu, "-").replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/gu, "");
|
|
const label = normalized.slice(0, 63).replace(/[^A-Za-z0-9]+$/u, "");
|
|
assert.ok(label.length > 0, "secretPlane.issue must produce a Kubernetes label-safe value");
|
|
return label;
|
|
}
|
|
|
|
function opencodeRuntimeConfigForProfile(deploy, profile) {
|
|
const config = optionalObject(deploy?.lanes?.[profile]?.opencode);
|
|
const providerProfile = configString(config.providerProfile) || "dsflash-go";
|
|
const providerId = configString(config.providerId) || providerProfile;
|
|
const model = configString(config.model) || "deepseek-v4-flash";
|
|
const smallModel = configString(config.smallModel) || model;
|
|
const apiKeyEnv = configString(config.apiKeyEnv) || opencodeApiKeyEnvName(providerId);
|
|
const contextLimit = opencodePositiveInteger(config.contextLimit, 1000000, `deploy.lanes.${profile}.opencode.contextLimit`);
|
|
const outputLimit = opencodePositiveInteger(config.outputLimit, 384000, `deploy.lanes.${profile}.opencode.outputLimit`);
|
|
const reasoning = opencodeConfigBoolean(config.reasoning, false, `deploy.lanes.${profile}.opencode.reasoning`);
|
|
const toolCall = opencodeConfigBoolean(config.toolCall, true, `deploy.lanes.${profile}.opencode.toolCall`);
|
|
const upstreamBaseURL = configString(config.upstreamBaseURL) || configString(config.baseURL) || "https://opencode.ai/zen/go/v1";
|
|
const providerProxyPort = opencodePositiveInteger(config.providerProxyPort, 4097, `deploy.lanes.${profile}.opencode.providerProxyPort`);
|
|
const providerProxyBasePath = configString(config.providerProxyBasePath) || "/v1";
|
|
const providerProxyBaseURL = configString(config.providerProxyBaseURL) || `http://127.0.0.1:${providerProxyPort}${providerProxyBasePath}`;
|
|
return {
|
|
image: configString(config.image) || "ghcr.io/anomalyco/opencode:1.17.7",
|
|
providerProfile,
|
|
providerId,
|
|
displayName: configString(config.displayName) || `AgentRun ${providerProfile}`,
|
|
model,
|
|
smallModel,
|
|
baseURL: providerProxyBaseURL,
|
|
upstreamBaseURL,
|
|
apkProxyURL: configString(config.apkProxyURL) || "",
|
|
providerProxyPort,
|
|
providerProxyBasePath,
|
|
npm: configString(config.npm) || "@ai-sdk/openai-compatible",
|
|
apiKeyEnv,
|
|
apiKeySecretRef: configString(config.apiKeySecretRef) || "hwlab-code-agent-provider/opencode-api-key",
|
|
contextLimit,
|
|
outputLimit,
|
|
reasoning,
|
|
toolCall,
|
|
agentrunSecretNamespace: configString(config.agentrunSecretNamespace) || "agentrun-v02",
|
|
agentrunSecretName: configString(config.agentrunSecretName) || `agentrun-v01-provider-${providerProfile}`,
|
|
agentrunSecretKeys: Array.isArray(config.agentrunSecretKeys) && config.agentrunSecretKeys.length > 0
|
|
? config.agentrunSecretKeys.map(configString).filter(Boolean)
|
|
: ["auth.json", "config.toml", "model-catalog.json"]
|
|
};
|
|
}
|
|
|
|
function opencodePositiveInteger(value, fallback, label) {
|
|
if (value === undefined || value === null) return fallback;
|
|
assert.ok(Number.isInteger(value) && value > 0, `${label} must be a positive integer`);
|
|
return value;
|
|
}
|
|
|
|
function opencodeConfigBoolean(value, fallback, label) {
|
|
if (value === undefined || value === null) return fallback;
|
|
assert.equal(typeof value, "boolean", `${label} must be a boolean`);
|
|
return value;
|
|
}
|
|
|
|
function opencodeEgressProxyUrlForProfile(deploy, profile) {
|
|
const proxy = deploy?.lanes?.[profile]?.gitMirror?.egressProxy;
|
|
if (!proxy || proxy.required === false) return "";
|
|
const proxyUrl = configString(proxy.proxyUrl);
|
|
if (proxyUrl) return proxyUrl;
|
|
const serviceName = configString(proxy.serviceName);
|
|
const namespace = configString(proxy.namespace) || "platform-infra";
|
|
const port = Number(proxy.port);
|
|
if (!serviceName || !Number.isInteger(port) || port <= 0) return "";
|
|
return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`;
|
|
}
|
|
|
|
function opencodeEgressNoProxyForProfile(deploy, profile) {
|
|
const proxy = deploy?.lanes?.[profile]?.gitMirror?.egressProxy;
|
|
const entries = Array.isArray(proxy?.noProxy) ? proxy.noProxy.map(configString).filter(Boolean) : [];
|
|
return entries.length > 0 ? entries.join(",") : "localhost,127.0.0.1,::1,.svc,.svc.cluster.local,.cluster.local";
|
|
}
|
|
|
|
function opencodeApiKeyEnvName(providerId) {
|
|
const normalized = String(providerId || "provider").toUpperCase().replace(/[^A-Z0-9]+/gu, "_").replace(/^_+|_+$/gu, "");
|
|
return `OPENCODE_${normalized || "PROVIDER"}_API_KEY`;
|
|
}
|
|
|
|
function opencodeConfigContent(config) {
|
|
return JSON.stringify({
|
|
$schema: "https://opencode.ai/config.json",
|
|
autoupdate: false,
|
|
share: "disabled",
|
|
model: `${config.providerId}/${config.model}`,
|
|
small_model: `${config.providerId}/${config.smallModel}`,
|
|
provider: {
|
|
[config.providerId]: {
|
|
npm: config.npm,
|
|
name: config.displayName,
|
|
options: {
|
|
baseURL: config.baseURL,
|
|
apiKey: `{env:${config.apiKeyEnv}}`,
|
|
timeout: 600000,
|
|
headerTimeout: 600000,
|
|
chunkTimeout: 300000
|
|
},
|
|
models: {
|
|
[config.model]: {
|
|
name: config.model,
|
|
reasoning: config.reasoning,
|
|
tool_call: config.toolCall,
|
|
limit: {
|
|
context: config.contextLimit,
|
|
output: config.outputLimit
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function opencodeServerManifest({ profile = "v03", source, deploy = null, catalog = null, registryPrefix = defaultRegistryPrefix, useDeployImages = false, sourceBranch = defaultBranch, gitReadUrl = defaultV02GitReadUrl, sourceRepo = defaultSourceRepo }) {
|
|
assert.ok(isRuntimeLane(profile), `opencode-server profile must be a runtime lane, got ${profile}`);
|
|
const namespace = namespaceNameForProfile(profile, deploy);
|
|
const name = "opencode-server";
|
|
const helperServiceId = "hwlab-cloud-web";
|
|
const envReuseServiceIds = envReuseServiceIdsForLane(deploy, profile);
|
|
const helperImage = runtimeImageForService({ catalog, deploy, serviceId: helperServiceId, source, registryPrefix, useDeployImages, digestPin: true, envReuseServiceIds });
|
|
const helperCommit = runtimeCommitForService({ catalog, deploy, serviceId: helperServiceId, source, registryPrefix, useDeployImages, digestPin: true, envReuseServiceIds });
|
|
const helperImageTag = runtimeImageTagForService({ catalog, deploy, serviceId: helperServiceId, source, registryPrefix, useDeployImages, digestPin: true, envReuseServiceIds });
|
|
const providerProxyBootSh = "deploy/runtime/boot/opencode-provider-proxy.sh";
|
|
const providerProxyBootMetadata = v02EnvReuseEnabled(catalog, helperServiceId, envReuseServiceIds)
|
|
? bootMetadataForService({ args: { sourceRepo: deploy?.lanes?.[profile]?.sourceRepo || sourceRepo, registryPrefix }, catalog, deployService: deployServiceForBoot(deploy, helperServiceId, profile), serviceId: helperServiceId, source })
|
|
: null;
|
|
const selector = {
|
|
"app.kubernetes.io/name": name,
|
|
"app.kubernetes.io/part-of": "hwlab",
|
|
"hwlab.pikastech.local/environment": profile,
|
|
"hwlab.pikastech.local/profile": profile
|
|
};
|
|
const labels = {
|
|
...selector,
|
|
"hwlab.pikastech.local/component": "opencode",
|
|
"hwlab.pikastech.local/gitops-target": profile,
|
|
"hwlab.pikastech.local/service-id": name,
|
|
"hwlab.pikastech.local/source-commit": source.full
|
|
};
|
|
const opencodeConfig = opencodeRuntimeConfigForProfile(deploy, profile);
|
|
const configContent = opencodeConfigContent(opencodeConfig);
|
|
const configSha256 = createHash("sha256").update(configContent).digest("hex");
|
|
const apiKeySecretRef = secretRefObjectForProfile(opencodeConfig.apiKeySecretRef, profile);
|
|
const opencodeApkProxyUrl = opencodeConfig.apkProxyURL || opencodeEgressProxyUrlForProfile(deploy, profile);
|
|
const opencodeApkNoProxy = opencodeEgressNoProxyForProfile(deploy, profile);
|
|
const annotations = {
|
|
"hwlab.pikastech.local/rendered-by": "scripts/gitops-render.mjs",
|
|
"hwlab.pikastech.local/opencode-provider-profile": opencodeConfig.providerProfile,
|
|
"hwlab.pikastech.local/opencode-provider-id": opencodeConfig.providerId,
|
|
"hwlab.pikastech.local/opencode-model": opencodeConfig.model,
|
|
"hwlab.pikastech.local/opencode-provider-base-url": opencodeConfig.baseURL,
|
|
"hwlab.pikastech.local/opencode-provider-upstream-base-url": opencodeConfig.upstreamBaseURL,
|
|
"hwlab.pikastech.local/opencode-image": opencodeConfig.image,
|
|
"hwlab.pikastech.local/opencode-config-sha256": configSha256,
|
|
"hwlab.pikastech.local/opencode-api-key-secret-ref": `${apiKeySecretRef.name}/${apiKeySecretRef.key}`,
|
|
"hwlab.pikastech.local/agentrun-provider-secret-ref": `${opencodeConfig.agentrunSecretNamespace}/${opencodeConfig.agentrunSecretName}`,
|
|
"hwlab.pikastech.local/agentrun-provider-secret-keys": opencodeConfig.agentrunSecretKeys.join(","),
|
|
"hwlab.pikastech.local/values-printed": "false"
|
|
};
|
|
if (providerProxyBootMetadata) {
|
|
Object.assign(annotations, {
|
|
"hwlab.pikastech.local/opencode-provider-proxy-runtime-mode": providerProxyBootMetadata.runtimeMode,
|
|
"hwlab.pikastech.local/opencode-provider-proxy-boot-repo": providerProxyBootMetadata.bootRepo,
|
|
"hwlab.pikastech.local/opencode-provider-proxy-boot-commit": providerProxyBootMetadata.bootCommit,
|
|
"hwlab.pikastech.local/opencode-provider-proxy-boot-sh": providerProxyBootSh,
|
|
"hwlab.pikastech.local/opencode-provider-proxy-environment-digest": providerProxyBootMetadata.environmentDigest ?? "not_published"
|
|
});
|
|
}
|
|
const authSecretName = secretNameForProfile("hwlab-opencode-server-auth", profile);
|
|
return {
|
|
apiVersion: "v1",
|
|
kind: "List",
|
|
items: [
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "ServiceAccount",
|
|
metadata: { name, namespace, labels, annotations }
|
|
},
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "PersistentVolumeClaim",
|
|
metadata: { name: `${name}-data`, namespace, labels, annotations },
|
|
spec: {
|
|
accessModes: ["ReadWriteOnce"],
|
|
resources: { requests: { storage: "8Gi" } }
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "v1",
|
|
kind: "Service",
|
|
metadata: { name, namespace, labels, annotations },
|
|
spec: {
|
|
type: "ClusterIP",
|
|
selector,
|
|
ports: [{ name: "http", port: 4096, targetPort: "http" }]
|
|
}
|
|
},
|
|
{
|
|
apiVersion: "apps/v1",
|
|
kind: "Deployment",
|
|
metadata: { name, namespace, labels, annotations },
|
|
spec: {
|
|
replicas: 1,
|
|
strategy: { type: "Recreate" },
|
|
selector: { matchLabels: selector },
|
|
template: {
|
|
metadata: { labels, annotations },
|
|
spec: {
|
|
serviceAccountName: name,
|
|
securityContext: { fsGroup: 1000, fsGroupChangePolicy: "OnRootMismatch" },
|
|
initContainers: [{
|
|
name: "opencode-workspace-git-init",
|
|
image: helperImage,
|
|
imagePullPolicy: "IfNotPresent",
|
|
command: ["/bin/sh", "-ec"],
|
|
args: [
|
|
[
|
|
"set -eu",
|
|
"mkdir -p /workspace",
|
|
"if [ ! -d /workspace/.git ]; then",
|
|
" git -C /workspace init",
|
|
"fi",
|
|
"git -C /workspace config user.name 'HWLAB OpenCode' || true",
|
|
"git -C /workspace config user.email 'opencode@hwlab.local' || true",
|
|
"chgrp -R 1000 /workspace/.git 2>/dev/null || true",
|
|
"chmod -R g+rwX /workspace/.git 2>/dev/null || true"
|
|
].join("\n")
|
|
],
|
|
resources: { requests: { cpu: "25m", memory: "64Mi" }, limits: { cpu: "250m", memory: "256Mi" } },
|
|
volumeMounts: [{ name: "workspace", mountPath: "/workspace" }]
|
|
}],
|
|
containers: [{
|
|
name,
|
|
image: opencodeConfig.image,
|
|
imagePullPolicy: "IfNotPresent",
|
|
command: ["/bin/sh", "-ec"],
|
|
args: ["exec opencode serve --hostname 0.0.0.0 --port 4096"],
|
|
workingDir: "/workspace",
|
|
env: [
|
|
{ name: "HOME", value: "/workspace" },
|
|
{ name: "XDG_CONFIG_HOME", value: "/workspace/.config" },
|
|
{ name: "XDG_DATA_HOME", value: "/workspace/.local/share" },
|
|
...(opencodeApkProxyUrl ? [
|
|
{ name: "HTTP_PROXY", value: opencodeApkProxyUrl },
|
|
{ name: "HTTPS_PROXY", value: opencodeApkProxyUrl },
|
|
{ name: "http_proxy", value: opencodeApkProxyUrl },
|
|
{ name: "https_proxy", value: opencodeApkProxyUrl },
|
|
{ name: "NO_PROXY", value: opencodeApkNoProxy },
|
|
{ name: "no_proxy", value: opencodeApkNoProxy }
|
|
] : []),
|
|
{ name: "OPENCODE_CONFIG_CONTENT", value: configContent },
|
|
{ name: opencodeConfig.apiKeyEnv, valueFrom: { secretKeyRef: apiKeySecretRef } },
|
|
{ name: "OPENCODE_SERVER_USERNAME", valueFrom: { secretKeyRef: { name: authSecretName, key: "username" } } },
|
|
{ name: "OPENCODE_SERVER_PASSWORD", valueFrom: { secretKeyRef: { name: authSecretName, key: "password" } } }
|
|
],
|
|
ports: [{ name: "http", containerPort: 4096 }],
|
|
startupProbe: { tcpSocket: { port: "http" }, periodSeconds: 5, failureThreshold: 60 },
|
|
readinessProbe: { tcpSocket: { port: "http" }, periodSeconds: 10, failureThreshold: 3 },
|
|
livenessProbe: { tcpSocket: { port: "http" }, periodSeconds: 20, failureThreshold: 3 },
|
|
resources: { requests: { cpu: "100m", memory: "256Mi" }, limits: { cpu: "1", memory: "1Gi" } },
|
|
volumeMounts: [{ name: "workspace", mountPath: "/workspace" }]
|
|
}, (() => {
|
|
const container = {
|
|
name: "opencode-provider-proxy",
|
|
image: helperImage,
|
|
imagePullPolicy: "IfNotPresent",
|
|
command: ["node", "/app/internal/dev-entrypoint/opencode-provider-proxy.mjs"],
|
|
env: [
|
|
{ name: "HWLAB_SERVICE_ID", value: "opencode-provider-proxy" },
|
|
{ name: "HWLAB_ENVIRONMENT", value: profile },
|
|
{ name: "HWLAB_COMMIT_ID", value: helperCommit },
|
|
{ name: "HWLAB_IMAGE", value: helperImage },
|
|
{ name: "HWLAB_IMAGE_TAG", value: helperImageTag },
|
|
{ name: "PORT", value: String(opencodeConfig.providerProxyPort) },
|
|
{ name: "HWLAB_OPENCODE_PROVIDER_PROXY_UPSTREAM_BASE_URL", value: opencodeConfig.upstreamBaseURL },
|
|
{ name: "HWLAB_OPENCODE_PROVIDER_PROXY_PUBLIC_BASE_PATH", value: opencodeConfig.providerProxyBasePath },
|
|
{ name: "HWLAB_OPENCODE_PROVIDER_PROXY_TIMEOUT_MS", value: "600000" },
|
|
{ name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", value: "http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces" },
|
|
{ name: "OTEL_SERVICE_NAME", value: "opencode-provider-proxy" }
|
|
],
|
|
ports: [{ name: "provider", containerPort: opencodeConfig.providerProxyPort }],
|
|
readinessProbe: { httpGet: { path: "/health/readiness", port: "provider" }, periodSeconds: 10, failureThreshold: 3 },
|
|
livenessProbe: { httpGet: { path: "/health/live", port: "provider" }, periodSeconds: 20, failureThreshold: 3 },
|
|
resources: { requests: { cpu: "25m", memory: "64Mi" }, limits: { cpu: "250m", memory: "256Mi" } }
|
|
};
|
|
if (providerProxyBootMetadata) {
|
|
applyEnvReuseBootEnv(container, providerProxyBootMetadata, { sourceBranch, gitReadUrl, bootSh: providerProxyBootSh });
|
|
}
|
|
return container;
|
|
})()],
|
|
volumes: [{ name: "workspace", persistentVolumeClaim: { claimName: `${name}-data` } }]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
export {
|
|
argoApplication,
|
|
argoProject,
|
|
deepSeekProxyManifest,
|
|
deviceAgent71FreqManifest,
|
|
deviceAgent71FreqServerScript,
|
|
externalPostgresConfigForLane,
|
|
externalPostgresManifest,
|
|
moonBridgeConfigRenderScript,
|
|
nodeFrpcManifest,
|
|
normalizeSecretPlaneNodeList,
|
|
opencodeApiKeyEnvName,
|
|
opencodeConfigBoolean,
|
|
opencodeConfigContent,
|
|
opencodeEgressNoProxyForProfile,
|
|
opencodeEgressProxyUrlForProfile,
|
|
opencodePositiveInteger,
|
|
opencodeRuntimeConfigForProfile,
|
|
opencodeServerManifest,
|
|
runtimeConfigMapsForProfile,
|
|
runtimeConfigMapsManifest,
|
|
runtimePostgresImageForProfile,
|
|
runtimeSecretPlaneConfigForProfile,
|
|
runtimeSecretPlaneEnabledForNode,
|
|
runtimeSecretPlaneExternalSecret,
|
|
runtimeSecretPlaneManifest,
|
|
secretPlaneIssueLabelValue,
|
|
v02OpenFgaManifest,
|
|
v02PostgresManifest,
|
|
workbenchRuntimeRedisConf,
|
|
workbenchRuntimeRedisManifest
|
|
};
|