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
|
port: 80
|
||||||
targetPort: 8080
|
targetPort: 8080
|
||||||
nodePort: 32009
|
nodePort: 32009
|
||||||
|
- name: hwlab-hwpod-web-public
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: hwlab-hwpod-web
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
nodePort: 32012
|
||||||
frp:
|
frp:
|
||||||
server:
|
server:
|
||||||
address: 82.156.23.220
|
address: 82.156.23.220
|
||||||
@@ -515,6 +521,9 @@ lanes:
|
|||||||
- hwlab-harnessrl-worker
|
- hwlab-harnessrl-worker
|
||||||
- hwlab-workbench-api
|
- hwlab-workbench-api
|
||||||
- hwlab-workbench-worker
|
- hwlab-workbench-worker
|
||||||
|
- hwlab-hwpod-api
|
||||||
|
- hwlab-hwpod-worker
|
||||||
|
- hwlab-hwpod-web
|
||||||
- hwlab-cloud-web
|
- hwlab-cloud-web
|
||||||
- hwlab-gateway
|
- hwlab-gateway
|
||||||
- hwlab-edge-proxy
|
- hwlab-edge-proxy
|
||||||
@@ -530,6 +539,9 @@ lanes:
|
|||||||
hwlab-harnessrl-worker: deploy/runtime/boot/hwlab-harnessrl-worker.sh
|
hwlab-harnessrl-worker: deploy/runtime/boot/hwlab-harnessrl-worker.sh
|
||||||
hwlab-workbench-api: deploy/runtime/boot/hwlab-workbench-api.sh
|
hwlab-workbench-api: deploy/runtime/boot/hwlab-workbench-api.sh
|
||||||
hwlab-workbench-worker: deploy/runtime/boot/hwlab-workbench-worker.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-cloud-web: deploy/runtime/boot/hwlab-cloud-web.sh
|
||||||
hwlab-gateway: deploy/runtime/boot/hwlab-gateway.sh
|
hwlab-gateway: deploy/runtime/boot/hwlab-gateway.sh
|
||||||
hwlab-edge-proxy: deploy/runtime/boot/hwlab-edge-proxy.sh
|
hwlab-edge-proxy: deploy/runtime/boot/hwlab-edge-proxy.sh
|
||||||
@@ -743,6 +755,61 @@ lanes:
|
|||||||
env:
|
env:
|
||||||
TZ: Asia/Shanghai
|
TZ: Asia/Shanghai
|
||||||
observable: true
|
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:
|
hwlab-cloud-web:
|
||||||
runtimeKind: cloud-web
|
runtimeKind: cloud-web
|
||||||
entrypoint: web/hwlab-cloud-web/index.html
|
entrypoint: web/hwlab-cloud-web/index.html
|
||||||
@@ -867,7 +934,7 @@ lanes:
|
|||||||
bootConfig: &v03BootConfig
|
bootConfig: &v03BootConfig
|
||||||
template: default
|
template: default
|
||||||
overrides: {}
|
overrides: {}
|
||||||
configMaps:
|
configMaps: &v03ConfigMaps
|
||||||
- name: hwlab-v03-hwpod-preinstalled-specs
|
- name: hwlab-v03-hwpod-preinstalled-specs
|
||||||
labels:
|
labels:
|
||||||
hwlab.pikastech.local/config-kind: hwpod-preinstalled-specs
|
hwlab.pikastech.local/config-kind: hwpod-preinstalled-specs
|
||||||
@@ -902,11 +969,33 @@ lanes:
|
|||||||
probeName: MicroLink CMSIS-DAP
|
probeName: MicroLink CMSIS-DAP
|
||||||
programBackend: keil-headless
|
programBackend: keil-headless
|
||||||
autoBindUvoptx: true
|
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:
|
ioProbe:
|
||||||
uart:
|
uart:
|
||||||
id: uart/1
|
id: uart/1
|
||||||
port: COM4
|
port: COM4
|
||||||
baudrate: 921600
|
baudrate: 921600
|
||||||
|
probes:
|
||||||
|
main41.ai0.current:
|
||||||
|
endpointRef: main41
|
||||||
|
quantity: current
|
||||||
|
unit: mA
|
||||||
|
path: hardware/ai/0
|
||||||
|
valuePath: ai_current_mA
|
||||||
nodeBinding:
|
nodeBinding:
|
||||||
nodeId: node-d601-f103-v2
|
nodeId: node-d601-f103-v2
|
||||||
nodeType: pc-host
|
nodeType: pc-host
|
||||||
@@ -1172,6 +1261,58 @@ lanes:
|
|||||||
HWLAB_KAFKA_EVENT_TOPIC: hwlab.event.v1
|
HWLAB_KAFKA_EVENT_TOPIC: hwlab.event.v1
|
||||||
HWLAB_KAFKA_CLIENT_ID: hwlab-v03-workbench-worker
|
HWLAB_KAFKA_CLIENT_ID: hwlab-v03-workbench-worker
|
||||||
OTEL_SERVICE_NAME: hwlab-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:
|
production:
|
||||||
name: Production
|
name: Production
|
||||||
node: NC01
|
node: NC01
|
||||||
@@ -1213,6 +1354,7 @@ lanes:
|
|||||||
envReuseServices: *v03EnvReuseServices
|
envReuseServices: *v03EnvReuseServices
|
||||||
bootScripts: *v03BootScripts
|
bootScripts: *v03BootScripts
|
||||||
serviceDeclarations: *v03ServiceDeclarations
|
serviceDeclarations: *v03ServiceDeclarations
|
||||||
|
configMaps: *v03ConfigMaps
|
||||||
services:
|
services:
|
||||||
- serviceId: hwlab-cloud-api
|
- serviceId: hwlab-cloud-api
|
||||||
shutdownDrain:
|
shutdownDrain:
|
||||||
@@ -1307,6 +1449,58 @@ lanes:
|
|||||||
TASKTREE_TEMPORAL_TASK_QUEUE: hwlab-production-tasktree
|
TASKTREE_TEMPORAL_TASK_QUEUE: hwlab-production-tasktree
|
||||||
TASKTREE_WORKER_HEALTH_PORT: "6674"
|
TASKTREE_WORKER_HEALTH_PORT: "6674"
|
||||||
OTEL_SERVICE_NAME: hwlab-tasktree-worker
|
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
|
envRecipe: *v03EnvRecipe
|
||||||
bootConfig: *v03BootConfig
|
bootConfig: *v03BootConfig
|
||||||
services:
|
services:
|
||||||
|
|||||||
@@ -2,6 +2,48 @@
|
|||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "List",
|
"kind": "List",
|
||||||
"items": [
|
"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",
|
"apiVersion": "v1",
|
||||||
"kind": "Service",
|
"kind": "Service",
|
||||||
|
|||||||
@@ -2,6 +2,78 @@
|
|||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"kind": "List",
|
"kind": "List",
|
||||||
"items": [
|
"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",
|
"apiVersion": "apps/v1",
|
||||||
"kind": "Deployment",
|
"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 namespace = String(env.HWPOD_TEMPORAL_NAMESPACE ?? "unidesk").trim();
|
||||||
const taskQueue = String(env.HWPOD_TEMPORAL_TASK_QUEUE ?? "hwlab-v03-hwpod").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 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 (!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");
|
if (!runtimeApiAuthorization) throw codedError("hwpod_runtime_api_authorization_required", "HWPOD_RUNTIME_API_AUTHORIZATION is required");
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import { readdirSync, readFileSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
import pg from "pg";
|
import pg from "pg";
|
||||||
|
import { parse as parseYaml } from "yaml";
|
||||||
|
|
||||||
import { buildPostgresPoolConfig, postgresSslModeFromConnectionString } from "../db/runtime-store-core.ts";
|
import { buildPostgresPoolConfig, postgresSslModeFromConnectionString } from "../db/runtime-store-core.ts";
|
||||||
import { normalizeHwpodSpec } from "./spec-document.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 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 table = postgresIdentifier(requiredEnv(env, "HWPOD_SPEC_DATABASE_TABLE"), "HWPOD_SPEC_DATABASE_TABLE");
|
||||||
const connectionTimeoutMs = positiveIntegerEnv(env, "HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS");
|
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 });
|
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 };
|
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 [];
|
if (!String(raw ?? "").trim()) return [];
|
||||||
let value: unknown;
|
let value: unknown;
|
||||||
try { value = JSON.parse(String(raw)); }
|
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[]) {
|
function normalizeBuiltIns(inputs: BuiltInInput[]) {
|
||||||
const records = new Map<string, any>();
|
const records = new Map<string, any>();
|
||||||
for (const input of inputs) {
|
for (const input of inputs) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"check:tsc-strict": "bun run deps --quiet && bun run scripts/tsc-check.ts --strict",
|
"check:tsc-strict": "bun run deps --quiet && bun run scripts/tsc-check.ts --strict",
|
||||||
"typecheck": "bun run deps --quiet && vue-tsc --noEmit",
|
"typecheck": "bun run deps --quiet && vue-tsc --noEmit",
|
||||||
"build": "bun run deps --quiet && bun run scripts/build.ts",
|
"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",
|
"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": "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"
|
"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 path from "node:path";
|
||||||
import { defineConfig, type Plugin } from "vite";
|
import { defineConfig, type Plugin } from "vite";
|
||||||
|
|
||||||
const apiUrl = process.env.HWPOD_NATIVE_API_URL;
|
export default defineConfig(({ command }) => ({
|
||||||
if (!apiUrl) throw new Error("HWPOD_NATIVE_API_URL is required");
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
root: path.resolve(import.meta.dirname, "../hwpod"),
|
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",
|
host: process.env.HWPOD_WEB_HOST || "0.0.0.0",
|
||||||
port: requiredPort(process.env.HWPOD_WEB_PORT),
|
port: requiredPort(process.env.HWPOD_WEB_PORT),
|
||||||
strictPort: true,
|
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()]
|
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 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; }
|
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