feat(device-plane): add canonical Device Manager runtime
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"schemaVersion": "nodedc.device-plane.device-manager-control-plane.v1",
|
||||
"action": "activate",
|
||||
"service": "device-manager",
|
||||
"publicIngress": "reverse-proxy-only",
|
||||
"deviceCoreManagementApi": "file-token-authenticated",
|
||||
"launcherTrust": "file-token-scoped-to-device-core-handoff",
|
||||
"commandTransport": "disabled",
|
||||
"gelios": "untouched"
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
services:
|
||||
device-control-core:
|
||||
environment:
|
||||
DEVICE_MANAGEMENT_API_ENABLED: "true"
|
||||
DEVICE_MANAGEMENT_CORE_TOKEN_FILE: /run/nodedc-secrets/management-core-token
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /volume1/docker/nodedc-device-plane/secrets/management-core-token
|
||||
target: /run/nodedc-secrets/management-core-token
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
|
||||
device-manager:
|
||||
image: nodedc/device-manager:local
|
||||
pull_policy: never
|
||||
build:
|
||||
context: ./services/device-manager
|
||||
restart: unless-stopped
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=16m,mode=1777
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
HOST: 0.0.0.0
|
||||
PORT: "18122"
|
||||
NODEDC_DEVICE_MANAGER_AUTH_REQUIRED: "true"
|
||||
NODEDC_DEVICE_MANAGER_COOKIE_SECURE: "true"
|
||||
NODEDC_DEVICE_MANAGER_LOCAL_PREVIEW: "false"
|
||||
NODEDC_DEVICE_MANAGER_SERVICE_SLUG: device-core
|
||||
NODEDC_LAUNCHER_BASE_URL: https://hub.nodedc.ru
|
||||
NODEDC_LAUNCHER_INTERNAL_URL: http://launcher:5173
|
||||
NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE: /run/nodedc-secrets/device-core-internal-token
|
||||
NODEDC_DEVICE_CORE_INTERNAL_URL: http://device-control-core:18120
|
||||
NODEDC_DEVICE_CORE_TOKEN_FILE: /run/nodedc-secrets/management-core-token
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /volume1/docker/nodedc-platform/secrets/device-core-internal-token
|
||||
target: /run/nodedc-secrets/device-core-internal-token
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
- type: bind
|
||||
source: /volume1/docker/nodedc-device-plane/secrets/management-core-token
|
||||
target: /run/nodedc-secrets/management-core-token
|
||||
read_only: true
|
||||
bind:
|
||||
create_host_path: false
|
||||
expose:
|
||||
- "18122"
|
||||
networks:
|
||||
- device-plane-private
|
||||
- platform-edge
|
||||
depends_on:
|
||||
device-control-core:
|
||||
condition: service_healthy
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- node
|
||||
- -e
|
||||
- fetch('http://127.0.0.1:18122/healthz').then(r=>r.json()).then(v=>{if(!v.ok||!v.authRequired||!v.deviceCoreConfigured)process.exit(1)}).catch(()=>process.exit(1))
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
platform-edge:
|
||||
external: true
|
||||
name: nodedc-platform_edge
|
||||
+13
-5
@@ -5,7 +5,7 @@ import test from "node:test";
|
||||
const appUrl = new URL("../src/app.mjs", import.meta.url);
|
||||
const serverUrl = new URL("../src/server.mjs", import.meta.url);
|
||||
const repositoryUrl = new URL("../src/postgres-repository.mjs", import.meta.url);
|
||||
const composeUrl = new URL("../../../docker-compose.device-plane.yml", import.meta.url);
|
||||
const managerComposeUrl = new URL("../../../docker-compose.device-manager.yml", import.meta.url);
|
||||
|
||||
test("management surface is internal, POST-only and disabled by default", async () => {
|
||||
const source = await readFile(appUrl, "utf8");
|
||||
@@ -22,14 +22,22 @@ test("management surface is internal, POST-only and disabled by default", async
|
||||
assert.doesNotMatch(source, /device-commands:(?:plan|confirm|dispatch)/);
|
||||
});
|
||||
|
||||
test("management token remains file-backed and is not enabled by current Compose", async () => {
|
||||
test("management API is enabled only through a runner-owned file token", async () => {
|
||||
const server = await readFile(serverUrl, "utf8");
|
||||
const compose = await readFile(composeUrl, "utf8");
|
||||
const compose = await readFile(managerComposeUrl, "utf8");
|
||||
|
||||
assert.match(server, /DEVICE_MANAGEMENT_API_ENABLED/);
|
||||
assert.match(server, /DEVICE_MANAGEMENT_CORE_TOKEN_FILE/);
|
||||
assert.doesNotMatch(compose, /DEVICE_MANAGEMENT_API_ENABLED/);
|
||||
assert.doesNotMatch(compose, /DEVICE_MANAGEMENT_CORE_TOKEN_FILE/);
|
||||
assert.match(compose, /DEVICE_MANAGEMENT_API_ENABLED: "true"/);
|
||||
assert.match(
|
||||
compose,
|
||||
/DEVICE_MANAGEMENT_CORE_TOKEN_FILE: \/run\/nodedc-secrets\/management-core-token/,
|
||||
);
|
||||
assert.match(
|
||||
compose,
|
||||
/source: \/volume1\/docker\/nodedc-device-plane\/secrets\/management-core-token/,
|
||||
);
|
||||
assert.doesNotMatch(compose, /DEVICE_MANAGEMENT_CORE_TOKEN:\s/);
|
||||
});
|
||||
|
||||
test("repository pins idempotency, audit and last-owner checks inside one transaction", async () => {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=18122
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY server ./server
|
||||
COPY dist ./dist
|
||||
|
||||
USER node
|
||||
|
||||
EXPOSE 18122
|
||||
|
||||
CMD ["node", "server/device-manager-server.mjs"]
|
||||
Reference in New Issue
Block a user