feat: render 71 freq device agent gitops

This commit is contained in:
Codex
2026-05-26 07:44:38 +08:00
parent b2b2a0e4df
commit 025c9a7197
+190 -2
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { promisify } from "node:util";
@@ -1418,8 +1419,193 @@ function deepSeekProxyManifest({ profile = "dev" } = {}) {
};
}
function runtimeKustomization({ profile = "dev" } = {}) {
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 }) {
assert.equal(profile, "dev", "71-freq device-agent is dev-only");
const namespace = namespaceNameForProfile(profile);
const name = "device-agent-71-freq";
const labels = {
"app.kubernetes.io/name": name,
"app.kubernetes.io/part-of": "hwlab",
"hwlab.pikastech.local/component": "device-agent",
"hwlab.pikastech.local/device-id": "71-freq",
"hwlab.pikastech.local/environment": runtimeLabelForProfile(profile),
"hwlab.pikastech.local/gitops-target": "g14",
"hwlab.pikastech.local/profile": runtimeLabelForProfile(profile),
"hwlab.pikastech.local/service-id": name,
"hwlab.pikastech.local/source-commit": source.full
};
const annotations = { "hwlab.pikastech.local/rendered-by": "scripts/g14-gitops-render.mjs" };
const selector = { "app.kubernetes.io/name": name };
const script = deviceAgent71FreqServerScript();
const scriptSha256 = createHash("sha256").update(script).digest("hex");
const templateAnnotations = { ...annotations, "hwlab.pikastech.local/script-sha256": scriptSha256 };
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: { ...labels, ...selector }, annotations: templateAnnotations },
spec: {
containers: [{
name: "device-agent",
image: imageFor(registryPrefix, "hwlab-cloud-api", source.short),
imagePullPolicy: "IfNotPresent",
command: ["node", "/opt/device-agent/server.mjs"],
env: [
{ name: "PORT", value: "7601" },
{ 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 }]
}],
volumes: [{ name: "script", configMap: { name: `${name}-script` } }]
}
}
}
},
{
apiVersion: "v1",
kind: "Service",
metadata: { name, namespace, labels, annotations },
spec: { type: "ClusterIP", selector, ports: [{ name: "http", port: 7601, targetPort: "http" }] }
}
]
};
}
function runtimeKustomization({ profile = "dev", includeDeviceAgent = profile === "dev" } = {}) {
const resources = ["namespace.yaml", "code-agent-codex-config.yaml", "services.yaml", "health-contract.yaml", "workloads.yaml", "deepseek-proxy.yaml"];
if (includeDeviceAgent) resources.push("device-agent-71-freq.yaml");
resources.push("g14-frpc.yaml");
return {
apiVersion: "kustomize.config.k8s.io/v1beta1",
@@ -1498,6 +1684,7 @@ async function plannedFiles(args) {
for (const profile of ["dev", "prod"]) {
const namespace = namespaceNameForProfile(profile);
const includeDeviceAgent = profile === "dev";
const profileLabels = { ...baseLabels, "hwlab.pikastech.local/environment": runtimeLabelForProfile(profile), "hwlab.pikastech.local/profile": runtimeLabelForProfile(profile) };
const runtimeNamespace = cloneJson(namespaceTemplate);
runtimeNamespace.metadata.name = namespace;
@@ -1505,13 +1692,14 @@ async function plannedFiles(args) {
annotate(runtimeNamespace.metadata, annotations);
const runtimePath = runtimePathForProfile(profile);
const endpoints = profileEndpoint(args, profile);
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile }));
putJson(`${runtimePath}/kustomization.yaml`, runtimeKustomization({ profile, includeDeviceAgent }));
putJson(`${runtimePath}/namespace.yaml`, runtimeNamespace);
putJson(`${runtimePath}/code-agent-codex-config.yaml`, transformListNamespace(config, namespace, profileLabels, annotations));
putJson(`${runtimePath}/services.yaml`, transformListNamespace(services, namespace, profileLabels, annotations));
putJson(`${runtimePath}/health-contract.yaml`, transformHealthContract(health, namespace, profileLabels, annotations, endpoints.runtimeEndpoint, endpoints.webEndpoint, profile));
putJson(`${runtimePath}/workloads.yaml`, transformWorkloads({ workloads, deploy, source, registryPrefix: args.registryPrefix, runtimeEndpoint: endpoints.runtimeEndpoint, webEndpoint: endpoints.webEndpoint, profile }));
putJson(`${runtimePath}/deepseek-proxy.yaml`, deepSeekProxyManifest({ profile }));
if (includeDeviceAgent) putJson(`${runtimePath}/device-agent-71-freq.yaml`, deviceAgent71FreqManifest({ profile, source, registryPrefix: args.registryPrefix }));
putJson(`${runtimePath}/g14-frpc.yaml`, g14FrpcManifest({ profile }));
}
return { files, source };