Adds a one-command packager (`npm run package:relay`) that produces a
self-contained zip ready to transfer into the data center and start
with `docker compose up -d --build`. Three commands on the DC host:
unzip, edit .env, docker compose up.
Why a packager instead of `docker build` in the repo:
The agent's index.js imports the shared cisco-dect + httpDigestAuth
modules via `../integrations/...` paths, so a naive
`docker build dect-relay-agent/` would fail because those files live
outside the build context. The packager copies them into a
`workspace/` tree inside the bundle so the Dockerfile sees them as
local paths without any source rewriting.
Docker artifacts (in dect-relay-agent/):
- Dockerfile: multi-stage node:20-alpine build (~55MB final image),
non-root `dect` user (UID/GID 1500), tini as PID 1 for clean
SIGTERM propagation to node's graceful-shutdown path,
`npm install --omit=dev --ignore-scripts` in the deps stage.
- docker-compose.yml: restart:unless-stopped, JSON log rotation
(10MB × 5 files), pgrep-based health check. No `ports:` block
because the agent is outbound-only (dials the bot).
- .dockerignore: defensive — the bundle already excludes cruft, but
this hardens against a stray manual build.
Packager (scripts/packageDectRelayAgent.js):
- Assembles agent code + shared modules + deploy artifacts into a
timestamped staging dir (.package-relay-tmp/, git-ignored).
- Generates a bundle README with three-command deploy instructions,
ongoing-ops table, no-internet-DC fallback (docker save/load), and
troubleshooting for the most common failure modes.
- Generates BUNDLE_INFO.txt with build metadata (git sha + dirty
flag + timestamp + size) so the DC operator can trace deployed
bundles back to source.
- Emits `dist/dect-relay-agent-bundle-<YYYYMMDD-HHMMSS>.zip` (30KB).
- Cleans staging in a finally block so failed runs don't leak.
Bundle layout (matches Dockerfile expectations):
dect-relay-agent-bundle-<version>/
Dockerfile, docker-compose.yml, .dockerignore
.env.example, README.md, BUNDLE_INFO.txt
workspace/dect-relay-agent/{package.json, index.js}
workspace/integrations/cisco-dect/{client,probes,statusXml}.js
workspace/utils/httpDigestAuth.js
Wiring:
- package.json: new `package:relay` and `test` npm scripts.
- .gitignore: `scripts/` changed to `scripts/*` so `!scripts/
packageDectRelayAgent.js` can re-include just the packager
(git forbids re-including files under a fully-excluded directory,
hence the glob form).
- dect-relay-agent/README.md: rewrites deployment section to show
the Docker path as the recommended production route, with the
node-directly path kept for local dev.
Verified end-to-end: `npm run package:relay` produces a valid zip
that unpacks to the expected layout in <2s. All 113 existing tests
still pass.
139 lines
5.5 KiB
Markdown
139 lines
5.5 KiB
Markdown
# DECT Relay Agent
|
|
|
|
Bridges the CollabSupport bot (public cloud) to Cisco DBS-210 DECT base stations on the private `10.0.0.0/8` corporate network.
|
|
|
|
## Why it exists
|
|
|
|
The bot process runs in the public cloud and can't reach `10.x`. This agent runs inside the data center, dials outbound over WSS to the bot, and executes any DECT command (collect status, reboot, factory-reset, etc.) the bot pushes to it.
|
|
|
|
Only one agent is expected to run at a time. If a second agent connects, the bot assumes it's a legitimate restart, closes the old socket, and adopts the new one.
|
|
|
|
## Prerequisites
|
|
|
|
- Node.js ≥ 20
|
|
- Route from the agent host to `10.0.0.0/8` on TCP 443
|
|
- Route from the agent host to the bot's public HTTPS endpoint
|
|
- The DECT serviceability password (Control Hub → Calling → Features → DECT Networks → Manage → Manage DECT serviceability password)
|
|
|
|
## Deploy paths
|
|
|
|
There are two ways to run this. Pick one based on where you're deploying.
|
|
|
|
### 1. Docker container in the data center (recommended for production)
|
|
|
|
Run the packager on your dev machine to produce a self-contained zip that includes the shared modules the agent imports:
|
|
|
|
```bash
|
|
# From the parent repo root:
|
|
npm run package:relay
|
|
# → writes dist/dect-relay-agent-bundle-<YYYYMMDD-HHMMSS>.zip
|
|
```
|
|
|
|
Transfer the zip to the DC host and follow the bundle's own `README.md` — three commands (`unzip`, edit `.env`, `docker compose up -d --build`).
|
|
|
|
If your DC host can't reach the npm registry, see the "No-internet DC option" section in the bundle's README — you can build the image on an internet-connected machine, `docker save` it to a tarball, and ship that instead.
|
|
|
|
### 2. Direct node process (dev + local iteration)
|
|
|
|
```bash
|
|
cd dect-relay-agent
|
|
npm install
|
|
```
|
|
|
|
`ws`, `axios`, and `dotenv` are the only runtime dependencies. The agent imports the shared `integrations/cisco-dect/` modules from the parent repo via relative paths, so the parent workspace must be present on disk.
|
|
|
|
## Configure
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
$EDITOR .env
|
|
```
|
|
|
|
Required values:
|
|
|
|
| Var | Meaning |
|
|
|---|---|
|
|
| `DECT_RELAY_BOT_URL` | Full WSS URL to the bot's DECT relay endpoint (`wss://your-bot-host/dect-relay/ws`) |
|
|
| `DECT_RELAY_AGENT_TOKEN` | Shared bearer token — MUST match the bot's `DECT_RELAY_AGENT_TOKEN` exactly |
|
|
| `DECT_ADMIN_USER` | Usually `admin` |
|
|
| `DECT_ADMIN_PASSWORD` | Fleet-wide serviceability password |
|
|
|
|
Generate a fresh token: `openssl rand -hex 32`. Rotate on both sides at once — the bot compares tokens with `timingSafeEqual` and will reject any drift with a 401 on the WSS upgrade.
|
|
|
|
## Run
|
|
|
|
```bash
|
|
npm start
|
|
```
|
|
|
|
You should see:
|
|
|
|
```
|
|
[startup] dect-relay-agent v0.1.0 — hostname=..., bot=wss://...
|
|
[connect] Dialing wss://.../dect-relay/ws
|
|
[connect] Connected — sending hello
|
|
```
|
|
|
|
And on the bot side:
|
|
|
|
```
|
|
[dect:relay-hub] Agent connected from ...
|
|
[dect:relay-hub] Agent hello: version=0.1.0 host=... caps=collect,reboot,...
|
|
```
|
|
|
|
## Wire protocol
|
|
|
|
All frames are JSON, one per WebSocket message.
|
|
|
|
**Agent → Bot on connect:**
|
|
```json
|
|
{ "type": "hello",
|
|
"agentVersion": "0.1.0",
|
|
"hostname": "dc-dect-relay-01",
|
|
"capabilities": ["collect","reboot","force-reboot","reboot-chain",
|
|
"force-reboot-chain","factory-reset","reconfigure-tree"] }
|
|
```
|
|
|
|
**Bot → Agent (command):**
|
|
```json
|
|
{ "id": "cmd_<uuid>", "type": "collect", "baseIp": "10.4.11.87" }
|
|
{ "id": "cmd_<uuid>", "type": "reboot", "baseIp": "10.4.11.87" }
|
|
```
|
|
|
|
**Agent → Bot (reply):**
|
|
```json
|
|
{ "id": "cmd_<uuid>", "ok": true, "elapsedMs": 812,
|
|
"result": { "parsed": { ... }, "verdict": { "healthy": true, ... } } }
|
|
|
|
{ "id": "cmd_<uuid>", "ok": false,
|
|
"error": { "code": "DIGEST_401", "message": "Base rejected credentials" } }
|
|
```
|
|
|
|
**Heartbeat (both directions, every 30s):**
|
|
```json
|
|
{ "type": "ping", "at": 1720000000000 }
|
|
{ "type": "pong", "at": 1720000000000 }
|
|
```
|
|
|
|
The bot terminates the socket if no `pong` arrives within 90s; the agent auto-reconnects with exponential backoff (1s / 2s / 4s / … capped at 30s + 0-1000ms jitter).
|
|
|
|
## Safety guarantees
|
|
|
|
- DECT admin credentials NEVER leave this agent. The bot only knows the WSS bearer token.
|
|
- All mutating actions (reboot, factory-reset, reconfigure-tree) are only executed when the bot explicitly issues the corresponding command frame. The agent has no autonomous logic.
|
|
- The agent enforces no policy — the bot decides who can reboot what. See the bot's audit log for the full record of actions taken (`igmp:audit` style scopes in daily log files).
|
|
- The agent quarantines mutating actions from probes via the exact same safety model as the CLI tool (`integrations/cisco-dect/probes.js` — GET-triggered actions are only reachable via explicit `trigger*` helpers, never via a generic path fetcher).
|
|
|
|
## What's in this folder
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `index.js` | Agent entrypoint — WSS client + command dispatcher |
|
|
| `package.json` | Deps: `ws`, `axios`, `dotenv` |
|
|
| `.env.example` | Annotated env template |
|
|
| `README.md` | This file |
|
|
| `Dockerfile` | Multi-stage alpine build used by the deploy bundle |
|
|
| `docker-compose.yml` | One-command deploy on the DC host |
|
|
| `.dockerignore` | Defensive; the packager already excludes cruft |
|
|
|
|
The Dockerfile is intentionally NOT designed to be built from this folder directly (`docker build dect-relay-agent/` will fail — the shared modules live one level up and are outside the build context). Always build from a bundle produced by `npm run package:relay`, which assembles a `workspace/` tree that makes the shared imports resolvable inside the build context.
|