4ce4204b6c
CI / Lint (push) Failing after 2s
CI / Build (push) Has been skipped
SDK CI / PHP SDK (push) Failing after 1s
Split PHP SDK / PHP SDK tests (push) Failing after 1s
CI / Test (push) Failing after 1s
CI / Dashboard (push) Failing after 0s
SDK CI / JavaScript SDK (push) Failing after 0s
SDK CI / Python SDK (push) Failing after 2s
SDK CI / Java SDK (push) Failing after 1s
Split PHP SDK / Mirror sdk/php -> rmyndharis/openwa-php (push) Has been skipped
CI / Test (PostgreSQL migrations) (push) Failing after 7m47s
CI / Docker Build (push) Has been skipped
339 lines
20 KiB
Bash
339 lines
20 KiB
Bash
# =============================================================================
|
|
# OpenWA - Environment Configuration
|
|
# =============================================================================
|
|
# Copy this file to .env and customize as needed.
|
|
# This is the Single Source of Truth for all configuration.
|
|
|
|
# =============================================================================
|
|
# CORE SETTINGS
|
|
# =============================================================================
|
|
NODE_ENV=production
|
|
# Port the app binds to when run directly (bare metal / `npm run start:prod`). In the bundled Docker
|
|
# Compose the container always listens on 2785; the API_PORT below is only the HOST-published port.
|
|
PORT=2785
|
|
# Docker Compose only: the host-side port mapped to the container's 2785 (no effect on a bare-metal run).
|
|
API_PORT=2785
|
|
LOG_LEVEL=info # error | warn | info | debug
|
|
# Console output format. Default: json in production (containers, log aggregators),
|
|
# human-readable pretty otherwise. Force one with: json | pretty
|
|
# LOG_FORMAT=pretty
|
|
|
|
# Auto-start previously authenticated sessions on server boot. Recommended `true` for a SINGLE-instance
|
|
# production deployment so a crash-restart self-heals authenticated sessions; keep `false` if you run
|
|
# multiple replicas behind a scheduler (two replicas resurrecting one session risks a forced logout/ban —
|
|
# see docs/13-horizontal-scaling.md).
|
|
AUTO_START_SESSIONS=false
|
|
# 0 = unlimited. Set a positive integer to cap concurrently running/initializing sessions.
|
|
# Failed sessions still hold a slot until stopped (their engine stays resident); stop them to free capacity.
|
|
MAX_CONCURRENT_SESSIONS=0
|
|
|
|
# Graceful-shutdown drain: on SIGTERM/SIGINT the app flips readiness to 503 (so a load balancer stops
|
|
# routing) and keeps serving in-flight requests for this many ms before teardown. Default 3000 in
|
|
# production, 0 elsewhere (a dev hot-reload/Ctrl+C is not slowed). Capped at 30000. A second signal
|
|
# forces an immediate exit. In Docker, keep `stop_grace_period` >= this + your worst-case teardown.
|
|
# SHUTDOWN_DELAY_MS=3000
|
|
|
|
# Domain Configuration
|
|
DOMAIN=localhost
|
|
|
|
# Host interface the dev compose binds the API/dashboard ports to. Defaults to 127.0.0.1
|
|
# (localhost only). Set to 0.0.0.0 to reach the dev stack from another machine (e.g. a remote
|
|
# VPS) — put a TLS reverse proxy in front for anything internet-facing (the API key is sent in
|
|
# cleartext over plain HTTP).
|
|
# BIND_HOST=0.0.0.0
|
|
|
|
# Public URLs (for startup banner and external access)
|
|
# BASE_URL=https://api.yourdomain.com
|
|
# DASHBOARD_URL=https://dashboard.yourdomain.com
|
|
|
|
# CORS Configuration: comma-separated allowed origins. The wildcard "*" is allowed
|
|
# only in development; in production it is REFUSED (cross-origin browser requests are
|
|
# blocked) — set explicit origin(s) there, e.g. https://dashboard.yourdomain.com
|
|
CORS_ORIGINS=*
|
|
|
|
# SSL/TLS: terminate TLS at your own reverse proxy (nginx, Caddy, a cloud load
|
|
# balancer, or a k8s Ingress) in front of the API — see docs/12-troubleshooting-faq.md.
|
|
|
|
# Trusted reverse proxies (comma-separated IPs/CIDRs) whose X-Forwarded-For
|
|
# header is trusted to determine the real client IP for API-key IP whitelisting.
|
|
# Leave empty (default) to ignore X-Forwarded-For and use the direct socket
|
|
# address, which prevents IP spoofing. If you run behind a reverse proxy AND use
|
|
# per-key allowedIps restrictions, set this to the proxy's address/subnet, e.g.:
|
|
# TRUSTED_PROXIES=172.18.0.0/16
|
|
|
|
# =============================================================================
|
|
# ENGINE CONFIGURATION
|
|
# =============================================================================
|
|
# Which WhatsApp engine to use (plugin-based)
|
|
# Options: whatsapp-web.js, baileys
|
|
# Left unset by default so the dashboard (Infrastructure > Engine) governs the active engine via
|
|
# data/.env.generated (defaults to whatsapp-web.js). Uncomment to pin an engine from the environment;
|
|
# a value set here always wins over the dashboard selection.
|
|
# ENGINE_TYPE=whatsapp-web.js
|
|
|
|
# Engine-specific settings
|
|
SESSION_DATA_PATH=./data/sessions
|
|
PUPPETEER_HEADLESS=true
|
|
PUPPETEER_ARGS=--no-sandbox,--disable-setuid-sandbox,--disable-dev-shm-usage,--disable-gpu
|
|
# Path to a system Chromium/Chrome binary. Leave unset to use the bundled Puppeteer browser.
|
|
# Required in the Docker image and on hosts without a bundled browser (e.g. Alpine/ARM).
|
|
# PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
|
|
|
# ── Container resource limits (docker-compose only) ───────────────────────────
|
|
# These map to docker-compose `mem_limit` / `pids_limit` and only apply when running via the
|
|
# bundled compose files. They are read by docker-compose.yml, not by the app process.
|
|
# Override in your .env to tune for your fleet.
|
|
|
|
# Memory ceiling for the openwa-api container. whatsapp-web.js runs a full Chromium per session, so
|
|
# raise this for multi-session deployments (e.g. 4g). Baileys (no Chromium) is far lighter.
|
|
OPENWA_MEM_LIMIT=2g
|
|
|
|
# Per-container process (PID) ceiling — a fork-bomb guard, NOT an allocation (the kernel only
|
|
# rejects forks once the count is reached, so a higher limit is free for light containers).
|
|
# Default 2048 fits ~8-10 whatsapp-web.js sessions with startup-spike headroom; each Chromium is
|
|
# itself multi-process (browser + renderer + GPU + zygote + utilities) and WhatsApp Web is
|
|
# process-heavy, so the old default (512) could get a session's Chromium killed mid-spawn during
|
|
# startup — surfacing in the API as a `Code: null` launch failure (#636). Baileys is single-process
|
|
# and uses a handful of PIDs regardless. Raise for larger fleets; do NOT set -1 (drops the guard).
|
|
OPENWA_PIDS_LIMIT=2048
|
|
|
|
# Optional WhatsApp Web client version pin. Leave unset (or use "latest", "auto", or "off") to
|
|
# let whatsapp-web.js auto-select. If sessions get stuck at "authenticating" after scanning the QR,
|
|
# set this to a known-good version from https://github.com/wppconnect-team/wa-version (browse the
|
|
# html/ folder). WWEBJS_WEB_VERSION_REMOTE_PATH overrides the URL template (use {version} as the
|
|
# placeholder) if you self-host the version HTML.
|
|
# WWEBJS_WEB_VERSION=2.3000.1040641150-alpha
|
|
|
|
# Extend the initial boot/inject wait (milliseconds) before QR generation. On slow first boots
|
|
# (WSL2 or low-resource containers) the default 30000ms can expire before WhatsApp Web finishes
|
|
# loading. Raise it (e.g. 120000) if startup times out. Unset keeps the default (30000).
|
|
# WWEBJS_AUTH_TIMEOUT_MS=120000
|
|
|
|
# Baileys engine (used when ENGINE_TYPE=baileys). WebSocket client, no Chromium.
|
|
# NOTE: the Baileys engine is loaded lazily (dynamic import, only when ENGINE_TYPE=baileys), so there is no
|
|
# global Node.js version floor. Node.js 22 LTS is recommended for all deployments.
|
|
BAILEYS_AUTH_DIR=./data/baileys
|
|
# NOTE: proxy (PROXY_URL/PROXY_TYPE) is not yet supported by the baileys engine; it is ignored.
|
|
# Pull the FULL message-history archive on connect (large). Off by default: the engine still enables the
|
|
# initial sync (contacts, chats, recent messages, lid mappings) without downloading the entire history.
|
|
BAILEYS_SYNC_FULL_HISTORY=false
|
|
# Surface the Baileys library's own logs for debugging (trace|debug|info|warn|error). Silent by default.
|
|
# `trace` dumps the decoded WhatsApp wire frames to stdout (context "baileys-wire").
|
|
# BAILEYS_LOG_LEVEL=debug
|
|
|
|
# Humanise single sends: show a "typing…" indicator and pause briefly (length-scaled, jittered)
|
|
# before each text send so messages don't look instantaneous (anti-ban). ON by default — set to
|
|
# false to disable. SIMULATE_TYPING_MAX_MS caps the pause (default 5000). Does not affect bulk
|
|
# sends, which have their own delayBetweenMessages throttle.
|
|
# SIMULATE_TYPING=false
|
|
# SIMULATE_TYPING_MAX_MS=5000
|
|
|
|
# Inline @lid -> phone resolution (#263). When a sender is identified by a WhatsApp privacy id
|
|
# (@lid) instead of a phone number, attach a best-effort `senderPhone` (MSISDN digits, or null when
|
|
# the engine can't map it) to the message.received webhook + websocket payload. OFF by default —
|
|
# it adds a per-sender lookup (cached). The on-demand endpoint GET /sessions/:id/contacts/:id/phone
|
|
# works regardless of this flag.
|
|
# RESOLVE_LID_TO_PHONE=true
|
|
|
|
# Observability — Prometheus scrape endpoint at GET /api/metrics.
|
|
# Disabled by default; set a token to enable. Scrapers must send `Authorization: Bearer <token>`.
|
|
# METRICS_TOKEN=change-me-to-a-long-random-string
|
|
|
|
# Audit-log retention. Logs older than this many days are pruned daily (and once at startup).
|
|
# Default 90; set to 0 to keep audit logs forever (disable pruning).
|
|
# AUDIT_RETENTION_DAYS=90
|
|
|
|
# =============================================================================
|
|
# DATABASE
|
|
# =============================================================================
|
|
# Options: sqlite, postgres
|
|
DATABASE_TYPE=sqlite
|
|
POSTGRES_BUILTIN=false # Use built-in PostgreSQL container?
|
|
|
|
# PostgreSQL settings (ignored for sqlite, auto-configured if POSTGRES_BUILTIN=true)
|
|
DATABASE_HOST=localhost
|
|
DATABASE_PORT=5432
|
|
# PostgreSQL database name ONLY. Leave unset for SQLite (DATABASE_TYPE=sqlite) to use the
|
|
# default file path ./data/openwa.sqlite — a bare value here would become the SQLite file PATH
|
|
# and, under a read-only container rootfs, trigger a SQLITE_CANTOPEN boot-loop (#677).
|
|
# DATABASE_NAME=openwa
|
|
# PostgreSQL schema (ignored for sqlite). Default 'public' keeps the historical behavior.
|
|
# Set to a dedicated schema to isolate OpenWA's tables + migration ledger (e.g. on managed
|
|
# Postgres where you get a project schema, or to share one DB across apps). The schema must
|
|
# already exist — the built-in container creates it; for external/managed Postgres create it
|
|
# once (CREATE SCHEMA openwa;). A missing schema fails fast at migration time.
|
|
POSTGRES_SCHEMA=public
|
|
DATABASE_USERNAME=openwa
|
|
# MUST set a strong, unique value before using Postgres — no default is shipped.
|
|
# Production refuses to start if this is empty or a known placeholder when DATABASE_TYPE=postgres.
|
|
DATABASE_PASSWORD=
|
|
DATABASE_SYNCHRONIZE=false # WARNING: Set false in production! (data DB)
|
|
DATABASE_LOGGING=false
|
|
# Auth/audit (main) DB schema management. Default ON (zero-config first boot).
|
|
# Set to "false" to manage the api_keys/audit_logs schema via the bundled main-owned
|
|
# migration instead of synchronize (migrationsRun creates them at boot).
|
|
MAIN_DATABASE_SYNCHRONIZE=true
|
|
DATABASE_SSL=false # Set true for managed Postgres (Supabase, Heroku, Render, Railway)
|
|
DATABASE_SSL_REJECT_UNAUTHORIZED=true # Set false to allow self-signed certs (only when DATABASE_SSL=true)
|
|
# Postgres pool/query timeouts (ms). Defaults are conservative; set any to 0 to disable.
|
|
DATABASE_STATEMENT_TIMEOUT_MS=30000 # Aborts a single runtime query that runs longer (server-side; not applied to migrations)
|
|
DATABASE_IDLE_TIMEOUT_MS=30000 # Closes idle pooled connections after this long
|
|
DATABASE_CONNECTION_TIMEOUT_MS=10000 # Fails fast if a new connection can't be acquired in time
|
|
|
|
# =============================================================================
|
|
# REDIS / QUEUE (Phase 2)
|
|
# =============================================================================
|
|
REDIS_ENABLED=false # Enable Redis for queue and caching
|
|
REDIS_BUILTIN=false # Use built-in Redis container?
|
|
# Process webhooks/ingress through the BullMQ queue (needs a reachable Redis via REDIS_HOST/REDIS_PORT).
|
|
# Off by default (inline dispatch). In the bundled Docker Compose this is dashboard-managed
|
|
# (Infrastructure > Redis & Queue) — a host value here is not forwarded to the container.
|
|
# QUEUE_ENABLED=false
|
|
# Redis-backed caching, independent of the queue. Off by default (in-memory cache).
|
|
# CACHE_ENABLED=false
|
|
|
|
# Redis settings (auto-configured if REDIS_BUILTIN=true)
|
|
REDIS_HOST=localhost
|
|
REDIS_PORT=6379
|
|
# Fail Redis queue/cache connection attempts after this many ms.
|
|
REDIS_CONNECT_TIMEOUT_MS=5000
|
|
# REDIS_USERNAME=
|
|
# REDIS_PASSWORD=
|
|
|
|
# =============================================================================
|
|
# STORAGE
|
|
# =============================================================================
|
|
# Options: local, s3
|
|
STORAGE_TYPE=local
|
|
MINIO_BUILTIN=false # Use built-in MinIO container?
|
|
|
|
# Local storage path (if STORAGE_TYPE=local)
|
|
STORAGE_LOCAL_PATH=./data/media
|
|
|
|
# S3/MinIO settings (if STORAGE_TYPE=s3, auto-configured if MINIO_BUILTIN=true)
|
|
S3_ENDPOINT=http://localhost:9000
|
|
S3_BUCKET=openwa
|
|
S3_REGION=us-east-1
|
|
# MUST set strong, unique values before using S3/MinIO — no defaults shipped.
|
|
# Production refuses to start if these are empty/placeholder when STORAGE_TYPE=s3 (external).
|
|
# Canonical names (what the app reads first and the dashboard writes); the legacy
|
|
# S3_ACCESS_KEY / S3_SECRET_KEY are still accepted as a fallback for older setups.
|
|
S3_ACCESS_KEY_ID=
|
|
S3_SECRET_ACCESS_KEY=
|
|
|
|
# =============================================================================
|
|
# WEBHOOK
|
|
# =============================================================================
|
|
WEBHOOK_TIMEOUT=10000 # Timeout in milliseconds
|
|
WEBHOOK_MAX_RETRIES=3 # Number of retry attempts
|
|
WEBHOOK_RETRY_DELAY=5000 # Delay between retries in ms
|
|
# Days to keep webhook delivery-failure records before pruning them (default 90; set <= 0 to disable).
|
|
# WEBHOOK_FAILURE_RETENTION_DAYS=90
|
|
# Block outbound WEBHOOK deliveries to internal/reserved addresses (SSRF
|
|
# protection). ON by default; set to "false" only on closed networks. When on,
|
|
# webhook URLs resolving to loopback/private/link-local/metadata ranges are
|
|
# refused (at registration AND delivery) and redirects are not followed.
|
|
# (Server-side media-by-URL fetches are ALWAYS SSRF-guarded, regardless of this flag.)
|
|
WEBHOOK_SSRF_PROTECT=true
|
|
|
|
# Expose the FULL sender `contact` field set (id, number, shortName, business flags, isBlocked,
|
|
# labels, …) on the message.received webhook + websocket payload. OFF by default — the payload keeps
|
|
# the minimal { name, pushName }. Opt in only if your consumer needs the richer data; all fields are
|
|
# read from the already-cached contact (no extra WhatsApp API calls).
|
|
# WEBHOOK_CONTACT_DETAILS=true
|
|
# Comma-separated hosts/IPs allowed to bypass SSRF protection for BOTH webhooks
|
|
# and media — escape-hatch for trusted internal targets (e.g. a localhost media
|
|
# store or a sidecar webhook receiver).
|
|
# SSRF_ALLOWED_HOSTS=localhost,minio
|
|
# Server-side media size/time limits:
|
|
# MEDIA_DOWNLOAD_ENABLED=true # Set to false to skip downloading inbound media entirely
|
|
# # (no decryption, no memory allocation, no storage).
|
|
# # NOTE: disabling also makes hasMedia report false and removes the
|
|
# # media field from webhooks and the dashboard.
|
|
# STORE_EPHEMERAL_MESSAGES=true # Set to false to skip persisting and dispatching incoming
|
|
# # WhatsApp disappearing messages (ephemeralDuration > 0).
|
|
# # Default: true (backward compatible — store everything).
|
|
# MEDIA_DOWNLOAD_MAX_BYTES=52428800 # cap remote-URL sends, inbound media, AND outbound base64 sends (default 50 MiB; oversized inbound media is dropped, message kept; oversized base64 is rejected with 400)
|
|
# MEDIA_DOWNLOAD_TIMEOUT_MS=30000 # abort a slow media download (default 30s)
|
|
# Storage import/export limits (ADMIN /infra/storage/* endpoints):
|
|
# STORAGE_IMPORT_MAX_BYTES=209715200 # per-entry cap for a tar.gz import; aborts on overflow (default 200 MiB)
|
|
# STORAGE_IMPORT_MAX_ENTRIES=100000 # max entries in an import archive; aborts beyond this (default 100000)
|
|
# STORAGE_EXPORT_TTL_MS=3600000 # auto-delete an export archive after this long (default 1h)
|
|
|
|
# =============================================================================
|
|
# RATE LIMITING (all TTLs are in MILLISECONDS)
|
|
# =============================================================================
|
|
# The "medium" tier is the one enforced on the API; short/long are optional extra tiers.
|
|
RATE_LIMIT_MEDIUM_TTL=60000 # window in ms (default 60000 = 60s)
|
|
RATE_LIMIT_MEDIUM_LIMIT=100 # max requests per window
|
|
# RATE_LIMIT_SHORT_TTL=1000 # 1s burst window (default)
|
|
# RATE_LIMIT_SHORT_LIMIT=10
|
|
# RATE_LIMIT_LONG_TTL=3600000 # 1h window (default)
|
|
# RATE_LIMIT_LONG_LIMIT=1000
|
|
|
|
# Per-instance fairness cap on the Integration Fabric ingress route (POST /api/ingress/:pluginId/:instanceId/*).
|
|
# Providers deliver every tenant's webhooks from one shared egress IP, so this is keyed on
|
|
# (pluginId, instanceId) instead of IP — a noisy tenant gets 429'd without throttling its neighbors.
|
|
# Independent of, and in addition to, the IP-keyed tiers above.
|
|
# INGRESS_INSTANCE_LIMIT=120 # max requests per instance per window (default 120)
|
|
# INGRESS_INSTANCE_TTL=60000 # window in ms (default 60000 = 60s)
|
|
|
|
# =============================================================================
|
|
# MCP (Model Context Protocol) — Agent / AI-assistant tool server
|
|
# =============================================================================
|
|
# Off by default. When enabled, mounts a stateless Streamable-HTTP MCP server
|
|
# at POST /mcp on the same port. See docs/24-mcp-integration.md for details.
|
|
# MCP_ENABLED=true
|
|
|
|
# Mount read-only tools only (no sends, no mutations). Recommended for observer agents.
|
|
# MCP_READONLY=true
|
|
|
|
# Per-key sliding-window rate limit for tool calls.
|
|
# Any blank/non-positive/non-numeric value falls back to the default.
|
|
# MCP_RATE_LIMIT_MAX=60 # max tool calls per key per window (default 60)
|
|
# MCP_RATE_LIMIT_WINDOW_MS=60000 # window size in ms (default 60000 = 1 min)
|
|
# Pre-auth, per-IP sliding-window throttle for the /mcp mount (gates invalid-key requests before
|
|
# key validation, since the raw mount bypasses the global throttler). Same fallback rules.
|
|
# MCP_IP_RATE_LIMIT_MAX=120 # max requests per IP per window (default 120)
|
|
# MCP_IP_RATE_LIMIT_WINDOW_MS=60000 # window size in ms (default 60000 = 1 min)
|
|
|
|
# =============================================================================
|
|
# GLOBAL MESSAGE SEARCH
|
|
# =============================================================================
|
|
# Global message search (optional). Default ON, using the built-in DB full-text
|
|
# provider (Postgres tsvector/GIN, SQLite FTS5) — zero external services.
|
|
SEARCH_ENABLED=true # set to false to disable the /search route + module entirely
|
|
SEARCH_PROVIDER=auto # auto | builtin-fts | none (plugin ids selectable once Spec 2 lands)
|
|
SEARCH_LIMIT_MAX=100 # hard cap on the `limit` query param
|
|
|
|
# =============================================================================
|
|
# PLUGINS
|
|
# =============================================================================
|
|
PLUGINS_ENABLED=true # Enable plugin system
|
|
PLUGINS_DIR=./data/plugins # Plugin directory
|
|
|
|
# =============================================================================
|
|
# SECURITY
|
|
# =============================================================================
|
|
# Master API key (leave empty to disable, or set to secure value)
|
|
API_MASTER_KEY=
|
|
|
|
# First-boot default admin key. By default a cryptographically random key is
|
|
# generated (printed in the startup banner / written to data/.api-key). Set this
|
|
# to true ONLY for local development to seed the well-known, insecure
|
|
# `dev-admin-key` instead. Ignored when API_MASTER_KEY is set.
|
|
# ALLOW_DEV_API_KEY=true
|
|
|
|
# Optional server-side pepper for API-key hashing (HMAC-SHA256 instead of plain SHA-256).
|
|
# Recommended in production. Note: setting or changing it invalidates all existing key hashes,
|
|
# so re-issue keys after enabling. Leave unset to keep the current (unpeppered) behaviour.
|
|
# API_KEY_PEPPER=
|
|
|
|
# =============================================================================
|
|
# DEVELOPER SETTINGS
|
|
# =============================================================================
|
|
ENABLE_SWAGGER=true # Enable API documentation at /api/docs (set false to disable on exposed deployments)
|
|
BODY_SIZE_LIMIT=25mb # Max request body size (base64 media sends ride in the JSON body)
|
|
# CSP_UPGRADE_INSECURE_REQUESTS=false # Set false for an HTTP-only deployment on a trusted private network
|
|
# (default: on in production). Stops the browser upgrading the dashboard to https.
|