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.
78 lines
3.2 KiB
Docker
78 lines
3.2 KiB
Docker
# syntax=docker/dockerfile:1.6
|
|
#
|
|
# DECT Relay Agent — production container image
|
|
#
|
|
# Build context: the bundle produced by scripts/packageDectRelayAgent.js.
|
|
# The bundle contains a `workspace/` directory that mirrors just enough of
|
|
# the parent repo to satisfy the agent's `../integrations/...` and
|
|
# `../utils/...` imports without any source rewriting:
|
|
#
|
|
# workspace/
|
|
# dect-relay-agent/ ← WORKDIR at runtime
|
|
# package.json
|
|
# index.js
|
|
# integrations/cisco-dect/{client,probes,statusXml}.js
|
|
# utils/httpDigestAuth.js
|
|
#
|
|
# Building this Dockerfile in the raw repo (`docker build dect-relay-agent/`)
|
|
# WILL NOT WORK — the shared modules live one directory up and would be
|
|
# outside the build context. Always build from a bundle produced by the
|
|
# packager.
|
|
|
|
# ── Stage 1: install prod deps ──────────────────────────────────────
|
|
# node:20-alpine keeps the final image ~55MB. Alpine's musl libc has
|
|
# been fine for this agent's plain JS + axios + ws footprint (no
|
|
# native modules) but if you ever add one that needs glibc, switch to
|
|
# node:20-slim.
|
|
FROM node:20-alpine AS deps
|
|
WORKDIR /build
|
|
|
|
# Only copy the agent's manifest first so this layer caches across
|
|
# code-only changes.
|
|
COPY workspace/dect-relay-agent/package.json ./package.json
|
|
|
|
# `npm install --omit=dev` because there's no committed lockfile
|
|
# (the agent has three dependencies; every deploy resolving the same
|
|
# `^` ranges is acceptable for this operational tool). Add
|
|
# --ignore-scripts to refuse arbitrary lifecycle-script execution from
|
|
# the registry — none of our current deps use lifecycle scripts.
|
|
RUN npm install --omit=dev --ignore-scripts \
|
|
&& npm cache clean --force
|
|
|
|
# ── Stage 2: runtime ────────────────────────────────────────────────
|
|
FROM node:20-alpine AS runtime
|
|
|
|
# tini gives us proper PID-1 signal handling (SIGTERM propagates
|
|
# cleanly to node so our graceful shutdown path in index.js actually
|
|
# runs on `docker stop`).
|
|
RUN apk add --no-cache tini
|
|
|
|
# Non-root user. UID/GID pinned so bind-mounted volumes (if any) are
|
|
# predictable across hosts.
|
|
RUN addgroup -S -g 1500 dect \
|
|
&& adduser -S -u 1500 -G dect -H -s /sbin/nologin dect
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy the shared workspace tree — the agent's imports of
|
|
# `../integrations/...` and `../utils/...` resolve exactly as they do
|
|
# in the source repo. See the bundle layout comment at the top of
|
|
# this file.
|
|
COPY --chown=dect:dect workspace/ ./
|
|
|
|
# Bring in the deps that stage 1 resolved.
|
|
COPY --from=deps --chown=dect:dect /build/node_modules ./dect-relay-agent/node_modules
|
|
|
|
USER dect
|
|
WORKDIR /app/dect-relay-agent
|
|
|
|
# Runtime config comes from `docker compose` (--env-file .env) or
|
|
# `docker run --env-file ...`. Never bake secrets into the image.
|
|
# The agent process itself validates required vars and exits 1 if any
|
|
# are missing (see assertConfig() in index.js).
|
|
|
|
# tini reaps zombies + forwards SIGTERM. `-g` puts tini in the same
|
|
# process group as node so `docker stop` also delivers SIGTERM to
|
|
# child processes if any are spawned in the future.
|
|
ENTRYPOINT ["/sbin/tini", "-g", "--"]
|
|
CMD ["node", "index.js"]
|