Configuration
All environment variables with defaults and descriptions.
Core
| Variable | Default | Description |
|---|---|---|
ECHELON_PORT | 1947 | HTTP listen port |
ECHELON_DB_PATH | ./echelon.db | Path to SQLite database file |
ECHELON_SECRET | (empty) | Bearer token for API auth. If unset, PoW tokens won't survive restarts |
ECHELON_RETENTION_DAYS | 90 | Days to keep raw visitor_views and semantic_events |
ECHELON_BOT_RETENTION_DAYS | (same as retention) | Days to keep raw rows scored as bots (bot_score ≥ 50) or server-ingested. Set lower to reclaim disk sooner |
Authentication
| Variable | Default | Description |
|---|---|---|
ECHELON_USERNAME | (empty) | Username for password-based login |
ECHELON_PASSWORD_HASH | (empty) | PBKDF2 hash (pbkdf2$600000$<salt>$<hash>) |
Generate a password hash:
deno eval "import{hashPassword}from'./lib/auth.ts';console.log(await hashPassword('yourpassword'))"
Bot Defense
| Variable | Default | Description |
|---|---|---|
ECHELON_SUSPECT_COUNTRIES | CN | Comma-separated ISO-2 codes that add penalty points |
ECHELON_SUSPECT_POINTS | 30 | Points added per suspect country match |
ECHELON_SITE_SUSPECT_COUNTRIES | (empty) | Per-site suspect countries: site:CC,CC;site:CC |
ECHELON_BOT_DISCARD_THRESHOLD | 50 | Scores ≥ this are discarded before storage. 0 = store all (rows scoring 50 or more are still left out of statistics) |
ECHELON_BOT_UA_PATTERNS | (long list) | Comma-separated UA substrings to drop entirely |
ECHELON_CHALLENGE_WINDOW_MINUTES | 10 | Minutes of past PoW challenges accepted as valid |
ECHELON_REQUIRE_ENGAGEMENT | false | Also treat sessions that never interact (click, key press, tap, form use) as bots: their views and events are raised to the bot threshold 30–180 minutes after they were written and stop counting. Visitors who only read and scroll are removed by this. Leave off unless you want engaged visits only, and do not switch it on in the same deploy as an upgrade (see Upgrading) |
ECHELON_BOT_SCORE_WEIGHTS | (empty) | Per-signal bot score weights, key:points,key:points (0–100 each; 0 turns the signal off; an unknown key or bad value is ignored with a startup warning). Example: linux_desktop:0 for a developer audience, cross_site:0 for sibling sites. Keys: timing_fast, timing_quick, burst, no_accept_language, no_sec_headers, screen, referrer, crawler_feed, ai_crawler_feed, datacenter_ip, win_tablet_mismatch, linux_desktop, cross_site, country_mismatch, cross_continent, global_common_foreign, linux_foreign, stale_android_foreign. See Bot Defense for defaults |
Rate Limiting
| Variable | Default | Description |
|---|---|---|
ECHELON_RATE_LIMIT_MAX | 100 | Max requests per IP per window |
ECHELON_RATE_LIMIT_WINDOW_MS | 60000 | Rate limit window in milliseconds |
Buffered Writes
| Variable | Default | Description |
|---|---|---|
ECHELON_VIEW_FLUSH_MS | 15000 | Flush interval for pageview buffer (ms) |
ECHELON_EVENT_FLUSH_MS | 10000 | Flush interval for event buffer (ms) |
ECHELON_SHUTDOWN_TIMEOUT_MS | 60000 | How long shutdown may spend flushing buffers before forcing exit. Keep your container stop grace period longer than this |
Network & Proxy
| Variable | Default | Description |
|---|---|---|
ECHELON_TRUST_PROXY | false | Trust X-Forwarded-For / X-Real-IP for client IP |
ECHELON_BEHIND_CLOUDFLARE | false | Trust Cloudflare headers (cf-*) for geo and bot data |
ECHELON_TRUST_GEO_HEADERS | false | Trust cloudfront-viewer-country / x-country-code |
ECHELON_ALLOWED_ORIGINS | (empty) | Comma-separated domains allowed to load the tracker. Empty = all |
Admin UI
| Variable | Default | Description |
|---|---|---|
ECHELON_LIVE_STATS_MINUTES | 10 | Time window (minutes) for live stats in the admin nav bar |
ECHELON_DISPLAY_TIMEZONE | UTC | IANA timezone for admin UI timestamps (e.g. Europe/Oslo). Data is stored in UTC. |
MCP Server
| Variable | Default | Description |
|---|---|---|
ECHELON_URL | (required) | Base URL of the Echelon Analytics instance the MCP server queries (e.g. https://ea.islets.app) |
ECHELON_SECRET | (empty) | Bearer token for authenticated instances. Not needed for public mode instances. |
Anonymization & Public Mode
| Variable | Default | Description |
|---|---|---|
ECHELON_ANONYMIZE_SITES | (empty) | Comma-separated site IDs to anonymize before storage. Records for these sites get HMAC-hashed visitor IDs, fictional countries, redacted referrers, and codename UTMs. Session ids come from a list of 50 names that unrelated visits share, so on these sites interaction validation and the engagement gate match by the anonymized visitor id (within 30 minutes) and never by session. See Telemetry |
ECHELON_PUBLIC_MODE | false | Disable admin auth requirement. The dashboard is openly accessible without login — ideal for public analytics dashboards |
Miscellaneous
| Variable | Default | Description |
|---|---|---|
ECHELON_COOKIE_CONSENT | false | Show consent banner before setting visitor cookie |
ECHELON_IGNORED_SITES | (empty) | Comma-separated site IDs to silently ignore. "smoke-test" always ignored |
ECHELON_ALLOWED_SITES | (empty) | Comma-separated site IDs to accept. Tracking for any other site ID is silently dropped. Empty = all |
ECHELON_DEBUG | false | Verbose debug logging (PoW, scoring, events) |
ECHELON_TELEMETRY | (unset) | Override telemetry: true = force on, false = force off, unset = respect admin choice. See Telemetry |
🌍 "Did you know you can set per-site suspect countries with ECHELON_SITE_SUSPECT_COUNTRIES? Format: site1:CN,RU;site2:KP for granular bot defense." -🦭
Example .env
# Core ECHELON_PORT=1947 ECHELON_DB_PATH=./data/echelon.db ECHELON_SECRET=change-me-to-a-secure-random-string ECHELON_RETENTION_DAYS=90 # Auth ECHELON_USERNAME=admin ECHELON_PASSWORD_HASH=pbkdf2$600000$... # Network ECHELON_TRUST_PROXY=true ECHELON_BEHIND_CLOUDFLARE=true # Bot defense ECHELON_SUSPECT_COUNTRIES=CN ECHELON_SUSPECT_POINTS=30 ECHELON_RATE_LIMIT_MAX=100 # Admin UI ECHELON_LIVE_STATS_MINUTES=10 ECHELON_DISPLAY_TIMEZONE=Europe/Oslo # Telemetry (optional — unset = prompt admin on first access) # ECHELON_TELEMETRY=false