appspace/docker-compose.yml
jmcqueen 025b70de56 Initial commit: Appspace + Webex alerting bot
Node/Express service that:
- Receives Appspace outbound webhooks, enriches with Workspace ONE MDM
  data (matched by serial), and posts Adaptive Card alerts to Webex.
- Runs a Webex bot in WebSocket mode with two commands:
    * `offline [filter]`  - lists currently offline / lost / failed
      Appspace devices, enriched with per-device MDM facts + console links.
    * `restart-offline [filter]` - sends WS1 SoftReset (reboot) to every
      currently-offline device that has a WS1 record. Capped at 50 per
      invocation with bounded concurrency to protect the WS1 API.

Notes on hardening already applied:
- In-flight promise coalescing in mdm.js and index.js so burst webhook
  traffic can't stampede the WS1 token / device-cache refresh or the
  Appspace token refresh.
- Structured logger that serializes Error instances (message, stack,
  code, axios response.status/data) instead of stringifying to "{}".
- Webex 7439-char message-limit handling: `offline` builds its body
  incrementally against a character budget and reports accurate
  "N more not shown" truncation.
- Uses string phrases for `framework.hears(...)` so the framework's
  `(^| )phrase($| )` wrapper handles group-space @mentions correctly,
  and a shared `extractFilterArg()` helper so filter parsing works
  identically in DMs and mentioned messages.

Config, Docker, smoke-test profile, and healthcheck included.
Secrets are managed via `.env` (gitignored); see `.env.example`.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 17:43:53 -04:00

51 lines
No EOL
2.1 KiB
YAML

services:
app-dev:
build: .
container_name: appspace-webex-dev
ports:
- "1889:3000" # host 1889 → container 3000 (app always listens on 3000 inside)
env_file: .env.dev
environment:
- PORT=3000 # force internal listen port (overrides any PORT in .env.dev for container)
volumes:
- .:/app
- /app/node_modules
command: npm run dev
user: root # dev bind mounts often require root for host uid/perms; prod image uses non-root
restart: unless-stopped
profiles: ["dev"]
app-prod:
build: .
container_name: appspace-webex-prod
ports:
- "1889:3000" # host 1889 → container 3000 (app always listens on 3000 inside)
env_file: .env
environment:
- PORT=3000 # force internal listen port (overrides any PORT in .env for container)
restart: unless-stopped
# No `profiles:` key on this service, so `docker compose up -d` (or `docker compose up -d app-prod`) starts it by default.
# Use `docker compose --profile dev up -d app-dev` for development (only starts the dev service).
# Inherits non-root USER node from Dockerfile for security hardening
# Dockerfile HEALTHCHECK is automatically used by Docker
# Smoke test service: starts the prod image with minimal dummy envs
# (required vars are validated at startup). Used by `npm run docker:smoke`
# to verify the container builds, starts, and /health responds.
smoke-test:
build: .
container_name: appspace-smoke-test
environment:
- PORT=3000
- NODE_ENV=production
# Dummy values for required env vars (validation happens early)
- WEBEX_BOT_TOKEN=smoke-test-bot-token
- WEBEX_ROOM_ID=smoke-test-room-id
- APPSPACE_INSTANCE_URL=https://smoke.example.com
- APPSPACE_SUBJECT_ID=smoke-subject
- APPSPACE_REFRESH_TOKEN=smoke-refresh-token
- APPSPACE_API_BASE_URL=https://smoke.example.com
- SMOKE_TEST=true
# No ports published; we use docker inspect for the built-in healthcheck status.
profiles: ["smoke"]
# Uses the hardened Dockerfile (non-root, healthcheck, etc.)