feat(hwpod): 增加 L2 独立服务部署
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bun
|
||||
import path from "node:path";
|
||||
|
||||
const host = process.env.HWPOD_WEB_HOST || "0.0.0.0";
|
||||
const port = positivePort(process.env.HWPOD_WEB_PORT, 8080);
|
||||
const apiUrl = requiredUrl(process.env.HWPOD_API_URL, "HWPOD_API_URL");
|
||||
const root = path.resolve(process.env.HWPOD_WEB_ROOT || "web/hwlab-cloud-web/hwpod/dist");
|
||||
|
||||
const server = Bun.serve({
|
||||
hostname: host,
|
||||
port,
|
||||
async fetch(request) {
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname === "/health/live") return Response.json({ ok: true, serviceId: "hwlab-hwpod-web", status: "live", valuesPrinted: false });
|
||||
if (url.pathname === "/health/ready" || url.pathname.startsWith("/v1/")) return proxyRequest(request, apiUrl, url);
|
||||
const relativePath = url.pathname === "/" ? "index.html" : decodeURIComponent(url.pathname.replace(/^\/+/, ""));
|
||||
const filePath = path.resolve(root, relativePath);
|
||||
if (!filePath.startsWith(`${root}${path.sep}`) && filePath !== path.join(root, "index.html")) return new Response("not found", { status: 404 });
|
||||
const file = Bun.file(filePath);
|
||||
if (await file.exists()) return new Response(file, { headers: { "cache-control": relativePath === "index.html" ? "no-store" : "public, max-age=3600" } });
|
||||
return new Response("not found", { status: 404 });
|
||||
},
|
||||
});
|
||||
|
||||
process.stdout.write(`${JSON.stringify({ serviceId: "hwlab-hwpod-web", status: "listening", host, port: server.port, apiAuthority: apiUrl, valuesPrinted: false })}\n`);
|
||||
for (const signal of ["SIGINT", "SIGTERM"] as const) process.once(signal, () => { server.stop(true); process.exit(0); });
|
||||
|
||||
async function proxyRequest(request: Request, baseUrl: string, sourceUrl: URL) {
|
||||
const target = new URL(`${sourceUrl.pathname}${sourceUrl.search}`, baseUrl);
|
||||
const headers = new Headers(request.headers);
|
||||
headers.delete("host");
|
||||
return fetch(target, { method: request.method, headers, body: request.body, redirect: "manual", signal: request.signal });
|
||||
}
|
||||
function requiredUrl(value: string | undefined, name: string) { const parsed = new URL(String(value ?? "")); if (!/^https?:$/u.test(parsed.protocol)) throw new Error(`${name} must be an HTTP URL`); return parsed.toString().replace(/\/$/u, ""); }
|
||||
function positivePort(value: string | undefined, fallback: number) { const parsed = Number.parseInt(String(value ?? fallback), 10); if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) throw new Error("HWPOD_WEB_PORT must be a valid port"); return parsed; }
|
||||
+195
-1
@@ -336,6 +336,12 @@ lanes:
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
nodePort: 32009
|
||||
- name: hwlab-hwpod-web-public
|
||||
selector:
|
||||
app.kubernetes.io/name: hwlab-hwpod-web
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
nodePort: 32012
|
||||
frp:
|
||||
server:
|
||||
address: 82.156.23.220
|
||||
@@ -515,6 +521,9 @@ lanes:
|
||||
- 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
|
||||
@@ -530,6 +539,9 @@ lanes:
|
||||
hwlab-harnessrl-worker: deploy/runtime/boot/hwlab-harnessrl-worker.sh
|
||||
hwlab-workbench-api: deploy/runtime/boot/hwlab-workbench-api.sh
|
||||
hwlab-workbench-worker: deploy/runtime/boot/hwlab-workbench-worker.sh
|
||||
hwlab-hwpod-api: deploy/runtime/boot/hwlab-hwpod-api.sh
|
||||
hwlab-hwpod-worker: deploy/runtime/boot/hwlab-hwpod-worker.sh
|
||||
hwlab-hwpod-web: deploy/runtime/boot/hwlab-hwpod-web.sh
|
||||
hwlab-cloud-web: deploy/runtime/boot/hwlab-cloud-web.sh
|
||||
hwlab-gateway: deploy/runtime/boot/hwlab-gateway.sh
|
||||
hwlab-edge-proxy: deploy/runtime/boot/hwlab-edge-proxy.sh
|
||||
@@ -743,6 +755,61 @@ lanes:
|
||||
env:
|
||||
TZ: Asia/Shanghai
|
||||
observable: true
|
||||
hwlab-hwpod-api:
|
||||
runtimeKind: bun-command
|
||||
entrypoint: cmd/hwlab-hwpod-api/main.ts
|
||||
artifactKind: bun-command
|
||||
healthPath: /health/ready
|
||||
healthPort: 6681
|
||||
componentPaths:
|
||||
- cmd/hwlab-hwpod-api/
|
||||
- internal/hwpod/
|
||||
- internal/db/runtime-store-core.ts
|
||||
- package.json
|
||||
- package-lock.json
|
||||
- deploy/runtime/boot/hwlab-hwpod-api.sh
|
||||
env:
|
||||
TZ: Asia/Shanghai
|
||||
observable: true
|
||||
hwlab-hwpod-worker:
|
||||
runtimeKind: bun-command
|
||||
entrypoint: cmd/hwlab-hwpod-worker/main.ts
|
||||
artifactKind: bun-command
|
||||
healthPath: /health/ready
|
||||
healthPort: 6682
|
||||
componentPaths:
|
||||
- cmd/hwlab-hwpod-worker/
|
||||
- internal/hwpod/
|
||||
- internal/db/runtime-store-core.ts
|
||||
- package.json
|
||||
- package-lock.json
|
||||
- deploy/runtime/boot/hwlab-hwpod-worker.sh
|
||||
env:
|
||||
TZ: Asia/Shanghai
|
||||
observable: true
|
||||
hwlab-hwpod-web:
|
||||
runtimeKind: cloud-web
|
||||
entrypoint: cmd/hwlab-hwpod-web/main.ts
|
||||
artifactKind: cloud-web
|
||||
healthPath: /health/live
|
||||
healthPort: 8080
|
||||
healthProbe:
|
||||
startup:
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 60
|
||||
componentPaths:
|
||||
- cmd/hwlab-hwpod-web/
|
||||
- web/hwlab-cloud-web/hwpod/
|
||||
- web/hwlab-cloud-web/scripts/hwpod-native-vite.config.ts
|
||||
- web/hwlab-cloud-web/package.json
|
||||
- package.json
|
||||
- package-lock.json
|
||||
- deploy/runtime/boot/hwlab-hwpod-web.sh
|
||||
env:
|
||||
BUN_CONFIG_REGISTRY: https://registry.npmjs.org/
|
||||
npm_config_registry: https://registry.npmjs.org/
|
||||
observable: true
|
||||
hwlab-cloud-web:
|
||||
runtimeKind: cloud-web
|
||||
entrypoint: web/hwlab-cloud-web/index.html
|
||||
@@ -867,7 +934,7 @@ lanes:
|
||||
bootConfig: &v03BootConfig
|
||||
template: default
|
||||
overrides: {}
|
||||
configMaps:
|
||||
configMaps: &v03ConfigMaps
|
||||
- name: hwlab-v03-hwpod-preinstalled-specs
|
||||
labels:
|
||||
hwlab.pikastech.local/config-kind: hwpod-preinstalled-specs
|
||||
@@ -902,11 +969,33 @@ lanes:
|
||||
probeName: MicroLink CMSIS-DAP
|
||||
programBackend: keil-headless
|
||||
autoBindUvoptx: true
|
||||
boardComm:
|
||||
endpoints:
|
||||
hwpod-node-ops:
|
||||
kind: hwpod-node-ops
|
||||
endpointRef: config/hwlab-gateway/constart-71freq.yaml#gateway.profile.nodeOps.publicUrl
|
||||
freq71:
|
||||
kind: jsonrpc-tcp
|
||||
host: 192.168.0.154
|
||||
port: 8000
|
||||
role: frequency-controller
|
||||
main41:
|
||||
kind: jsonrpc-tcp
|
||||
host: 192.168.0.151
|
||||
port: 8000
|
||||
role: main-controller
|
||||
ioProbe:
|
||||
uart:
|
||||
id: uart/1
|
||||
port: COM4
|
||||
baudrate: 921600
|
||||
probes:
|
||||
main41.ai0.current:
|
||||
endpointRef: main41
|
||||
quantity: current
|
||||
unit: mA
|
||||
path: hardware/ai/0
|
||||
valuePath: ai_current_mA
|
||||
nodeBinding:
|
||||
nodeId: node-d601-f103-v2
|
||||
nodeType: pc-host
|
||||
@@ -1172,6 +1261,58 @@ lanes:
|
||||
HWLAB_KAFKA_EVENT_TOPIC: hwlab.event.v1
|
||||
HWLAB_KAFKA_CLIENT_ID: hwlab-v03-workbench-worker
|
||||
OTEL_SERVICE_NAME: hwlab-workbench-worker
|
||||
- serviceId: hwlab-hwpod-api
|
||||
replicas: 1
|
||||
env:
|
||||
HWPOD_API_HOST: 0.0.0.0
|
||||
HWPOD_API_PORT: "6681"
|
||||
HWPOD_TEMPORAL_ADDRESS: temporal-frontend.temporal.svc.cluster.local:7233
|
||||
HWPOD_TEMPORAL_NAMESPACE: unidesk
|
||||
HWPOD_TEMPORAL_TASK_QUEUE: hwlab-v03-hwpod
|
||||
HWPOD_RUNTIME_API_URL: http://hwlab-cloud-api.hwlab-v03.svc.cluster.local:6667
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION: secretRef:hwlab-v03-master-server-admin-api-key/api-key
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION_PREFIX: Bearer
|
||||
HWPOD_SPEC_DATABASE_URL: secretRef:hwlab-cloud-api-v03-db/database-url
|
||||
HWPOD_SPEC_DATABASE_SCHEMA: hwpod
|
||||
HWPOD_SPEC_DATABASE_TABLE: runtime_specs
|
||||
HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS: "5000"
|
||||
HWPOD_BUILTIN_SPEC_CONFIG_DIRECTORY: /etc/hwlab/hwpod-specs
|
||||
OTEL_SERVICE_NAME: hwlab-hwpod-api
|
||||
configMapMounts:
|
||||
- name: hwpod-preinstalled-specs
|
||||
configMapName: hwlab-v03-hwpod-preinstalled-specs
|
||||
mountPath: /etc/hwlab/hwpod-specs
|
||||
readOnly: true
|
||||
- serviceId: hwlab-hwpod-worker
|
||||
replicas: 1
|
||||
env:
|
||||
HWPOD_WORKER_HEALTH_HOST: 0.0.0.0
|
||||
HWPOD_WORKER_HEALTH_PORT: "6682"
|
||||
HWPOD_TEMPORAL_ADDRESS: temporal-frontend.temporal.svc.cluster.local:7233
|
||||
HWPOD_TEMPORAL_NAMESPACE: unidesk
|
||||
HWPOD_TEMPORAL_TASK_QUEUE: hwlab-v03-hwpod
|
||||
HWPOD_RUNTIME_API_URL: http://hwlab-cloud-api.hwlab-v03.svc.cluster.local:6667
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION: secretRef:hwlab-v03-master-server-admin-api-key/api-key
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION_PREFIX: Bearer
|
||||
HWPOD_SPEC_DATABASE_URL: secretRef:hwlab-cloud-api-v03-db/database-url
|
||||
HWPOD_SPEC_DATABASE_SCHEMA: hwpod
|
||||
HWPOD_SPEC_DATABASE_TABLE: runtime_specs
|
||||
HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS: "5000"
|
||||
HWPOD_BUILTIN_SPEC_CONFIG_DIRECTORY: /etc/hwlab/hwpod-specs
|
||||
OTEL_SERVICE_NAME: hwlab-hwpod-worker
|
||||
configMapMounts:
|
||||
- name: hwpod-preinstalled-specs
|
||||
configMapName: hwlab-v03-hwpod-preinstalled-specs
|
||||
mountPath: /etc/hwlab/hwpod-specs
|
||||
readOnly: true
|
||||
- serviceId: hwlab-hwpod-web
|
||||
replicas: 1
|
||||
env:
|
||||
HWPOD_WEB_HOST: 0.0.0.0
|
||||
HWPOD_WEB_PORT: "8080"
|
||||
HWPOD_API_URL: http://hwlab-hwpod-api.hwlab-v03.svc.cluster.local:6681
|
||||
HWPOD_WEB_ROOT: web/hwlab-cloud-web/hwpod/dist
|
||||
OTEL_SERVICE_NAME: hwlab-hwpod-web
|
||||
production:
|
||||
name: Production
|
||||
node: NC01
|
||||
@@ -1213,6 +1354,7 @@ lanes:
|
||||
envReuseServices: *v03EnvReuseServices
|
||||
bootScripts: *v03BootScripts
|
||||
serviceDeclarations: *v03ServiceDeclarations
|
||||
configMaps: *v03ConfigMaps
|
||||
services:
|
||||
- serviceId: hwlab-cloud-api
|
||||
shutdownDrain:
|
||||
@@ -1307,6 +1449,58 @@ lanes:
|
||||
TASKTREE_TEMPORAL_TASK_QUEUE: hwlab-production-tasktree
|
||||
TASKTREE_WORKER_HEALTH_PORT: "6674"
|
||||
OTEL_SERVICE_NAME: hwlab-tasktree-worker
|
||||
- serviceId: hwlab-hwpod-api
|
||||
replicas: 1
|
||||
env:
|
||||
HWPOD_API_HOST: 0.0.0.0
|
||||
HWPOD_API_PORT: "6681"
|
||||
HWPOD_TEMPORAL_ADDRESS: temporal-frontend.temporal.svc.cluster.local:7233
|
||||
HWPOD_TEMPORAL_NAMESPACE: unidesk
|
||||
HWPOD_TEMPORAL_TASK_QUEUE: hwlab-production-hwpod
|
||||
HWPOD_RUNTIME_API_URL: http://hwlab-cloud-api.hwlab-production.svc.cluster.local:6667
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION: secretRef:hwlab-production-master-server-admin-api-key/api-key
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION_PREFIX: Bearer
|
||||
HWPOD_SPEC_DATABASE_URL: secretRef:hwlab-cloud-api-production-db/database-url
|
||||
HWPOD_SPEC_DATABASE_SCHEMA: hwpod
|
||||
HWPOD_SPEC_DATABASE_TABLE: runtime_specs
|
||||
HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS: "5000"
|
||||
HWPOD_BUILTIN_SPEC_CONFIG_DIRECTORY: /etc/hwlab/hwpod-specs
|
||||
OTEL_SERVICE_NAME: hwlab-hwpod-api
|
||||
configMapMounts:
|
||||
- name: hwpod-preinstalled-specs
|
||||
configMapName: hwlab-v03-hwpod-preinstalled-specs
|
||||
mountPath: /etc/hwlab/hwpod-specs
|
||||
readOnly: true
|
||||
- serviceId: hwlab-hwpod-worker
|
||||
replicas: 1
|
||||
env:
|
||||
HWPOD_WORKER_HEALTH_HOST: 0.0.0.0
|
||||
HWPOD_WORKER_HEALTH_PORT: "6682"
|
||||
HWPOD_TEMPORAL_ADDRESS: temporal-frontend.temporal.svc.cluster.local:7233
|
||||
HWPOD_TEMPORAL_NAMESPACE: unidesk
|
||||
HWPOD_TEMPORAL_TASK_QUEUE: hwlab-production-hwpod
|
||||
HWPOD_RUNTIME_API_URL: http://hwlab-cloud-api.hwlab-production.svc.cluster.local:6667
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION: secretRef:hwlab-production-master-server-admin-api-key/api-key
|
||||
HWPOD_RUNTIME_API_AUTHORIZATION_PREFIX: Bearer
|
||||
HWPOD_SPEC_DATABASE_URL: secretRef:hwlab-cloud-api-production-db/database-url
|
||||
HWPOD_SPEC_DATABASE_SCHEMA: hwpod
|
||||
HWPOD_SPEC_DATABASE_TABLE: runtime_specs
|
||||
HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS: "5000"
|
||||
HWPOD_BUILTIN_SPEC_CONFIG_DIRECTORY: /etc/hwlab/hwpod-specs
|
||||
OTEL_SERVICE_NAME: hwlab-hwpod-worker
|
||||
configMapMounts:
|
||||
- name: hwpod-preinstalled-specs
|
||||
configMapName: hwlab-v03-hwpod-preinstalled-specs
|
||||
mountPath: /etc/hwlab/hwpod-specs
|
||||
readOnly: true
|
||||
- serviceId: hwlab-hwpod-web
|
||||
replicas: 1
|
||||
env:
|
||||
HWPOD_WEB_HOST: 0.0.0.0
|
||||
HWPOD_WEB_PORT: "8080"
|
||||
HWPOD_API_URL: http://hwlab-hwpod-api.hwlab-production.svc.cluster.local:6681
|
||||
HWPOD_WEB_ROOT: web/hwlab-cloud-web/hwpod/dist
|
||||
OTEL_SERVICE_NAME: hwlab-hwpod-web
|
||||
envRecipe: *v03EnvRecipe
|
||||
bootConfig: *v03BootConfig
|
||||
services:
|
||||
|
||||
@@ -2,6 +2,48 @@
|
||||
"apiVersion": "v1",
|
||||
"kind": "List",
|
||||
"items": [
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"name": "hwlab-hwpod-api",
|
||||
"namespace": "hwlab-dev",
|
||||
"labels": { "app.kubernetes.io/name": "hwlab-hwpod-api", "hwlab.pikastech.local/service-id": "hwlab-hwpod-api" }
|
||||
},
|
||||
"spec": {
|
||||
"type": "ClusterIP",
|
||||
"selector": { "app.kubernetes.io/name": "hwlab-hwpod-api" },
|
||||
"ports": [{ "name": "http", "port": 6681, "targetPort": "http" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"name": "hwlab-hwpod-worker",
|
||||
"namespace": "hwlab-dev",
|
||||
"labels": { "app.kubernetes.io/name": "hwlab-hwpod-worker", "hwlab.pikastech.local/service-id": "hwlab-hwpod-worker" }
|
||||
},
|
||||
"spec": {
|
||||
"type": "ClusterIP",
|
||||
"selector": { "app.kubernetes.io/name": "hwlab-hwpod-worker" },
|
||||
"ports": [{ "name": "health", "port": 6682, "targetPort": "health" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"name": "hwlab-hwpod-web",
|
||||
"namespace": "hwlab-dev",
|
||||
"labels": { "app.kubernetes.io/name": "hwlab-hwpod-web", "hwlab.pikastech.local/service-id": "hwlab-hwpod-web" }
|
||||
},
|
||||
"spec": {
|
||||
"type": "ClusterIP",
|
||||
"selector": { "app.kubernetes.io/name": "hwlab-hwpod-web" },
|
||||
"ports": [{ "name": "http", "port": 8080, "targetPort": "http" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
|
||||
@@ -2,6 +2,78 @@
|
||||
"apiVersion": "v1",
|
||||
"kind": "List",
|
||||
"items": [
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "hwlab-hwpod-api",
|
||||
"namespace": "hwlab-dev",
|
||||
"labels": { "app.kubernetes.io/name": "hwlab-hwpod-api", "hwlab.pikastech.local/service-id": "hwlab-hwpod-api" }
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 0,
|
||||
"selector": { "matchLabels": { "app.kubernetes.io/name": "hwlab-hwpod-api" } },
|
||||
"template": {
|
||||
"metadata": { "labels": { "app.kubernetes.io/name": "hwlab-hwpod-api", "hwlab.pikastech.local/service-id": "hwlab-hwpod-api" } },
|
||||
"spec": { "containers": [{
|
||||
"name": "hwlab-hwpod-api",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-hwpod-api:dev",
|
||||
"ports": [{ "name": "http", "containerPort": 6681 }],
|
||||
"env": [{ "name": "HWPOD_API_PORT", "value": "6681" }],
|
||||
"readinessProbe": { "httpGet": { "path": "/health/ready", "port": "http" } },
|
||||
"livenessProbe": { "httpGet": { "path": "/health/live", "port": "http" } }
|
||||
}] }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "hwlab-hwpod-worker",
|
||||
"namespace": "hwlab-dev",
|
||||
"labels": { "app.kubernetes.io/name": "hwlab-hwpod-worker", "hwlab.pikastech.local/service-id": "hwlab-hwpod-worker" }
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 0,
|
||||
"selector": { "matchLabels": { "app.kubernetes.io/name": "hwlab-hwpod-worker" } },
|
||||
"template": {
|
||||
"metadata": { "labels": { "app.kubernetes.io/name": "hwlab-hwpod-worker", "hwlab.pikastech.local/service-id": "hwlab-hwpod-worker" } },
|
||||
"spec": { "containers": [{
|
||||
"name": "hwlab-hwpod-worker",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-hwpod-worker:dev",
|
||||
"ports": [{ "name": "health", "containerPort": 6682 }],
|
||||
"env": [{ "name": "HWPOD_WORKER_HEALTH_PORT", "value": "6682" }],
|
||||
"readinessProbe": { "httpGet": { "path": "/health/ready", "port": "health" } },
|
||||
"livenessProbe": { "httpGet": { "path": "/health/live", "port": "health" } }
|
||||
}] }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "hwlab-hwpod-web",
|
||||
"namespace": "hwlab-dev",
|
||||
"labels": { "app.kubernetes.io/name": "hwlab-hwpod-web", "hwlab.pikastech.local/service-id": "hwlab-hwpod-web" }
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 0,
|
||||
"selector": { "matchLabels": { "app.kubernetes.io/name": "hwlab-hwpod-web" } },
|
||||
"template": {
|
||||
"metadata": { "labels": { "app.kubernetes.io/name": "hwlab-hwpod-web", "hwlab.pikastech.local/service-id": "hwlab-hwpod-web" } },
|
||||
"spec": { "containers": [{
|
||||
"name": "hwlab-hwpod-web",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-hwpod-web:dev",
|
||||
"ports": [{ "name": "http", "containerPort": 8080 }],
|
||||
"env": [{ "name": "HWPOD_WEB_PORT", "value": "8080" }],
|
||||
"readinessProbe": { "httpGet": { "path": "/health/live", "port": "http" } },
|
||||
"livenessProbe": { "httpGet": { "path": "/health/live", "port": "http" } }
|
||||
}] }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
export HWLAB_SERVICE_ID=hwlab-hwpod-api
|
||||
export HWLAB_SERVICE_ENTRYPOINT=cmd/hwlab-hwpod-api/main.ts
|
||||
export HWPOD_API_PORT="${HWPOD_API_PORT:?HWPOD_API_PORT is required}"
|
||||
export PORT="$HWPOD_API_PORT"
|
||||
|
||||
exec "${HWLAB_BUN_COMMAND:-bun}" run cmd/hwlab-hwpod-api/main.ts
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
export HWLAB_SERVICE_ID=hwlab-hwpod-web
|
||||
export HWLAB_SERVICE_ENTRYPOINT=cmd/hwlab-hwpod-web/main.ts
|
||||
export HWPOD_WEB_PORT="${HWPOD_WEB_PORT:?HWPOD_WEB_PORT is required}"
|
||||
export PORT="$HWPOD_WEB_PORT"
|
||||
|
||||
"${HWLAB_BUN_COMMAND:-bun}" run --cwd web/hwlab-cloud-web build:hwpod
|
||||
exec "${HWLAB_BUN_COMMAND:-bun}" run cmd/hwlab-hwpod-web/main.ts
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
export HWLAB_SERVICE_ID=hwlab-hwpod-worker
|
||||
export HWLAB_SERVICE_ENTRYPOINT=cmd/hwlab-hwpod-worker/main.ts
|
||||
export HWPOD_WORKER_HEALTH_PORT="${HWPOD_WORKER_HEALTH_PORT:?HWPOD_WORKER_HEALTH_PORT is required}"
|
||||
export PORT="$HWPOD_WORKER_HEALTH_PORT"
|
||||
|
||||
exec "${HWLAB_BUN_COMMAND:-bun}" run cmd/hwlab-hwpod-worker/main.ts
|
||||
@@ -13,7 +13,9 @@ export function hwpodRuntime(env: Record<string, string | undefined> = process.e
|
||||
const namespace = String(env.HWPOD_TEMPORAL_NAMESPACE ?? "unidesk").trim();
|
||||
const taskQueue = String(env.HWPOD_TEMPORAL_TASK_QUEUE ?? "hwlab-v03-hwpod").trim();
|
||||
const runtimeApiUrl = String(env.HWPOD_RUNTIME_API_URL ?? "").trim().replace(/\/+$/u, "");
|
||||
const runtimeApiAuthorization = String(env.HWPOD_RUNTIME_API_AUTHORIZATION ?? "").trim();
|
||||
const runtimeApiAuthorizationValue = String(env.HWPOD_RUNTIME_API_AUTHORIZATION ?? "").trim();
|
||||
const runtimeApiAuthorizationPrefix = String(env.HWPOD_RUNTIME_API_AUTHORIZATION_PREFIX ?? "").trim();
|
||||
const runtimeApiAuthorization = runtimeApiAuthorizationPrefix ? `${runtimeApiAuthorizationPrefix} ${runtimeApiAuthorizationValue}` : runtimeApiAuthorizationValue;
|
||||
if (!runtimeApiUrl) throw codedError("hwpod_runtime_api_url_required", "HWPOD_RUNTIME_API_URL is required");
|
||||
if (!runtimeApiAuthorization) throw codedError("hwpod_runtime_api_authorization_required", "HWPOD_RUNTIME_API_AUTHORIZATION is required");
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import pg from "pg";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
|
||||
import { buildPostgresPoolConfig, postgresSslModeFromConnectionString } from "../db/runtime-store-core.ts";
|
||||
import { normalizeHwpodSpec } from "./spec-document.ts";
|
||||
@@ -29,7 +32,7 @@ export function hwpodSpecRegistryFromEnv(env: Record<string, string | undefined>
|
||||
const schema = postgresIdentifier(requiredEnv(env, "HWPOD_SPEC_DATABASE_SCHEMA"), "HWPOD_SPEC_DATABASE_SCHEMA");
|
||||
const table = postgresIdentifier(requiredEnv(env, "HWPOD_SPEC_DATABASE_TABLE"), "HWPOD_SPEC_DATABASE_TABLE");
|
||||
const connectionTimeoutMs = positiveIntegerEnv(env, "HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS");
|
||||
const builtIns = parseBuiltIns(env.HWPOD_BUILTIN_SPEC_DOCUMENTS_JSON);
|
||||
const builtIns = parseBuiltIns(env.HWPOD_BUILTIN_SPEC_DOCUMENTS_JSON, env.HWPOD_BUILTIN_SPEC_CONFIG_DIRECTORY);
|
||||
return createHwpodSpecRegistry({ store: createPostgresHwpodRuntimeSpecStore({ databaseUrl, schema, table, connectionTimeoutMs }), builtIns });
|
||||
}
|
||||
|
||||
@@ -163,7 +166,9 @@ function runtimeRow(row: any): HwpodRuntimeSpecRecord {
|
||||
return { authority: "runtime", hwpodId, createdAt: isoTimestamp(row.created_at), updatedAt: isoTimestamp(row.updated_at), document };
|
||||
}
|
||||
|
||||
function parseBuiltIns(raw: string | undefined): BuiltInInput[] {
|
||||
function parseBuiltIns(raw: string | undefined, configDirectory: string | undefined): BuiltInInput[] {
|
||||
if (String(raw ?? "").trim() && String(configDirectory ?? "").trim()) throw codedError("invalid_hwpod_builtin_specs", "HWPOD built-in specs must use either JSON or a config directory, not both");
|
||||
if (String(configDirectory ?? "").trim()) return parseBuiltInConfigDirectory(String(configDirectory));
|
||||
if (!String(raw ?? "").trim()) return [];
|
||||
let value: unknown;
|
||||
try { value = JSON.parse(String(raw)); }
|
||||
@@ -177,6 +182,23 @@ function parseBuiltIns(raw: string | undefined): BuiltInInput[] {
|
||||
});
|
||||
}
|
||||
|
||||
function parseBuiltInConfigDirectory(directoryInput: string): BuiltInInput[] {
|
||||
const directory = path.resolve(directoryInput.trim());
|
||||
return readdirSync(directory, { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile() && /\.ya?ml$/iu.test(entry.name))
|
||||
.map((entry) => entry.name)
|
||||
.sort()
|
||||
.map((fileName) => {
|
||||
const source = readFileSync(path.join(directory, fileName), "utf8");
|
||||
const document = parseYaml(source);
|
||||
return {
|
||||
configRef: `configMap:hwlab-v03-hwpod-preinstalled-specs/${fileName}`,
|
||||
sha256: createHash("sha256").update(stableJson(document)).digest("hex"),
|
||||
document,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeBuiltIns(inputs: BuiltInInput[]) {
|
||||
const records = new Map<string, any>();
|
||||
for (const input of inputs) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"check:tsc-strict": "bun run deps --quiet && bun run scripts/tsc-check.ts --strict",
|
||||
"typecheck": "bun run deps --quiet && vue-tsc --noEmit",
|
||||
"build": "bun run deps --quiet && bun run scripts/build.ts",
|
||||
"build:hwpod": "bun run deps --quiet && vite build --config scripts/hwpod-native-vite.config.ts",
|
||||
"test": "bun run deps --quiet && vitest run",
|
||||
"e2e:workbench": "bun run deps --quiet && bun run build && bunx playwright test -c playwright.workbench.config.ts",
|
||||
"e2e:workbench:report": "bun run deps --quiet && bunx playwright show-report .state/workbench-e2e/html-report"
|
||||
|
||||
@@ -6,19 +6,18 @@
|
||||
import path from "node:path";
|
||||
import { defineConfig, type Plugin } from "vite";
|
||||
|
||||
const apiUrl = process.env.HWPOD_NATIVE_API_URL;
|
||||
if (!apiUrl) throw new Error("HWPOD_NATIVE_API_URL is required");
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig(({ command }) => ({
|
||||
root: path.resolve(import.meta.dirname, "../hwpod"),
|
||||
server: {
|
||||
build: { outDir: "dist", emptyOutDir: true },
|
||||
server: command === "serve" ? {
|
||||
host: process.env.HWPOD_WEB_HOST || "0.0.0.0",
|
||||
port: requiredPort(process.env.HWPOD_WEB_PORT),
|
||||
strictPort: true,
|
||||
proxy: { "/v1": { target: apiUrl, changeOrigin: false }, "/health/ready": { target: apiUrl, changeOrigin: false } }
|
||||
},
|
||||
proxy: { "/v1": { target: requiredApiUrl(), changeOrigin: false }, "/health/ready": { target: requiredApiUrl(), changeOrigin: false } }
|
||||
} : undefined,
|
||||
plugins: [healthPlugin()]
|
||||
});
|
||||
}));
|
||||
|
||||
function healthPlugin(): Plugin { return { name: "hwpod-native-health", configureServer(server) { server.middlewares.use((request, response, next) => { if (request.url !== "/health/live") return next(); response.statusCode = 200; response.setHeader("content-type", "application/json"); response.end(JSON.stringify({ ok: true, serviceId: "hwlab-hwpod-web", status: "live", valuesPrinted: false })); }); } }; }
|
||||
function requiredApiUrl() { const value = process.env.HWPOD_NATIVE_API_URL; if (!value) throw new Error("HWPOD_NATIVE_API_URL is required"); return value; }
|
||||
function requiredPort(value: string | undefined) { const port = Number.parseInt(String(value ?? ""), 10); if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("HWPOD_WEB_PORT is required and must be valid"); return port; }
|
||||
|
||||
Reference in New Issue
Block a user