changemaker.lite/.env.example
bunker-admin 21208b58c7 feat(media): HLS adaptive bitrate streaming with MP4 fallback
Replaces single-MP4 + range-request streaming with HLS multi-bitrate
segments to fix video stutter through the Newt tunnel. Range-request
bursts were the root cause; HLS chunks are small and tunnel-friendly,
plus the player adapts bitrate to bandwidth.

Backend
- New BullMQ `hls-transcode` queue (in-process worker, concurrency 1)
- FFmpeg single-pass transcode → 360p/720p/1080p variants with aligned
  keyframes; output at /media/local/hls/{id}/master.m3u8
- New /api/{videos|public}/{id}/hls/* routes serving signed manifests
  and segments (URLs emitted as /media/* so nginx rewrites to media-api)
- Prisma: HlsStatus enum + 6 fields on Video + index, migration
- Upload + yt-dlp fetch paths enqueue transcode jobs
- ENABLE_HLS_TRANSCODE flag (default off; gates enqueue only)
- Backfill script: `npm run backfill:hls`
- media-api bumped to 4 CPU / 2G for FFmpeg headroom

Frontend
- New useHls hook: lazy-imports hls.js (kept out of main bundle),
  native HLS on Safari/iOS, gives up after 2 NETWORK_ERRORs so MP4
  fallback engages cleanly
- VideoPlayer, VideoViewerModal, ShortsPage, ProductDetailPage now
  prefer HLS when ready; MP4 fallback is automatic
- ShortsPage prefetches next-3 master manifests via <link rel="prefetch">
- PublicVideoCard hover preview stays MP4 (avoids hls.js init latency)

Bunker Admin
2026-04-30 19:03:29 -06:00

468 lines
18 KiB
Plaintext

# ==============================================================================
# Changemaker Lite v2 — Environment Variables
# Copy this file to .env and fill in the values
# Generate secrets with: openssl rand -hex 32
# ==============================================================================
#
# SECURITY WARNING:
# - All passwords marked REQUIRED_STRONG_PASSWORD_CHANGE_THIS MUST be changed
# - Use strong, unique passwords (20+ characters recommended)
# - Generate secrets with: openssl rand -hex 32
# - NEVER commit .env to version control
# ==============================================================================
# ==============================================================================
# MINIMUM VIABLE SETUP (required — change these before deploying)
# ==============================================================================
# 1. V2_POSTGRES_PASSWORD — database password (8+ chars)
# 2. REDIS_PASSWORD — cache password (8+ chars)
# 3. JWT_ACCESS_SECRET — openssl rand -hex 32
# 4. JWT_REFRESH_SECRET — openssl rand -hex 32 (different from above)
# 5. JWT_INVITE_SECRET — openssl rand -hex 32 (different from above)
# 6. ENCRYPTION_KEY — openssl rand -hex 32 (different from JWT secrets)
# 7. INITIAL_ADMIN_PASSWORD — 12+ chars, uppercase + lowercase + digit
# 8. DOMAIN — your deployment domain (default: cmlite.org)
#
# Everything below these 8 values works with defaults for development.
# ==============================================================================
# --- General ---
NODE_ENV=development
# Root domain serves MkDocs documentation site only
# All application routes (admin + public) accessible at app.${DOMAIN}
DOMAIN=cmlite.org
USER_ID=1000
GROUP_ID=1000
DOCKER_GROUP_ID=984
# --- V2 PostgreSQL ---
V2_POSTGRES_USER=changemaker
V2_POSTGRES_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
V2_POSTGRES_DB=changemaker_v2
V2_POSTGRES_PORT=5433
# --- JWT Auth ---
JWT_ACCESS_SECRET=GENERATE_WITH_openssl_rand_hex_32
JWT_REFRESH_SECRET=GENERATE_WITH_openssl_rand_hex_32
JWT_INVITE_SECRET=GENERATE_WITH_openssl_rand_hex_32
JWT_ACCESS_EXPIRY=15m
# Reduced from 7d → 24h on 2026-04-12 (P2-3 hardening). Combined with
# device-fingerprint binding in the JWT payload, this tightens the
# exploitation window for stolen refresh tokens.
JWT_REFRESH_EXPIRY=24h
# Encryption key for DB-stored secrets (SMTP password, etc.)
# REQUIRED in production — must NOT reuse JWT_ACCESS_SECRET
# Generate with: openssl rand -hex 32
ENCRYPTION_KEY=GENERATE_WITH_openssl_rand_hex_32
# BREAKING CHANGE (2026-04-12): both GITEA_SSO_SECRET and SERVICE_PASSWORD_SALT
# are now REQUIRED (min 32 chars). The previous fallback to JWT_ACCESS_SECRET
# has been removed — a JWT leak must not compromise SSO cookies or service
# account passwords. Both values must be distinct from each other and from
# all JWT_* secrets. Generate with: openssl rand -hex 32
# Gitea SSO cookie signing secret (required, ≥32 chars, distinct from JWT secrets)
GITEA_SSO_SECRET=GENERATE_WITH_openssl_rand_hex_32
# Salt for deriving deterministic service passwords (Gitea, Rocket.Chat).
# Required, ≥32 chars, distinct from all other secrets.
SERVICE_PASSWORD_SALT=GENERATE_WITH_openssl_rand_hex_32
# --- Initial Super Admin User (auto-created during database seeding) ---
# These credentials are used to create the initial super admin account
# Change these before running the seed script in production
INITIAL_ADMIN_EMAIL=admin@cmlite.org
INITIAL_ADMIN_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
# --- API ---
API_PORT=4000
API_URL=http://localhost:4000
# Include docs/root domain for inline payment widgets on MkDocs pages
CORS_ORIGINS=http://localhost:3000,http://localhost,http://localhost:4003
# --- Admin GUI ---
ADMIN_PORT=3000
ADMIN_URL=http://localhost:3000
# --- Nginx ---
NGINX_HTTP_PORT=80
NGINX_HTTPS_PORT=443
# --- Embed Proxy Ports ---
# Dedicated nginx ports for iframe embedding without DNS/subdomain.
# Change these to avoid port conflicts when running multiple instances on one host.
NOCODB_EMBED_PORT=8881
N8N_EMBED_PORT=8882
GITEA_EMBED_PORT=8883
MAILHOG_EMBED_PORT=8884
MINI_QR_EMBED_PORT=8885
EXCALIDRAW_EMBED_PORT=8886
HOMEPAGE_EMBED_PORT=8887
VAULTWARDEN_EMBED_PORT=8890
ROCKETCHAT_EMBED_PORT=8891
GANCIO_EMBED_PORT=8892
JITSI_EMBED_PORT=8893
GRAFANA_EMBED_PORT=8894
ALERTMANAGER_EMBED_PORT=8895
# --- Docker / Container Management ---
# Docker network name (used by dashboard to auto-discover containers)
DOCKER_NETWORK_NAME=changemaker-lite
# Docker socket proxy URL (read-only container inspection)
DOCKER_PROXY_URL=http://docker-socket-proxy:2375
# Newt tunnel container (for Pangolin restart/status checks)
NEWT_CONTAINER_NAME=newt-changemaker
NEWT_COMPOSE_SERVICE=newt
# --- SMTP / Email ---
SMTP_HOST=mailhog-changemaker
SMTP_PORT=1025
SMTP_USER=
SMTP_PASS=
SMTP_FROM=noreply@cmlite.org
SMTP_FROM_NAME=Changemaker Lite
EMAIL_TEST_MODE=true
TEST_EMAIL_RECIPIENT=admin@cmlite.org
# --- Listmonk ---
LISTMONK_PORT=9001
# Use 5434 to avoid conflict with main PostgreSQL (5432 internal / 5433 host)
LISTMONK_DB_PORT=5434
LISTMONK_DB_USER=listmonk
LISTMONK_DB_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
LISTMONK_DB_NAME=listmonk
# Web admin login (for the Listmonk dashboard at :9001)
LISTMONK_WEB_ADMIN_USER=admin
LISTMONK_WEB_ADMIN_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
# API user (auto-created by listmonk-init container, used by V2 API for sync)
# Generate token: openssl rand -hex 16
# NOTE: LISTMONK_ADMIN_USER/PASSWORD are what the V2 API uses to connect.
# They MUST match LISTMONK_API_USER/TOKEN (same credentials, different var names).
LISTMONK_API_USER=v2-api
LISTMONK_API_TOKEN=GENERATE_WITH_openssl_rand_hex_16
LISTMONK_ADMIN_USER=v2-api
LISTMONK_ADMIN_PASSWORD=SAME_AS_LISTMONK_API_TOKEN
LISTMONK_SYNC_ENABLED=false
LISTMONK_WEBHOOK_SECRET=
LISTMONK_PROXY_PORT=9002
# Listmonk SMTP — MailHog for development (production SMTP added as second provider if credentials set)
LISTMONK_SMTP_HOST=mailhog-changemaker
LISTMONK_SMTP_PORT=1025
LISTMONK_SMTP_USER=
LISTMONK_SMTP_PASSWORD=
LISTMONK_SMTP_TLS_TYPE=none
LISTMONK_SMTP_FROM=Changemaker Lite <noreply@cmlite.org>
# Production SMTP (uncomment and set for real email delivery):
# LISTMONK_SMTP_HOST=smtp.protonmail.ch
# LISTMONK_SMTP_PORT=587
# LISTMONK_SMTP_USER=your@email.com
# LISTMONK_SMTP_PASSWORD=your-password
# LISTMONK_SMTP_TLS_TYPE=STARTTLS
# --- Represent API (Canadian electoral data) ---
REPRESENT_API_URL=https://represent.opennorth.ca
# --- NocoDB v2 (read-only data browser) ---
# NocoDB uses its own database (nocodb_meta) to avoid conflicts with Prisma
# The database is auto-created by init-nocodb-db.sh on first PostgreSQL startup
# nocodb-init container auto-registers changemaker_v2 as a browsable data source
NOCODB_V2_PORT=8091
NOCODB_URL=http://changemaker-v2-nocodb:8080
NOCODB_PORT=8091
NC_ADMIN_EMAIL=admin@cmlite.org
NC_ADMIN_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
# --- Redis ---
# Shared Redis (v2 uses authenticated connection)
REDIS_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
REDIS_URL=redis://:${REDIS_PASSWORD}@redis-changemaker:6379
# --- Payments (Stripe) ---
# Enable payments feature (memberships, products, donations)
# Stripe API keys are stored encrypted in DB via admin settings page
ENABLE_PAYMENTS=false
# --- Media Management ---
ENABLE_MEDIA_FEATURES=false
MEDIA_API_PORT=4100
MEDIA_API_PUBLIC_URL=http://media-api:4100
# Used during admin Docker build to set the media API endpoint for Vite
VITE_MEDIA_API_URL=http://changemaker-media-api:4100
# HLS adaptive bitrate transcoding. When 'true', uploaded videos are queued
# for FFmpeg transcoding into 360p/720p/1080p HLS variants and the player
# prefers HLS over the MP4 range-request stream. When 'false' (default),
# uploads are tagged SKIPPED and the player falls back to MP4 — no DB or
# disk impact, fully reversible. The worker is always registered so existing
# PENDING jobs from a prior run still process if you flip the flag back on.
ENABLE_HLS_TRANSCODE=false
MEDIA_ROOT=/media/library
MEDIA_UPLOADS=/media/uploads
MAX_UPLOAD_SIZE_GB=10
PUBLIC_MEDIA_PORT=3100
VIDEO_PLAYER_DEBUG=false
# Video Analytics (Feb 2026)
VIDEO_ANALYTICS_RETENTION_DAYS=90
VIDEO_ANALYTICS_IP_HASHING_ENABLED=true
# Video Scheduling (Feb 2026)
VIDEO_SCHEDULE_DEFAULT_TIMEZONE=UTC
VIDEO_SCHEDULE_NOTIFICATION_ENABLED=true
# Preview Links (Feb 2026)
VIDEO_PREVIEW_LINK_EXPIRY_HOURS=24
# --- Container Registry ---
# Gitea registry for pre-built production images.
# Set IMAGE_TAG to a commit SHA (or 'latest') to pull pre-built images instead of building from source.
# Leave IMAGE_TAG blank/unset (defaults to 'local') to build locally from source.
GITEA_REGISTRY=gitea.bnkops.com/admin
IMAGE_TAG=
# Docker Compose profiles — set to 'monitoring' to include Prometheus/Grafana/Alertmanager
# in every 'docker compose up -d'. Leave blank to start monitoring separately.
COMPOSE_PROFILES=
# Credentials used by the registry status API endpoint (GET /api/registry/status)
# For docker push/pull, run: docker login gitea.bnkops.com
GITEA_REGISTRY_USER=admin
GITEA_REGISTRY_PASS=
# API token for the REMOTE registry (gitea.bnkops.com) — used by build-release.sh --upload
# Create at: https://gitea.bnkops.com/user/settings/applications
# This is NOT the same as GITEA_API_TOKEN (which is for the local platform Gitea below)
GITEA_REGISTRY_API_TOKEN=
# --- Gitea (Local Platform Instance) ---
GITEA_URL=http://gitea-changemaker:3000
GITEA_PORT=3030
GITEA_WEB_PORT=3030
GITEA_SSH_PORT=2222
# Admin user (auto-created on first boot by gitea-init.sh)
GITEA_ADMIN_USER=admin
# Leave blank to reuse INITIAL_ADMIN_PASSWORD (compose resolves the fallback).
# Set only if you want a distinct password for the Gitea admin account.
GITEA_ADMIN_PASSWORD=
GITEA_DB_TYPE=mysql
GITEA_DB_HOST=gitea-db:3306
GITEA_DB_NAME=gitea
GITEA_DB_USER=gitea
GITEA_DB_PASSWD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
GITEA_DB_ROOT_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
GITEA_ROOT_URL=https://git.cmlite.org
GITEA_DOMAIN=git.cmlite.org
# --- Gitea Docs Comments ---
# Enable comments on MkDocs pages (backed by Gitea Issues)
GITEA_COMMENTS_ENABLED=false
# Personal access token for the LOCAL Gitea instance (docs comments, user provisioning, SSO)
# Create at: http://localhost:3030/user/settings/applications (or https://git.DOMAIN/...)
# This is NOT the same as GITEA_REGISTRY_API_TOKEN (which is for the remote registry above)
GITEA_API_TOKEN=
# Repository owner (Gitea username that will own the docs-comments repo)
GITEA_COMMENTS_REPO_OWNER=
# Repository name (auto-created via admin setup button)
GITEA_COMMENTS_REPO_NAME=docs-comments
# OAuth2 Application credentials (create in Gitea → Settings → Applications → OAuth2)
# Redirect URIs: https://{DOMAIN}/comments/callback/ and http://localhost:4003/comments/callback/
GITEA_OAUTH_CLIENT_ID=
GITEA_OAUTH_CLIENT_SECRET=
# --- n8n ---
N8N_URL=http://n8n-changemaker:5678
N8N_PORT=5678
N8N_HOST=n8n.cmlite.org
N8N_ENCRYPTION_KEY=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
N8N_USER_EMAIL=admin@example.com
N8N_USER_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
GENERIC_TIMEZONE=UTC
# --- MkDocs ---
# Port mapping for MkDocs container (host:container)
# This also controls the Vite dev proxy in local development
# Change this port to use a different local port, and the admin dev server will automatically use it
MKDOCS_PORT=4003
MKDOCS_SITE_SERVER_PORT=4004
BASE_DOMAIN=https://cmlite.org
MKDOCS_PREVIEW_URL=http://mkdocs:8000
MKDOCS_DOCS_PATH=/mkdocs/docs
# --- Code Server ---
CODE_SERVER_PORT=8888
CODE_SERVER_URL=http://code-server-changemaker:8443
USER_NAME=coder
# --- Homepage ---
HOMEPAGE_PORT=3010
HOMEPAGE_VAR_BASE_URL=http://localhost
# --- Mini QR ---
MINI_QR_PORT=8089
MINI_QR_URL=http://mini-qr:8080
# --- Excalidraw (Collaborative Whiteboard) ---
EXCALIDRAW_PORT=8090
EXCALIDRAW_URL=http://excalidraw-changemaker:80
EXCALIDRAW_WS_URL=wss://draw.cmlite.org
# --- Vaultwarden (Password Manager) ---
VAULTWARDEN_PORT=8445
VAULTWARDEN_URL=http://vaultwarden-changemaker:80
# Admin panel token (access at /admin) — generate with: openssl rand -hex 32
VAULTWARDEN_ADMIN_TOKEN=
# MUST use HTTPS — Bitwarden web vault enforces HTTPS for account creation
# Set to your Pangolin tunnel URL (e.g., https://vault.yourdomain.org)
# Local access (browsing existing vault) works on HTTP, but signup/invite requires HTTPS
VAULTWARDEN_DOMAIN=https://vault.cmlite.org
VAULTWARDEN_SIGNUPS_ALLOWED=false
VAULTWARDEN_WEBSOCKET_ENABLED=true
# SMTP security: "off" for MailHog, "starttls" or "force_tls" for production
VAULTWARDEN_SMTP_SECURITY=off
# --- MailHog ---
MAILHOG_SMTP_PORT=1025
MAILHOG_WEB_PORT=8025
# --- NAR (National Address Register) ---
# Path to extracted NAR data (contains YYYYMM/Addresses/ and YYYYMM/Locations/)
# Download from: https://www150.statcan.gc.ca/n1/pub/46-26-0002/462600022022001-eng.htm
NAR_DATA_DIR=/data
# --- Overpass / Area Import ---
# OpenStreetMap Overpass API endpoint (use a private instance for heavy usage)
OVERPASS_API_URL=https://overpass-api.de/api/interpreter
# Minimum delay between Overpass requests (ms) — public API requires 30s
OVERPASS_MIN_DELAY_MS=30000
# Maximum reverse geocode grid points for area import fill-in
AREA_IMPORT_MAX_GRID_POINTS=500
# --- Geocoding ---
# Optional Mapbox API key for improved geocoding accuracy
# Free tier: 100,000 requests/month
# Sign up: https://www.mapbox.com/pricing
MAPBOX_API_KEY=
# Rate limit delay between provider requests (milliseconds)
GEOCODING_RATE_LIMIT_MS=1100
# Redis-backed persistent cache settings
GEOCODING_CACHE_ENABLED=true
GEOCODING_CACHE_TTL_HOURS=24
# Phase 2: Performance & Accuracy
# Google Maps API (optional, most accurate but costs $0.005/request after 100k/month)
GOOGLE_MAPS_API_KEY=
GOOGLE_MAPS_ENABLED=false
# Parallel geocoding for bulk imports (10x speedup)
GEOCODING_PARALLEL_ENABLED=true
GEOCODING_BATCH_SIZE=10
# Bulk Re-Geocoding (Phase 3)
BULK_GEOCODE_ENABLED=true
BULK_GEOCODE_MAX_BATCH=5000
# --- Pangolin Tunnel ---
# Server: self-hosted Pangolin instance
PANGOLIN_API_URL=https://api.bnkserve.org/v1
PANGOLIN_API_KEY=
PANGOLIN_ORG_ID=
# Populated after setup (via admin GUI or API)
PANGOLIN_SITE_ID=
PANGOLIN_ENDPOINT=https://pangolin.bnkserve.org
PANGOLIN_NEWT_ID=
PANGOLIN_NEWT_SECRET=
# --- Prisma CLI (host-side only, NOT used by Docker containers) ---
# Containers resolve the DB hostname internally via docker-compose environment
# This is used when running `npx prisma migrate dev` from the host machine
DATABASE_URL=postgresql://changemaker:YOUR_POSTGRES_PASSWORD@localhost:5433/changemaker_v2
# --- Rocket.Chat (Team Chat) ---
# ENABLE_CHAT is the initial default; once saved in admin Settings, the DB value is authoritative
ENABLE_CHAT=false
ROCKETCHAT_ADMIN_USER=rcadmin
ROCKETCHAT_ADMIN_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
ROCKETCHAT_URL=http://rocketchat-changemaker:3000
# MongoDB credentials for Rocket.Chat (required — MongoDB runs with --auth)
MONGO_ROOT_USER=rocketchat
MONGO_ROOT_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
# --- Gancio (Event Management) ---
# Uses shared PostgreSQL (database: gancio, auto-created by init-gancio-db.sh)
GANCIO_PORT=8092
GANCIO_URL=http://gancio-changemaker:13120
GANCIO_BASE_URL=https://events.cmlite.org
# Gancio admin credentials for shift-to-event sync (OAuth login)
GANCIO_ADMIN_USER=admin
GANCIO_ADMIN_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
# Enable automatic shift → Gancio event sync
GANCIO_SYNC_ENABLED=false
# --- Jitsi Meet (Video Conferencing) ---
# Self-hosted Jitsi with JWT auth — integrates with Rocket.Chat for channel video calls
# ENABLE_MEET is the initial default; once saved in admin Settings, the DB value is authoritative
ENABLE_MEET=false
# JWT authentication (shared between Jitsi Prosody, Rocket.Chat, and the API)
# Generate with: openssl rand -hex 32
JITSI_APP_ID=changemaker
JITSI_APP_SECRET=GENERATE_WITH_openssl_rand_hex_32
# Internal XMPP passwords (used between Jitsi containers, not exposed externally)
# Generate each with: openssl rand -hex 16
JITSI_JICOFO_AUTH_PASSWORD=GENERATE_WITH_openssl_rand_hex_16
JITSI_JVB_AUTH_PASSWORD=GENERATE_WITH_openssl_rand_hex_16
JITSI_URL=http://jitsi-web-changemaker:80
# JVB public IP (required for NAT traversal — set to server's public IP in production)
JVB_ADVERTISE_IP=
# JVB UDP port for media traffic (must be open in firewall)
JVB_PORT=10000
# --- SMS Campaigns (Termux Android Bridge) ---
# ENABLE_SMS is the initial default; once saved in admin Settings, the DB value is authoritative
# URL + API key are typically managed via admin Settings page (DB overrides env)
# Use Tailscale IP (100.x.x.x) for stable addressing across networks
ENABLE_SMS=false
TERMUX_API_URL=http://100.x.x.x:5001
TERMUX_API_KEY=
SMS_DELAY_BETWEEN_MS=3000
SMS_MAX_RETRIES=3
SMS_RESPONSE_SYNC_INTERVAL_MS=120000
SMS_DEVICE_MONITOR_INTERVAL_MS=300000
# --- Social, People & Analytics ---
# ENABLE_SOCIAL is the initial default; once saved in admin Settings, the DB value is authoritative
ENABLE_SOCIAL=false
# ENABLE_PEOPLE is the initial default; once saved in admin Settings, the DB value is authoritative
ENABLE_PEOPLE=false
# ENABLE_ANALYTICS is the initial default; once saved in admin Settings, the DB value is authoritative
ENABLE_ANALYTICS=false
# --- Control Panel Agent ---
# Set to true to enable the CCP remote management agent
ENABLE_CCP_AGENT=false
# URL of the Changemaker Control Panel
CCP_URL=
# One-time invite code for registration
CCP_INVITE_CODE=
# How the CCP can reach this agent (must be externally accessible)
CCP_AGENT_URL=
# Agent port (default 7443)
CCP_AGENT_PORT=7443
# --- Monitoring (only used with --profile monitoring) ---
PROMETHEUS_PORT=9090
GRAFANA_PORT=3005
GRAFANA_ADMIN_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
GRAFANA_ROOT_URL=http://localhost:3005
CADVISOR_PORT=8086
NODE_EXPORTER_PORT=9100
REDIS_EXPORTER_PORT=9121
ALERTMANAGER_PORT=9093
GOTIFY_PORT=8889
GOTIFY_ADMIN_USER=admin
GOTIFY_ADMIN_PASSWORD=REQUIRED_STRONG_PASSWORD_CHANGE_THIS
# --- Bunker Ops (Fleet Management) ---
INSTANCE_LABEL= # Unique label for this instance (defaults to DOMAIN)
BUNKER_OPS_ENABLED=false # Enable remote metrics push to central server
BUNKER_OPS_REMOTE_WRITE_URL= # VictoriaMetrics remote_write endpoint (e.g., https://ops.example.com/api/v1/write)
# --- GeoIP (MaxMind GeoLite2) ---
# Free account: https://www.maxmind.com/en/geolite2/signup
MAXMIND_ACCOUNT_ID= # MaxMind account ID
MAXMIND_LICENSE_KEY= # MaxMind license key (auto-downloads GeoLite2-City DB at startup)