feat: render 71 freq device agent gitops
This commit is contained in:
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user