# ─── Server ─────────────────────────────────────── PORT=9000 ENVIRONMENT=development # development | staging | production BASE_URL=http://localhost:9000 # ─── Database ───────────────────────────────────── DATABASE_URL=postgres://keygate:keygate@localhost:5432/keygate?sslmode=disable # ─── Security (required, generate with: openssl rand -hex 32) ─── JWT_SECRET= LICENSE_SIGNING_KEY= # ─── Payments (Stripe) ────────────────────────── # Stripe: https://dashboard.stripe.com/apikeys # Only STRIPE_SECRET_KEY is required. Webhook is auto-configured. STRIPE_SECRET_KEY= # ─── Email (optional, OTP codes print to logs if not set) ─── SMTP_HOST= SMTP_PORT=587 SMTP_USERNAME= SMTP_PASSWORD= # Either a bare address or the display-name form. The name is what # recipients see in their inbox; the envelope sender is stripped to the # bare address automatically. # SMTP_FROM=noreply@example.com # SMTP_FROM=Acme Licensing SMTP_FROM= # ─── Redis-compatible endpoint (optional; Redis or Valkey) ─── # When set, rate limits are shared across instances. Unset (default): each # instance limits on its own, in memory, so with N instances a client can # get N times the limit. On a slow/down endpoint, requests fail fast and # rate limiting degrades to in-memory until Redis is back. # Example: redis://localhost:6379/0 (a bare host:port also works) REDIS_URL= # ─── Rate Limits (requests per minute) ─────────── RATE_LIMIT_API=60 RATE_LIMIT_ADMIN=120 # Login/OTP-verify budget per IP per minute. Offices behind one NAT share it. RATE_LIMIT_AUTH=60 # Login codes per IP per HOUR. Deliberately tight: this endpoint mails # an address the caller supplies, so a loose budget lets someone use # your server (and your sending reputation) to spam third parties. RATE_LIMIT_OTP_SEND=30 # ─── Webhooks ──────────────────────────────────── WEBHOOK_MAX_ATTEMPTS=5 WEBHOOK_RETRY_INTERVAL=30s WEBHOOK_HTTP_TIMEOUT=10s # Allow webhook deliveries to loopback/private/link-local addresses # (e.g. a receiver on the same Docker network). Off by default. # Also required when outbound traffic goes through HTTP_PROXY and only the # proxy can resolve DNS: the server classifies proxied targets by resolving # them itself and refuses names it cannot resolve. WEBHOOK_ALLOW_PRIVATE=false # ─── Quotas ────────────────────────────────────── QUOTA_WARNING_THRESHOLD=0.8 # ─── Admin Bootstrap ───────────────────────────── # Comma-separated emails to auto-promote to admin on first login # (to owner while the install has no owner). When set, only these # emails can complete first-run setup. ADMIN_EMAILS= # ─── Release storage (optional, S3-compatible) ─── # Needed for release management (artifact uploads, update feeds, # license-gated downloads). Leave STORAGE_BUCKET empty to turn it off; # license and billing features work without it. BUCKET, ACCESS_KEY and # SECRET_KEY must be set together. # Endpoint: empty for AWS S3, e.g. https://.r2.cloudflarestorage.com # for Cloudflare R2, or http://minio:9000 for MinIO. STORAGE_ENDPOINT= # Region: auto for R2, the bucket's region for AWS S3. STORAGE_REGION=auto STORAGE_BUCKET= STORAGE_ACCESS_KEY= STORAGE_SECRET_KEY= # true for MinIO and most self-hosted S3 gateways. STORAGE_FORCE_PATH_STYLE=false # Presigned URL lifetimes STORAGE_UPLOAD_TTL=1h STORAGE_DOWNLOAD_TTL=10m # Lifetime of artifact URLs inside public update feeds (Sparkle/Velopack/Tauri). # Users may click Install long after the feed was fetched. STORAGE_FEED_URL_TTL=24h # Master key that encrypts secrets at rest: release-signing keys, email # provider credentials and license keys. Required when release storage # is enabled; recommended in any production install. # Generate with: openssl rand -hex 32 # Keep it safe and stable: losing or changing it makes existing signing # keys undecryptable, and signing then fails. RELEASE_KEY_ENCRYPTION_KEY= # Largest artifact the server will read, hash and sign at publish, in MB. MAX_RELEASE_SIGN_SIZE_MB=500