diff --git a/CI.json b/CI.json new file mode 100644 index 00000000..901f9eca --- /dev/null +++ b/CI.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "name": "hwlab-deploy-skeleton-ci", + "profile": "dev", + "commands": [ + { + "name": "contract-check", + "run": "npm run check" + }, + { + "name": "parse-deploy-json", + "run": "node -e \"JSON.parse(require('node:fs').readFileSync('deploy/deploy.json','utf8'))\"" + }, + { + "name": "parse-k8s-json-yaml", + "run": "node -e \"const fs=require('node:fs'); for (const f of fs.readdirSync('deploy/k8s/base').concat(fs.readdirSync('deploy/k8s/dev').map(x=>'../dev/'+x), fs.readdirSync('deploy/k8s/prod').map(x=>'../prod/'+x))) { if (!f.endsWith('.yaml')) continue; const p=f.startsWith('..')?'deploy/k8s/'+f.replace('../',''):'deploy/k8s/base/'+f; JSON.parse(fs.readFileSync(p,'utf8')); }\"" + }, + { + "name": "parse-frp-toml", + "run": "python3 - <<'PY'\nimport pathlib, tomllib\nfor path in pathlib.Path('deploy/frp').glob('*.toml'):\n tomllib.loads(path.read_text())\nPY" + } + ], + "forbidden": [ + "prod-deploy", + "secret-material", + "unidesk-runtime-substitute", + "force-push" + ] +} diff --git a/deploy/README.md b/deploy/README.md index f57e7323..e48ebae4 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -5,3 +5,25 @@ The MVP acceptance environment is DEV only, with endpoint `http://74.48.78.17:6667`. PROD profile fields may be represented in schema for future compatibility, but PROD deployment is not part of MVP acceptance. + +## L5 DEV Skeleton + +- `deploy/deploy.json` is the DEV-only deploy manifest. +- `deploy/k8s/base` contains parseable k3s resource skeletons for the frozen + HWLAB service IDs. +- `deploy/k8s/dev` contains the DEV health contract and endpoint metadata. +- `deploy/k8s/prod` is a disabled placeholder gate only. +- `deploy/frp` describes the D601-to-master reverse link without secrets. +- `deploy/master-edge` describes public edge ownership and health boundaries. + +Next DEV deploy smoke commands, for a separately authorized deployment task: + +```sh +npm run check +node -e "JSON.parse(require('node:fs').readFileSync('deploy/deploy.json','utf8'))" +kubectl apply --dry-run=server -k deploy/k8s/dev +curl -fsS http://74.48.78.17:6667/health/live +``` + +Do not run PROD deployment or substitute UniDesk backend, provider-gateway, or +microservice proxy for HWLAB runtime services. diff --git a/deploy/deploy.json b/deploy/deploy.json index 34378d1b..051e14d0 100644 --- a/deploy/deploy.json +++ b/deploy/deploy.json @@ -1,7 +1,7 @@ { "manifestVersion": "v1", "environment": "dev", - "commitId": "6509a35", + "commitId": "24eb3bf", "namespace": "hwlab-dev", "endpoint": "http://74.48.78.17:6667", "profiles": { @@ -10,42 +10,172 @@ "enabled": true, "namespace": "hwlab-dev", "endpoint": "http://74.48.78.17:6667", - "notes": "HWLAB dev deploy manifest with explicit commitId for repo-local skills artifact" + "notes": "DEV-only k3s deployment skeleton. D601 reaches the master edge through frp reverse tunnel." + }, + "prod": { + "name": "prod", + "enabled": false, + "namespace": "hwlab-prod", + "notes": "Reserved placeholder only. PROD deployment and acceptance are explicitly out of scope." } }, "services": [ { - "serviceId": "hwlab-agent-mgr", - "image": "hwlab-agent-mgr:6509a35", + "serviceId": "hwlab-cloud-api", + "image": "ghcr.io/pikastech/hwlab-cloud-api:24eb3bf", "namespace": "hwlab-dev", - "healthPath": "/health", + "healthPath": "/health/live", "profile": "dev", "replicas": 1, "env": { - "HWLAB_AGENT_SESSION_MODE": "control-plane" + "HWLAB_ENVIRONMENT": "dev", + "HWLAB_PUBLIC_ENDPOINT": "http://74.48.78.17:6667", + "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN": "unidesk-backend,provider-gateway,microservice-proxy" + } + }, + { + "serviceId": "hwlab-cloud-web", + "image": "ghcr.io/pikastech/hwlab-cloud-web:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_ENVIRONMENT": "dev", + "HWLAB_API_BASE_URL": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" + } + }, + { + "serviceId": "hwlab-agent-mgr", + "image": "ghcr.io/pikastech/hwlab-agent-mgr:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_AGENT_SESSION_MODE": "control-plane", + "HWLAB_ENVIRONMENT": "dev" } }, { "serviceId": "hwlab-agent-worker", - "image": "hwlab-agent-worker:6509a35", + "image": "ghcr.io/pikastech/hwlab-agent-worker:24eb3bf", "namespace": "hwlab-dev", - "healthPath": "/health", + "healthPath": "/health/live", "profile": "dev", "replicas": 0, "env": { - "HWLAB_AGENT_SESSION_MODE": "session-scoped" + "HWLAB_AGENT_SESSION_MODE": "session-scoped", + "HWLAB_ENVIRONMENT": "dev" + } + }, + { + "serviceId": "hwlab-gateway", + "image": "ghcr.io/pikastech/hwlab-gateway:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 0, + "env": { + "HWLAB_GATEWAY_MODE": "hardware-boundary", + "HWLAB_ENVIRONMENT": "dev" + } + }, + { + "serviceId": "hwlab-gateway-simu", + "image": "ghcr.io/pikastech/hwlab-gateway-simu:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_GATEWAY_MODE": "simulator", + "HWLAB_BOX_RESOURCES": "res_boxsim_alpha" + } + }, + { + "serviceId": "hwlab-box-simu", + "image": "ghcr.io/pikastech/hwlab-box-simu:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_BOX_ID": "boxsim_alpha", + "HWLAB_ENVIRONMENT": "dev" + } + }, + { + "serviceId": "hwlab-patch-panel", + "image": "ghcr.io/pikastech/hwlab-patch-panel:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_PATCH_PANEL_MODE": "dev-skeleton", + "HWLAB_ENVIRONMENT": "dev" + } + }, + { + "serviceId": "hwlab-router", + "image": "ghcr.io/pikastech/hwlab-router:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_ROUTER_MODE": "edge-reachability", + "HWLAB_EDGE_PROXY_SERVICE": "hwlab-edge-proxy" + } + }, + { + "serviceId": "hwlab-tunnel-client", + "image": "ghcr.io/pikastech/hwlab-tunnel-client:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_TUNNEL_MODE": "frp-client", + "HWLAB_FRP_SERVER_ADDR": "74.48.78.17", + "HWLAB_FRP_PUBLIC_PORT": "6667" + } + }, + { + "serviceId": "hwlab-edge-proxy", + "image": "ghcr.io/pikastech/hwlab-edge-proxy:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 1, + "env": { + "HWLAB_EDGE_LISTEN": "0.0.0.0:6667", + "HWLAB_EDGE_UPSTREAM": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" + } + }, + { + "serviceId": "hwlab-cli", + "image": "ghcr.io/pikastech/hwlab-cli:24eb3bf", + "namespace": "hwlab-dev", + "healthPath": "/health/live", + "profile": "dev", + "replicas": 0, + "env": { + "HWLAB_CLI_ENDPOINT": "http://74.48.78.17:6667" } }, { "serviceId": "hwlab-agent-skills", - "image": "hwlab-agent-skills:6509a35", + "image": "ghcr.io/pikastech/hwlab-agent-skills:24eb3bf", "namespace": "hwlab-dev", - "healthPath": "/health", + "healthPath": "/health/live", "profile": "dev", "replicas": 1, "env": { - "HWLAB_SKILLS_COMMIT_ID": "6509a35" + "HWLAB_SKILLS_COMMIT_ID": "24eb3bf" } } - ] + ], + "createdAt": "2026-05-21T00:00:00Z" } diff --git a/deploy/frp/README.md b/deploy/frp/README.md new file mode 100644 index 00000000..50eaf013 --- /dev/null +++ b/deploy/frp/README.md @@ -0,0 +1,11 @@ +# FRP DEV Contract + +The DEV reverse link is D601 to master edge through frp. + +- Public DEV endpoint: `http://74.48.78.17:6667` +- `hwlab-tunnel-client` owns the frp client health contract. +- `hwlab-edge-proxy` owns public HTTP ingress on port `6667`. +- No secret values are stored in this repository. Operators must provide auth + material out of band before a real deployment. +- UniDesk backend, provider-gateway, and microservice proxy are not HWLAB runtime + substitutes. diff --git a/deploy/frp/frpc.dev.toml b/deploy/frp/frpc.dev.toml new file mode 100644 index 00000000..b8d1cd6e --- /dev/null +++ b/deploy/frp/frpc.dev.toml @@ -0,0 +1,17 @@ +serverAddr = "74.48.78.17" +serverPort = 7000 +loginFailExit = true + +[[proxies]] +name = "hwlab-dev-edge-proxy" +type = "tcp" +localIP = "hwlab-edge-proxy.hwlab-dev.svc.cluster.local" +localPort = 6667 +remotePort = 6667 + +[[proxies]] +name = "hwlab-dev-tunnel-health" +type = "tcp" +localIP = "hwlab-tunnel-client.hwlab-dev.svc.cluster.local" +localPort = 7402 +remotePort = 7402 diff --git a/deploy/frp/frps.dev.toml b/deploy/frp/frps.dev.toml new file mode 100644 index 00000000..09a032e9 --- /dev/null +++ b/deploy/frp/frps.dev.toml @@ -0,0 +1,10 @@ +bindPort = 7000 +vhostHTTPPort = 6667 + +[[allowPorts]] +start = 6667 +end = 6667 + +[[allowPorts]] +start = 7402 +end = 7402 diff --git a/deploy/k8s/base/kustomization.yaml b/deploy/k8s/base/kustomization.yaml new file mode 100644 index 00000000..8b582a59 --- /dev/null +++ b/deploy/k8s/base/kustomization.yaml @@ -0,0 +1,13 @@ +{ + "apiVersion": "kustomize.config.k8s.io/v1beta1", + "kind": "Kustomization", + "resources": [ + "namespace.yaml", + "services.yaml", + "workloads.yaml" + ], + "commonLabels": { + "app.kubernetes.io/part-of": "hwlab", + "hwlab.pikastech.local/environment": "dev" + } +} diff --git a/deploy/k8s/base/namespace.yaml b/deploy/k8s/base/namespace.yaml new file mode 100644 index 00000000..84f26b84 --- /dev/null +++ b/deploy/k8s/base/namespace.yaml @@ -0,0 +1,11 @@ +{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "hwlab-dev", + "labels": { + "app.kubernetes.io/part-of": "hwlab", + "hwlab.pikastech.local/profile": "dev" + } + } +} diff --git a/deploy/k8s/base/services.yaml b/deploy/k8s/base/services.yaml new file mode 100644 index 00000000..c937fce1 --- /dev/null +++ b/deploy/k8s/base/services.yaml @@ -0,0 +1,308 @@ +{ + "apiVersion": "v1", + "kind": "List", + "items": [ + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-cloud-api", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-cloud-api", + "hwlab.pikastech.local/service-id": "hwlab-cloud-api" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-cloud-api" + }, + "ports": [ + { + "name": "http", + "port": 6667, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-cloud-web", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-cloud-web", + "hwlab.pikastech.local/service-id": "hwlab-cloud-web" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-cloud-web" + }, + "ports": [ + { + "name": "http", + "port": 8080, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-gateway", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-gateway", + "hwlab.pikastech.local/service-id": "hwlab-gateway", + "hwlab.pikastech.local/activation": "manual" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-gateway" + }, + "ports": [ + { + "name": "http", + "port": 7001, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-cli", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-cli", + "hwlab.pikastech.local/service-id": "hwlab-cli", + "hwlab.pikastech.local/activation": "manual" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-cli" + }, + "ports": [ + { + "name": "http", + "port": 7501, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-agent-skills", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-agent-skills", + "hwlab.pikastech.local/service-id": "hwlab-agent-skills" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-agent-skills" + }, + "ports": [ + { + "name": "http", + "port": 7502, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-agent-mgr", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-agent-mgr", + "hwlab.pikastech.local/service-id": "hwlab-agent-mgr" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-agent-mgr" + }, + "ports": [ + { + "name": "http", + "port": 7410, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-gateway-simu", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-gateway-simu", + "hwlab.pikastech.local/service-id": "hwlab-gateway-simu" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-gateway-simu" + }, + "ports": [ + { + "name": "http", + "port": 7101, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-box-simu", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-box-simu", + "hwlab.pikastech.local/service-id": "hwlab-box-simu" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-box-simu" + }, + "ports": [ + { + "name": "http", + "port": 7201, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-patch-panel", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-patch-panel", + "hwlab.pikastech.local/service-id": "hwlab-patch-panel" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-patch-panel" + }, + "ports": [ + { + "name": "http", + "port": 7301, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-router", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-router", + "hwlab.pikastech.local/service-id": "hwlab-router" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-router" + }, + "ports": [ + { + "name": "http", + "port": 7401, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-tunnel-client", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-tunnel-client", + "hwlab.pikastech.local/service-id": "hwlab-tunnel-client" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-tunnel-client" + }, + "ports": [ + { + "name": "http", + "port": 7402, + "targetPort": "http" + } + ] + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "hwlab-edge-proxy", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-edge-proxy", + "hwlab.pikastech.local/service-id": "hwlab-edge-proxy" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "hwlab-edge-proxy" + }, + "ports": [ + { + "name": "http", + "port": 6667, + "targetPort": "http" + } + ] + } + } + ] +} diff --git a/deploy/k8s/base/workloads.yaml b/deploy/k8s/base/workloads.yaml new file mode 100644 index 00000000..a9771535 --- /dev/null +++ b/deploy/k8s/base/workloads.yaml @@ -0,0 +1,758 @@ +{ + "apiVersion": "v1", + "kind": "List", + "items": [ + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-cloud-api", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-cloud-api", + "hwlab.pikastech.local/service-id": "hwlab-cloud-api" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-cloud-api" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-cloud-api", + "hwlab.pikastech.local/service-id": "hwlab-cloud-api" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-cloud-api", + "image": "ghcr.io/pikastech/hwlab-cloud-api:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 6667 + } + ], + "env": [ + { + "name": "HWLAB_ENVIRONMENT", + "value": "dev" + }, + { + "name": "HWLAB_PUBLIC_ENDPOINT", + "value": "http://74.48.78.17:6667" + }, + { + "name": "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN", + "value": "unidesk-backend,provider-gateway,microservice-proxy" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-cloud-web", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-cloud-web", + "hwlab.pikastech.local/service-id": "hwlab-cloud-web" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-cloud-web" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-cloud-web", + "hwlab.pikastech.local/service-id": "hwlab-cloud-web" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-cloud-web", + "image": "ghcr.io/pikastech/hwlab-cloud-web:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 8080 + } + ], + "env": [ + { + "name": "HWLAB_API_BASE_URL", + "value": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-agent-mgr", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-agent-mgr", + "hwlab.pikastech.local/service-id": "hwlab-agent-mgr" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-agent-mgr" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-agent-mgr", + "hwlab.pikastech.local/service-id": "hwlab-agent-mgr" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-agent-mgr", + "image": "ghcr.io/pikastech/hwlab-agent-mgr:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7410 + } + ], + "env": [ + { + "name": "HWLAB_AGENT_SESSION_MODE", + "value": "control-plane" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "hwlab-agent-worker-template", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-agent-worker", + "hwlab.pikastech.local/service-id": "hwlab-agent-worker" + } + }, + "spec": { + "suspend": true, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-agent-worker", + "hwlab.pikastech.local/service-id": "hwlab-agent-worker" + } + }, + "spec": { + "restartPolicy": "Never", + "containers": [ + { + "name": "hwlab-agent-worker", + "image": "ghcr.io/pikastech/hwlab-agent-worker:24eb3bf", + "env": [ + { + "name": "HWLAB_AGENT_SESSION_MODE", + "value": "session-scoped" + } + ] + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-gateway", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-gateway", + "hwlab.pikastech.local/service-id": "hwlab-gateway", + "hwlab.pikastech.local/activation": "manual" + } + }, + "spec": { + "replicas": 0, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-gateway" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-gateway", + "hwlab.pikastech.local/service-id": "hwlab-gateway" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-gateway", + "image": "ghcr.io/pikastech/hwlab-gateway:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7001 + } + ], + "env": [ + { + "name": "HWLAB_GATEWAY_MODE", + "value": "hardware-boundary" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-gateway-simu", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-gateway-simu", + "hwlab.pikastech.local/service-id": "hwlab-gateway-simu" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-gateway-simu" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-gateway-simu", + "hwlab.pikastech.local/service-id": "hwlab-gateway-simu" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-gateway-simu", + "image": "ghcr.io/pikastech/hwlab-gateway-simu:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7101 + } + ], + "env": [ + { + "name": "HWLAB_BOX_RESOURCES", + "value": "res_boxsim_alpha" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-box-simu", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-box-simu", + "hwlab.pikastech.local/service-id": "hwlab-box-simu" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-box-simu" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-box-simu", + "hwlab.pikastech.local/service-id": "hwlab-box-simu" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-box-simu", + "image": "ghcr.io/pikastech/hwlab-box-simu:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7201 + } + ], + "env": [ + { + "name": "HWLAB_BOX_ID", + "value": "boxsim_alpha" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-patch-panel", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-patch-panel", + "hwlab.pikastech.local/service-id": "hwlab-patch-panel" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-patch-panel" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-patch-panel", + "hwlab.pikastech.local/service-id": "hwlab-patch-panel" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-patch-panel", + "image": "ghcr.io/pikastech/hwlab-patch-panel:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7301 + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-router", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-router", + "hwlab.pikastech.local/service-id": "hwlab-router" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-router" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-router", + "hwlab.pikastech.local/service-id": "hwlab-router" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-router", + "image": "ghcr.io/pikastech/hwlab-router:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7401 + } + ], + "env": [ + { + "name": "HWLAB_EDGE_PROXY_SERVICE", + "value": "hwlab-edge-proxy" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-tunnel-client", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-tunnel-client", + "hwlab.pikastech.local/service-id": "hwlab-tunnel-client" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-tunnel-client" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-tunnel-client", + "hwlab.pikastech.local/service-id": "hwlab-tunnel-client" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-tunnel-client", + "image": "ghcr.io/pikastech/hwlab-tunnel-client:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7402 + } + ], + "env": [ + { + "name": "HWLAB_TUNNEL_MODE", + "value": "frp-client" + }, + { + "name": "HWLAB_FRP_SERVER_ADDR", + "value": "74.48.78.17" + }, + { + "name": "HWLAB_FRP_PUBLIC_PORT", + "value": "6667" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-edge-proxy", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-edge-proxy", + "hwlab.pikastech.local/service-id": "hwlab-edge-proxy" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-edge-proxy" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-edge-proxy", + "hwlab.pikastech.local/service-id": "hwlab-edge-proxy" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-edge-proxy", + "image": "ghcr.io/pikastech/hwlab-edge-proxy:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 6667 + } + ], + "env": [ + { + "name": "HWLAB_EDGE_LISTEN", + "value": "0.0.0.0:6667" + }, + { + "name": "HWLAB_EDGE_UPSTREAM", + "value": "http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + }, + { + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "hwlab-cli-template", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-cli", + "hwlab.pikastech.local/service-id": "hwlab-cli", + "hwlab.pikastech.local/activation": "manual" + } + }, + "spec": { + "suspend": true, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-cli", + "hwlab.pikastech.local/service-id": "hwlab-cli" + } + }, + "spec": { + "restartPolicy": "Never", + "containers": [ + { + "name": "hwlab-cli", + "image": "ghcr.io/pikastech/hwlab-cli:24eb3bf", + "env": [ + { + "name": "HWLAB_CLI_ENDPOINT", + "value": "http://74.48.78.17:6667" + } + ] + } + ] + } + } + } + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "hwlab-agent-skills", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-agent-skills", + "hwlab.pikastech.local/service-id": "hwlab-agent-skills" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "hwlab-agent-skills" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "hwlab-agent-skills", + "hwlab.pikastech.local/service-id": "hwlab-agent-skills" + } + }, + "spec": { + "containers": [ + { + "name": "hwlab-agent-skills", + "image": "ghcr.io/pikastech/hwlab-agent-skills:24eb3bf", + "ports": [ + { + "name": "http", + "containerPort": 7502 + } + ], + "env": [ + { + "name": "HWLAB_SKILLS_COMMIT_ID", + "value": "24eb3bf" + } + ], + "readinessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + }, + "livenessProbe": { + "httpGet": { + "path": "/health/live", + "port": "http" + } + } + } + ] + } + } + } + } + ] +} diff --git a/deploy/k8s/dev/health-contract.yaml b/deploy/k8s/dev/health-contract.yaml new file mode 100644 index 00000000..0e3d9ea6 --- /dev/null +++ b/deploy/k8s/dev/health-contract.yaml @@ -0,0 +1,24 @@ +{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "hwlab-dev-health-contract", + "namespace": "hwlab-dev", + "labels": { + "app.kubernetes.io/name": "hwlab-dev-health-contract", + "hwlab.pikastech.local/profile": "dev" + } + }, + "data": { + "endpoint": "http://74.48.78.17:6667", + "cloud-api": "GET /health/live via hwlab-edge-proxy then hwlab-cloud-api:6667", + "cloud-web": "GET /health/live on hwlab-cloud-web:8080; consumes cloud-api only", + "agent": "hwlab-agent-mgr readiness gates worker template creation; hwlab-agent-worker is suspended until a session is scheduled", + "sim": "hwlab-gateway-simu:7101 and hwlab-box-simu:7201 expose /health/live for DEV smoke", + "patch-panel": "hwlab-patch-panel:7301 exposes /health/live and owns patch panel state", + "router": "hwlab-router:7401 exposes /health/live and owns edge reachability routing", + "tunnel-client": "hwlab-tunnel-client:7402 exposes /health/live and owns D601-to-master frp client state", + "edge-proxy": "hwlab-edge-proxy:6667 exposes /health/live and proxies DEV endpoint traffic to hwlab-cloud-api", + "runtime-substitute-policy": "Do not replace HWLAB runtime with UniDesk backend, provider-gateway, or microservice proxy" + } +} diff --git a/deploy/k8s/dev/kustomization.yaml b/deploy/k8s/dev/kustomization.yaml new file mode 100644 index 00000000..dfa0b58e --- /dev/null +++ b/deploy/k8s/dev/kustomization.yaml @@ -0,0 +1,12 @@ +{ + "apiVersion": "kustomize.config.k8s.io/v1beta1", + "kind": "Kustomization", + "namespace": "hwlab-dev", + "resources": [ + "../base", + "health-contract.yaml" + ], + "commonLabels": { + "hwlab.pikastech.local/profile": "dev" + } +} diff --git a/deploy/k8s/prod/kustomization.yaml b/deploy/k8s/prod/kustomization.yaml new file mode 100644 index 00000000..d6297851 --- /dev/null +++ b/deploy/k8s/prod/kustomization.yaml @@ -0,0 +1,7 @@ +{ + "apiVersion": "kustomize.config.k8s.io/v1beta1", + "kind": "Kustomization", + "resources": [ + "prod-disabled.yaml" + ] +} diff --git a/deploy/k8s/prod/prod-disabled.yaml b/deploy/k8s/prod/prod-disabled.yaml new file mode 100644 index 00000000..b2a7549f --- /dev/null +++ b/deploy/k8s/prod/prod-disabled.yaml @@ -0,0 +1,18 @@ +{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "hwlab-prod-disabled", + "namespace": "default", + "labels": { + "app.kubernetes.io/part-of": "hwlab", + "hwlab.pikastech.local/profile": "prod", + "hwlab.pikastech.local/acceptance": "disabled" + } + }, + "data": { + "status": "disabled", + "reason": "PROD deployment and acceptance are out of scope for MVP L5", + "allowed-use": "placeholder gate only" + } +} diff --git a/deploy/master-edge/README.md b/deploy/master-edge/README.md new file mode 100644 index 00000000..6d7d13f9 --- /dev/null +++ b/deploy/master-edge/README.md @@ -0,0 +1,22 @@ +# Master Edge DEV Contract + +The master edge exposes only the DEV acceptance endpoint for this skeleton: + +```text +http://74.48.78.17:6667 +``` + +Health ownership: + +- `hwlab-edge-proxy`: accepts public DEV traffic and exposes `/health/live`. +- `hwlab-router`: owns edge reachability routing and exposes `/health/live`. +- `hwlab-tunnel-client`: owns D601-to-master frp client status and exposes + `/health/live`. +- `hwlab-cloud-api`: receives proxied API traffic and exposes `/health/live`. +- `hwlab-cloud-web`: reads cloud APIs only; it does not control hardware. +- `hwlab-agent-mgr` and `hwlab-agent-worker`: own agent session and worker + session health boundaries. +- `hwlab-gateway-simu`, `hwlab-box-simu`, and `hwlab-patch-panel`: own DEV + simulator and patch-panel health boundaries. + +PROD is intentionally gated off for this task. diff --git a/deploy/master-edge/health-contract.json b/deploy/master-edge/health-contract.json new file mode 100644 index 00000000..12c273ef --- /dev/null +++ b/deploy/master-edge/health-contract.json @@ -0,0 +1,70 @@ +{ + "environment": "dev", + "endpoint": "http://74.48.78.17:6667", + "reverseLink": { + "mode": "frp", + "direction": "d601-to-master", + "client": "hwlab-tunnel-client", + "serverAddress": "74.48.78.17", + "publicPort": 6667 + }, + "contracts": [ + { + "serviceId": "hwlab-edge-proxy", + "owner": "public DEV ingress", + "health": "GET /health/live", + "upstream": "hwlab-cloud-api" + }, + { + "serviceId": "hwlab-router", + "owner": "edge reachability routing", + "health": "GET /health/live" + }, + { + "serviceId": "hwlab-tunnel-client", + "owner": "frp client reverse link", + "health": "GET /health/live" + }, + { + "serviceId": "hwlab-cloud-api", + "owner": "DEV cloud API boundary", + "health": "GET /health/live" + }, + { + "serviceId": "hwlab-cloud-web", + "owner": "web frontend boundary", + "health": "GET /health/live" + }, + { + "serviceId": "hwlab-agent-mgr", + "owner": "agent session scheduling", + "health": "GET /health/live" + }, + { + "serviceId": "hwlab-agent-worker", + "owner": "session-scoped worker execution", + "health": "job exit status or future GET /health/live sidecar" + }, + { + "serviceId": "hwlab-gateway-simu", + "owner": "DEV gateway simulator", + "health": "GET /health/live" + }, + { + "serviceId": "hwlab-box-simu", + "owner": "DEV box simulator", + "health": "GET /health/live" + }, + { + "serviceId": "hwlab-patch-panel", + "owner": "patch panel state", + "health": "GET /health/live" + } + ], + "forbiddenRuntimeSubstitutes": [ + "unidesk-backend", + "provider-gateway", + "microservice-proxy" + ], + "prodAcceptance": false +}