# ============================================================================= # 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 # IANA timezone for "Last checked" footers in chat output (avstatus, # phonestatus, etc.). The bot often runs in UTC inside Docker; this # keeps timestamps in operator-local time. Default: America/New_York. # DISPLAY_TIMEZONE=America/New_York # 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 ` or `X-API-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, voicediag reads, # webexhost, findEmptyLocations) # - spark-admin:people_write (webexhost: assign licenses, # voicediag: apply remediations — # disable DND / clear forwarding / # disable intercept / enable call # waiting via /v1/people/{id}/features/*) # - spark-admin:licenses_read (webexhost, findEmptyLocations) # - 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. # # NOTE — early drafts of /voicediag documented `spark-admin:telephony_config_*` # scopes and a `/v1/telephony/config/people/{id}/callSettings/*` URL scheme. # That URL family returns 404 "no static resource" from the Webex gateway. # The live admin surface is `/v1/people/{id}/features/{feature}` (see # services/voiceDiag/ for details) and it uses the same `spark-admin:people_*` # scopes that /webexhost and /offboarduser already require — no additional # scope work is needed to enable /voicediag. # # Refresh tokens preserve their original scope set, so if you ever DO add a # new scope you must: # 1. Save the updated scopes on the service app. # 2. Re-authorize the app for the org as a Full / User admin. # 3. Delete/rotate tokens/webex-service-tokens.json so the next request # re-bootstraps with the enlarged scope set. 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 # ----------------------------------------------------------------------------- # Palo Alto Prisma SD-WAN (formerly CloudGenix) — WAN metrics for /voicediag # and /phonestatus follow-up # ----------------------------------------------------------------------------- # The Prisma integration is optional: leave it unconfigured and the WAN # check bucket in /voicediag skips cleanly ("not applicable — missing # sdwanSite") and /phonestatus omits the WAN follow-up message. Enable it # by setting PRISMA_AUTH_MODE + the matching credential block below. # # AE convention (encoded in integrations/paloalto/sites.js): Prisma site # names are `CG${storeNum.padStart(5, '0')}` — store 782 → CG00782, store # 2477 → CG02477. Exact match. No fuzzy searching. # # --- Auth mode selection --- # PRISMA_AUTH_MODE=sase (default, recommended for production) # PRISMA_AUTH_MODE=legacy (only if you inherited a CloudGenix service # account and can't provision a SASE one) # PRISMA_AUTH_MODE= # --- Unified SASE OAuth 2.0 (PRISMA_AUTH_MODE=sase) --- # Provision a service-app credential in Strata Cloud Manager → Identity & # Access → Service Accounts. TSG_ID is the Tenant Service Group id shown # in the same panel — it scopes the resulting bearer token to your tenant. # Both defaults below are correct for the public SASE cloud; override for # region-specific gateways if you're on one. # PRISMA_CLIENT_ID= # PRISMA_CLIENT_SECRET= # PRISMA_TSG_ID= # PRISMA_SASE_BASE_URL=https://api.sase.paloaltonetworks.com # PRISMA_AUTH_URL=https://auth.apps.paloaltonetworks.com/oauth2/access_token # --- Legacy CloudGenix session token (PRISMA_AUTH_MODE=legacy) --- # Interactive-user credential (email + password). Kept for backward # compatibility only — the SASE mode above is what new deployments should # use because it's service-account-shaped instead of impersonating a # human. Session tokens last ~8 hours; the client transparently # re-authenticates on 401. # PRISMA_EMAIL= # PRISMA_PASSWORD= # PRISMA_LEGACY_BASE_URL=https://api.cloudgenix.com # --- Prisma client-side concurrency cap --- # Maximum number of in-flight Prisma requests at any moment. Prisma # throttles bursts hard — /voicediag fans out 10+ metric calls per # request, and without this cap they'd all fire simultaneously and # 429-cascade. Default 3 is empirically safe on the observed tenant. # Bump if your tenant tolerates more parallelism (fewer 429s), lower # if you still see them. # PRISMA_MAX_INFLIGHT=3 # --- Prisma Strata Cloud Manager (SCM) UI base URL --- # Used only for building "View in Prisma UI" deep links surfaced in # the /phonestatus WAN follow-up + /voicediag per-app details. The # API endpoints continue to use PRISMA_SASE_BASE_URL. Override this # only for tenants on a partner-branded or region-specific SCM # domain. Default matches the production SCM hostname. # PRISMA_UI_BASE_URL=https://stratacloudmanager.paloaltonetworks.com # --- WAN voice-quality thresholds (used by /voicediag WAN bucket + the # /phonestatus WAN follow-up renderer) --- # Defaults follow ITU-T G.114 (latency), G.711 PLC (loss), and RFC 3550 # (jitter) references. See services/voiceDiag/README.md for the standards # reference table. # WAN_STANDARD_LATENCY_WARN_MS=150 # WAN_STANDARD_LATENCY_ERROR_MS=400 # WAN_STANDARD_JITTER_WARN_MS=30 # WAN_STANDARD_JITTER_ERROR_MS=50 # WAN_STANDARD_LOSS_WARN_PCT=1 # WAN_STANDARD_LOSS_ERROR_PCT=3 # WAN_STANDARD_MOS_WARN=4.0 # WAN_STANDARD_MOS_ERROR=3.5 # WAN_STANDARD_HEALTHSCORE_WARN=80 # WAN_STANDARD_HEALTHSCORE_ERROR=60 # --- Per-application voice-quality thresholds --- # These grade the WORST-window value in the 24h series (rather than # the average) because for voice a brief 15-min degradation is a bad # operator experience even if the daily avg looks fine. Defaults are # less strict than the link-probe thresholds above — otherwise a # single 5-min bad window would drown the operator in warnings. # WAN_STANDARD_APP_MOS_WARN=4.0 # WAN_STANDARD_APP_MOS_ERROR=3.5 # WAN_STANDARD_APP_LOSS_WARN_PCT=5 # WAN_STANDARD_APP_LOSS_ERROR_PCT=15 # WAN_STANDARD_APP_JITTER_WARN_MS=30 # WAN_STANDARD_APP_JITTER_ERROR_MS=50 # --- Per-application DPI voice-metrics enablement --- # When set, /phonestatus and /voicediag will fetch REAL voice-traffic # quality metrics (MOS, packet loss, jitter, bandwidth) from Prisma's # Application Path Details endpoint for the configured voice app. This # is DPI data on actual RTP frames — the LQM link-probes above are # synthetic probes across the WAN circuits and can look green while # real audio is degrading (Prisma UI shows both; this env exposes the # second one to the bot). # # Which app to point at is a tenant choice: # - Webex_Calling_RTP ← recommended for Webex Calling shops. The # Webex-specific DPI signature excludes noise # from other UDP traffic and gives materially # more accurate per-call quality signal. # - rtp-base ← generic RTP DPI signature. Catches all RTP, # including non-Webex flows; use if you have # mixed voice apps or a tenant that doesn't # break out per-provider signatures. # - MS_Teams_RTP / Zoom_RTP / etc. — for those shops. # # To find the app id for your tenant: # npm run prisma:probe -- appdefs webex # or `rtp`, `teams`, `zoom` # or grab it from the Prisma UI's Application Path Details URL. Leave # blank to disable the extra API calls entirely (checks return skipped # with an explanation). # PRISMA_APP_ID_VOICE= # Optional human-friendly display label for the app configured above. # Shown in the "Voice Traffic Quality (…)" section headers and check # messages. Defaults to "voice" if unset. Recommend setting it to the # exact Prisma UI app label so operators can cross-reference: # PRISMA_APP_NAME_VOICE=Webex_Calling_RTP # --- BACKWARDS-COMPAT: PRISMA_APP_ID_RTP_BASE (deprecated) --- # Original name from the initial rtp-base-only release. Still honored # if set AND PRISMA_APP_ID_VOICE is unset, but logs a one-time # deprecation warning on first use. When falling back to this var, the # display name defaults to "rtp-base" so existing dashboards keep # reading the same label. Rename to PRISMA_APP_ID_VOICE at your # convenience — the old var will be removed in a future release. # PRISMA_APP_ID_RTP_BASE= # --- WAN bucket global kill-switch (mirror of VOICE_STANDARD_ENABLED for # the port bucket). Set to `false` to silence the entire WAN check # bucket while a Prisma cleanup is in progress. The /phonestatus # follow-up is unaffected — it always renders whatever Prisma # returns. Feature-config + port-hygiene buckets keep running. --- # WAN_STANDARD_ENABLED=true # --- WAN look-back window (minutes) used when the operator doesn't # pass `--window` on /voicediag (and always used by /phonestatus). # Applies to healthscore + LQM + per-app voice DPI + alarms. # Alarms window is max(60, WAN_STANDARD_WINDOW_MINUTES) since # sub-hour alarm queries are usually too noisy to be actionable. # # Default: 10080 (7 days). Widened from 24h because per-app DPI # metrics (Webex_Calling_RTP etc.) only generate datapoints WHEN # CALLS HAPPEN — a store that takes 3-4 Webex calls per day gives # only ~30 samples in 24h, well below the point where "worst- # window" statistics mean anything. Seven days consistently # yields 150-300+ per-app samples across stores of any size. # # For live-incident triage where you want a fresh snapshot, pass # `--window 1h` or `--window 24h` on /voicediag (or lower this # global default here). # # Interval selection is automatic per window size — see # integrations/paloalto/metrics.js:pickAppMetricInterval / # pickInterval. Hard-capped at 10080 (7d); beyond that Prisma # downsamples to 1-day buckets and the worst-window signal # dissolves. If you truly need 30d, edit MAX_WINDOW_MINUTES in # services/enrichment/sdwanEnrichment.js and confirm the interval # pickers still yield useful granularity for the check semantics. # # Common values: 60 (1h — live triage), 1440 (24h), 10080 (7d — default). # WAN_STANDARD_WINDOW_MINUTES=10080 # ----------------------------------------------------------------------------- # /voicediag — Store voice-line standards # ----------------------------------------------------------------------------- # Store phones are held to a fixed configuration standard. Most of the # standard is baked into the per-check modules under # services/voiceDiag/checks/ (see services/voiceDiag/README.md for the # reference table) — the values below only exist for the pieces that # vary per site / per environment. # # Expected VLAN for store phones. Today AE tags phones into the data # VLAN (102); some sites may move onto a proper voice VLAN in the # future. The check compares against whatever value is set here and # emits a warn when a phone shows up on any other VLAN. Default 102 # if unset. # VOICE_STANDARD_PHONE_VLAN=102 # Global kill-switch for /voicediag port-hygiene checks. Set to # `false` to silence portType / portVlan / portPoe / portEnabled while # an underlying Meraki cleanup is in progress and you don't want the # noise. Values: true (default) / false. The feature-config checks # (DND, forwarding, waiting, voicemail, intercept, hoteling, exec- # assistant, outgoingPermission) always run — this only gates the # switchport bucket. # VOICE_STANDARD_ENABLED=true # ----------------------------------------------------------------------------- # /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 ` 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 ` 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= # (constructs https://api.atlassian.com/ex/jira//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 — LOCAL DEV TEST HARNESS # Used only by scripts/testDectBase.js when iterating locally against a # lab base. NOT read by the bot at runtime — the bot never talks to a # DBS-210 directly. Actual production DBS-210 access lives in the # DECT_RELAY_* env below and is executed by dect-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. # ----------------------------------------------------------------------------- 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 # ----------------------------------------------------------------------------- # DECT Relay Hub — WSS endpoint for the data-center relay agent # The bot process runs in the public cloud and can't reach 10.x. The # relay agent (see dect-relay-agent/) runs INSIDE the DC, dials # outbound over WSS to this bot, and executes any DECT command the bot # pushes. Feature-gated: without DECT_RELAY_AGENT_TOKEN, the WSS # endpoint is not attached and /phonestatus's DECT follow-up + the # future /dectstatus command return "relay not connected". # # Rotate DECT_RELAY_AGENT_TOKEN on both sides at once. Suggested # generation: `openssl rand -hex 32`. # ----------------------------------------------------------------------------- DECT_RELAY_AGENT_TOKEN= # Optional. Default: /dect-relay/ws. Change only if you also change # DECT_RELAY_BOT_URL on the agent side to match. # DECT_RELAY_PATH=/dect-relay/ws # Optional. Per-base collect() RPC timeout. Corporate proxies can make # DBS-210 reads slow; 15s is comfortable, 30s is generous. # DECT_COLLECT_TIMEOUT_MS=15000 # ----------------------------------------------------------------------------- # 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.