collabSupport/.env.example
jmcqueen bc56b0a0fb Add Cisco DBS-210 DECT base spike (HTTP Digest client + safe probes)
Spike scaffolding for reverse-engineering the local admin UI on a
Cisco DBS-210 DECT base station. Not wired into the bot yet -- the
plan is a status.xml data-collector next, then a per-store relay
that fronts these calls over a websocket back to the bot.

- utils/httpDigestAuth.js: dependency-free HTTP Digest MD5/qop=auth
  header builder + WWW-Authenticate parser. Preserves empty realm,
  which the DBS-210 sends and which most libs silently drop.
- integrations/cisco-dect/client.js: axios wrapper with self-signed
  TLS bypass and a single-shot Digest challenge/response interceptor.
- integrations/cisco-dect/probes.js: verified-safe read paths only in
  READ_PROBE_PATHS. Every mutating path is quarantined in the
  MUTATING_ACTION_PATHS map and exposed only via explicit trigger
  helpers (reboot/force-reboot/reboot-chain/factory-reset/reconfigure-
  tree) that fetch and attach the CSRF token from /main.html. The
  legacy /admin/reboot.htm alias -- which triggered a real reboot
  during our first blind probe -- is intentionally NOT reachable.
- tests/httpDigestAuth.test.js: 6 unit tests, including the RFC 2617
  canonical example and the DBS-210 empty-realm quirk.
- .env.example: adds DECT_TEST_BASE_IP / _USER / _PASSWORD /
  _TIMEOUT_MS for the local test harness (script itself lives under
  scripts/, which stays gitignored).
- .gitignore: adds .dect-samples/ so lab captures don't leak.
2026-07-02 15:30:42 -04:00

246 lines
12 KiB
Text

# =============================================================================
# CollabFinder / CollabSupport Environment Variables
# Copy this file to .env and fill in your values.
# =============================================================================
# -----------------------------------------------------------------------------
# Server
# -----------------------------------------------------------------------------
SERVER_PORT=1800
# Logging level: info (default - clean), debug (verbose, includes per-fetch details)
LOG_LEVEL=info
# Verbose Webex framework debug logs. Default off; auto-enabled when LOG_LEVEL=debug.
# WEBEX_FRAMEWORK_DEBUG=false
# -----------------------------------------------------------------------------
# HTTP API authentication
# -----------------------------------------------------------------------------
# Shared secret required to call destructive /:command HTTP endpoints such as
# /offboarduser, /provision-dect, /provision-vc, /vcmonitor, /bulkavstatuscsv,
# /bulkavswitchcsv, /devicesbymodel. Without it, those endpoints fail-closed
# with HTTP 503 — set this to any high-entropy string (e.g. `openssl rand -hex 32`).
# Callers send the token as `Authorization: Bearer <token>` or `X-API-Token: <token>`.
HTTP_API_TOKEN=
# Optional. When set to "true" / "1" / "yes", the same token is also required
# for read-only endpoints (/avstatus, /phonestatus, /av/devices/build/…,
# /phone/devices/build/…, /api/av/*, etc.). Default = false (those endpoints
# stay open so the bundled dashboards keep working without auth headers).
HTTP_API_REQUIRE_AUTH=false
# -----------------------------------------------------------------------------
# Webex Bot (required)
# -----------------------------------------------------------------------------
# Bot token from developer.webex.com. The framework connects to Webex over
# websockets using this token, so no public ingress / webhook URL is needed.
WEBEX_BOT_TOKEN=your-bot-token-here
# Service App credentials (used by WebexServiceAppAuth for backend Webex API
# calls — people lookup, room operations, etc.). Separate from the bot token.
#
# Required scopes (set when creating the service app at developer.webex.com):
# - spark-admin:people_read (people lookup)
# - identity:tokens_read (offboarduser: list a user's authorizations)
# - identity:tokens_write (offboarduser: revoke a user's authorizations)
# The authorizing admin must also hold Full / User / Device Admin role for the
# token-management calls to succeed.
WEBEX_CLIENT_ID=your-service-app-client-id
WEBEX_CLIENT_SECRET=your-service-app-client-secret
# Path to the rotating service app tokens file (must be writable).
# IMPORTANT: Use a *relative* path (e.g. ./config/...). The same .env works for both:
# - Local runs (resolved against your project root cwd)
# - Docker (resolved against /app inside container; see docker-compose volume mount)
# Do NOT use an absolute host path here — it will break inside the container.
WEBEX_TOKENS_PATH=./config/webex-service-tokens.json
# Optional override for the Webex API base URL (default https://webexapis.com/v1).
# WEBEX_BASE_URL=https://webexapis.com/v1
# -----------------------------------------------------------------------------
# /webexhost — Webex Meetings host license helper
# -----------------------------------------------------------------------------
# Site to evaluate host status against. Default: aeo2go.webex.com.
# WEBEX_HOST_SITE_URL=aeo2go.webex.com
# License ID auto-assigned by `/webexhost <email>` when the user is missing a
# host license on the site. Discover the right id by running `/webexhost list`
# (lists every meeting license on the site with id + remaining seats).
# Until this is set, `/webexhost <email>` will still report status, but the
# confirm-assign step refuses with a clear message pointing at /webexhost list.
WEBEX_HOST_LICENSE_ID=
# -----------------------------------------------------------------------------
# Jira (required for /jira* commands)
# Use JIRA_CLOUD_ID for service accounts / new Atlassian API gateway endpoints:
# JIRA_CLOUD_ID=<uuid-from-atlassian>
# (constructs https://api.atlassian.com/ex/jira/<id>/rest/api/3 ...)
# Otherwise fall back to classic site base:
# JIRA_BASE_URL=https://your-org.atlassian.net
# -----------------------------------------------------------------------------
JIRA_CLOUD_ID=
JIRA_BASE_URL=https://your-org.atlassian.net
JIRA_EMAIL=your-email@company.com
JIRA_API_TOKEN=your-jira-api-token
JIRA_MAX_RESULTS=30
# -----------------------------------------------------------------------------
# Jira Poller (hourly ticket enrichment)
# -----------------------------------------------------------------------------
# Cron poller that scans unassigned tickets in the AV / Comm Services /
# Mobility queue every hour, posts a phone or AV status snapshot as a
# Jira comment on each store-scoped ticket, labels the ticket
# `bot-enriched` so it's not re-processed, and posts a summary of newly
# enriched tickets to a Webex space. Tickets the AI classifier decides
# are out-of-scope get labeled `bot-skipped` (same anti-retry purpose,
# no comment posted) so we don't burn AI tokens re-reading them every
# hour.
#
# Required scopes on JIRA_API_TOKEN: read + write on issues in the
# target projects (comment + edit-labels). The token owner needs "Add
# Comments" and "Edit Issues" permission — a plain read-only integration
# token WILL NOT work.
#
# JIRA_POLLER_ROOM_ID
# Webex space roomId to post the per-poll "N new tickets" summary to.
# Poller stays DISABLED (cron never registered) if unset — safe default
# for dev instances that share the same Jira credentials.
#
# JIRA_POLLER_PRIME_ON_START
# One-shot backlog-prime toggle. Set to `true` for a SINGLE deploy to
# bulk-label every ticket currently matching the poller's JQL as
# `bot-enriched` WITHOUT enriching them or posting a summary. Prevents
# day-one spam from a queue that already has dozens of open tickets.
# Flip back to `false` (or remove) before the next restart or the
# prime pass runs again.
#
# JIRA_STORE_FIELD_ID
# Optional. Numeric custom-field id for the `Store Number` field
# (e.g. `customfield_10042`). If unset, the poller discovers it at
# first use via GET /rest/api/3/field. Set explicitly to skip
# discovery (saves one API call at startup) or when the display
# name resolves ambiguously in your Jira schema.
#
# Note: on tenants where Store Number is an Atlassian Assets object
# reference (not a plain string), the field value the poller reads
# will be an opaque object like {"objectId":"81255"}. The AI
# classifier handles this by extracting the store number from the
# ticket summary / description text instead ("Store 3860 - ..."), so
# the field being unreadable is not fatal.
#
# JIRA_POLLER_MODEL
# Optional model override for the AI ticket classifier. Defaults to
# XAI_MODEL if unset. Classification is a small, deterministic
# structured task (~50-token JSON output per ticket) that doesn't
# need the reasoning depth of the summary model — a cheaper/faster
# model (e.g. `grok-3-mini`) saves noticeable money at scale without
# hurting classification accuracy on the phone/av/skip taxonomy.
# -----------------------------------------------------------------------------
JIRA_POLLER_ROOM_ID=
JIRA_POLLER_PRIME_ON_START=false
JIRA_STORE_FIELD_ID=
JIRA_POLLER_MODEL=
# -----------------------------------------------------------------------------
# xAI / Grok (used for ticket and work order summarization)
# -----------------------------------------------------------------------------
XAI_URL=https://api.x.ai/v1/chat/completions
XAI_API_KEY=your-xai-api-key
XAI_MODEL=grok-2-latest
# -----------------------------------------------------------------------------
# Meraki
# -----------------------------------------------------------------------------
MERAKI_API_KEY=your-meraki-api-key
MERAKI_ORG_ID=your-org-id
# -----------------------------------------------------------------------------
# RED (Digital Signage)
# Comma-separated list of company IDs (one per company tenant).
# -----------------------------------------------------------------------------
RED_BASE_URL=https://api.red.com
RED_CLIENT_ID=your-red-client-id
RED_API_KEY=your-red-api-key
RED_COMPANY_IDS=company-id-1,company-id-2,company-id-3
# -----------------------------------------------------------------------------
# OptiSigns
# -----------------------------------------------------------------------------
OPTISIGN_API_KEY=your-optisigns-api-key
# -----------------------------------------------------------------------------
# DigiCert (for VC provisioning)
# -----------------------------------------------------------------------------
DIGICERT_API_KEY=your-digicert-key
DIGICERT_BASE_URL=https://one.digicert.com
DIGICERT_PROFILE_ID=your-profile-id
DIGICERT_SEAT_EMAIL=your-email@company.com
# -----------------------------------------------------------------------------
# MDM / Workspace ONE (two instances)
# -----------------------------------------------------------------------------
# Standard / Store MDM
WS1_CLIENT_ID=...
WS1_CLIENT_SECRET=...
WS1_TENANT_CODE=...
# CORP MDM (used for offboarding / enterprise wipes)
CORP_WS1_API_BASE=https://...
CORP_WS1_CLIENT_ID=...
CORP_WS1_CLIENT_SECRET=...
CORP_WS1_TENANT_CODE=...
# -----------------------------------------------------------------------------
# Atlas
# -----------------------------------------------------------------------------
ATLAS_AUTH_KEY=your-atlas-key
# -----------------------------------------------------------------------------
# ServiceChannel
# OAuth password grant against ServiceChannel's identity endpoint.
# -----------------------------------------------------------------------------
SC_BASE_URL=https://api.servicechannel.com/v3
SC_OAUTH_URL=https://login.servicechannel.com/oauth/token
SC_CLIENT_ID=your-sc-client-id
SC_CLIENT_SECRET=your-sc-client-secret
SC_USERNAME=your-sc-username@company.com
SC_PASSWORD=your-sc-password
# -----------------------------------------------------------------------------
# VC Provisioning "backdoor" account
# Used by vcProvisionService for local-device authentication during certificate
# enrollment. DIGICERT_SEAT_EMAIL above is unrelated.
# -----------------------------------------------------------------------------
BACKDOOR_USERNAME=monitor
BACKDOOR_PASSWORD=...
# -----------------------------------------------------------------------------
# Cisco DBS-210 DECT base station — LOCAL TEST HARNESS
# Used only by scripts/testDectBase.js during the spike phase (see
# integrations/cisco-dect/). Not wired into the bot yet — this is for
# reverse-engineering the DBS-210 web UI (reboot, PRT pull, syslog,
# config export) against a single lab base before scaling to a
# per-store relay agent.
#
# Cisco's guidance: use the DECT serviceability password (Control Hub
# → Calling → Features → DECT Networks → Manage → Manage DECT
# serviceability password). Our tenant is configured to share a
# single password across all bases in the fleet, so this one credential
# will work fleet-wide once we add the relay layer.
# -----------------------------------------------------------------------------
DECT_TEST_BASE_IP=10.0.0.100
DECT_TEST_USER=admin
DECT_TEST_PASSWORD=your-dect-serviceability-password
# Optional: seconds to wait for base station responses. DBS-210 is slow
# on syslog/PRT downloads; 30s is a good starting point.
DECT_TEST_TIMEOUT_MS=30000
# -----------------------------------------------------------------------------
# Notes
# -----------------------------------------------------------------------------
# - config/config.json has been fully removed. All configuration is via env vars.
# - The rotating Webex service token lives in config/webex-service-tokens.json
# and is mounted separately when running in Docker.
# - Add any new integration keys above following the same pattern.