Files

259 lines
12 KiB
Plaintext

# ============================================
# Warmbly environment reference
# ============================================
# You do not need this file to start. `make up` boots the whole platform with
# no .env at all and prints a link to claim the instance. Copy this file when
# you are ready to run something other people can reach.
#
# Defaults are no-cloud: with just the secrets in the "Required" block set,
# Warmbly runs with no AWS, GCP, Stripe, or Kafka. Each subsystem is a provider
# switch, flip one to opt into a cloud service.
#
# Config priority: env var first, then AWS SSM/Secrets Manager (only when
# AWS_CONFIG_ENABLED=true).
# ============================================
# === Required ===
# dev | prod. dev allows the published default secrets, so set prod for
# anything other people can reach. prod needs no cloud account: error reporting
# and GeoIP are used when configured and skipped with a logged note when not.
APP_ENV=prod
# cloud | self_hosted. Picks the auth defaults (login code, signup lockdown,
# email verification); each stays individually overridable below.
DEPLOYMENT_MODE=self_hosted
# JWT / session signing. Min 32 chars. MUST match the realtime service JWT_SECRET.
AUTH_SECRET=change-me-min-32-characters-long
# 64 hex chars (32 bytes). Seals mailbox SMTP/IMAP credentials at rest.
# BACK IT UP — losing it makes connected mailboxes unrecoverable. `openssl rand -hex 32`
CREDENTIALS_ENCRYPTION_KEY=
# Shared token for the backend internal API (workers + tracking use it).
INTERNAL_API_TOKEN=change-me-internal-token
PRIMARY_DB=postgres://warmbly:warmbly@localhost:5432/warmbly_dev?sslmode=disable
REDIS=redis://localhost:6379
# === Provider switches (no-cloud defaults) ===
AWS_CONFIG_ENABLED=false # true => read secrets from AWS SSM/Secrets Manager
# Event bus. nats (default): one small JetStream binary. kafka: build the images
# with GO_TAGS=kafka / CARGO_FEATURES=kafka and set KAFKA_* below.
EVENTBUS_PROVIDER=nats
NATS_URL=nats://localhost:4222
# NATS_STREAM_NAME=warmbly
# NATS_SUBJECT_PREFIX=warmbly
# Serialization. json is required with NATS and wherever workers run. avro is
# only for a Kafka + Schema Registry deployment.
CODEC_PROVIDER=json
# Encryption root key. local (default): AES master key below. aws: AWS KMS.
KMS_PROVIDER=local
# base64 32 bytes. BACK IT UP — losing it is unrecoverable. `make gen-key`
KMS_LOCAL_MASTER_KEY=
# KMS_LOCAL_MASTER_KEY_FILE=/run/secrets/kms_master_key # alternative to the inline key
# KMS_AWS_KEY_ID=alias/warmbly # when KMS_PROVIDER=aws
# Blob storage. filesystem (default): a local dir. s3: any S3-compatible store.
BLOB_PROVIDER=filesystem
BLOB_FS_ROOT=/data/blobs # shared between backend + workers (same host / shared volume)
# Public URL base for avatars/logos served by the backend's /public route.
BLOB_PUBLIC_BASE_URL=http://localhost:8080/public
# For BLOB_PROVIDER=s3 (AWS / MinIO / R2 / B2):
# BLOB_BUCKET=warmbly
# AWS_ENDPOINT_URL_S3=http://minio:9000 # non-AWS endpoint
# AWS_REGION=us-east-1
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# Delayed tasks (campaign ticks, scheduled sends). local (default): an in-process
# Postgres poller — no external service. gcloud: GCP Cloud Tasks.
TASKS_PROVIDER=local
# TASKS_LOCAL_POLL_INTERVAL=1s
# When TASKS_PROVIDER=gcloud, also set:
# CLOUD_TASKS_QUEUE_NAME=projects/<p>/locations/<l>/queues/<q>
# CLOUD_TASKS_WEBHOOK_URL=https://<api-host>/webhook/email
# GOOGLE_APPLICATION_CREDENTIALS_JSON=<service-account-email>
# Billing. none (default): no Stripe, every feature unlocked, no trial expiry.
# stripe: wire the Stripe integration. All three keys below are then required —
# the backend exits at boot if any one of them is missing.
BILLING_PROVIDER=none
# STRIPE_SECRET_KEY=
# STRIPE_WEBHOOK_SECRET=
# STRIPE_PUBLISHABLE_KEY=
# Captcha. Auto-off when TURNSTILE_SECRET is unset; set CAPTCHA_PROVIDER=turnstile
# + TURNSTILE_SECRET to require Cloudflare Turnstile on auth endpoints.
# CAPTCHA_PROVIDER=none
# TURNSTILE_SECRET=
# === Backend API ===
API_HOST=0.0.0.0:8080 # binds all interfaces already
GIN_MODE=release # debug | release
# With the shipped docker-compose.yml, set PUBLIC_HOST to your LAN IP or domain
# and APP_URL / CORS_ALLOW_ORIGINS / WEBSOCKET_URL / TRACKING_DOMAIN / the VITE_*
# frontend URLs all derive from it. Set them explicitly if you're not using that
# compose (e.g. behind a reverse proxy on https://your-domain).
# PUBLIC_HOST=192.168.1.50
# API_PUBLIC_URL is the compose input for the API base the frontends and blob
# URLs use (falls back to http://<PUBLIC_HOST>:8080); set it explicitly behind
# a reverse proxy. ENV_LABEL labels the admin panel (compose: WARMBLY_ENV_LABEL).
# API_PUBLIC_URL=https://api.example.com
# ENV_LABEL=production
APP_URL=http://localhost:5173
CORS_ALLOW_ORIGINS=http://localhost:5173,http://localhost:5174
WEBSOCKET_URL=ws://localhost:4000/socket/websocket
ENCRYPTED_KEYS_PROVIDER=postgres # backend/consumer: postgres; workers: http (below)
GEODB_PATH=/app/data/GeoLite2-City.mmdb # optional everywhere; missing = no location labels
# === Mailbox connections ===
# Gmail mailboxes need YOUR Google Cloud OAuth client (a "Web application" client)
# with authorized redirect URI <API_HOST>/addresses/google/callback. Set on the
# backend AND every worker. Leave unset to connect mailboxes only via SMTP/IMAP.
BOX_GOOGLE_CLIENT_ID=
BOX_GOOGLE_CLIENT_SECRET=
# Microsoft 365 / Outlook OAuth client (redirect <API_HOST>/addresses/outlook/callback):
BOX_OUTLOOK_CLIENT_ID=
BOX_OUTLOOK_CLIENT_SECRET=
# === Social sign-in (all optional; email+password / passkeys work standalone) ===
# GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET / GOOGLE_REDIRECT_URI are the LOGIN client,
# separate from the BOX_GOOGLE_* mailbox client above.
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
# GOOGLE_REDIRECT_URI=
# GOOGLE_IOS_CLIENT_ID=
# APPLE_APP_ID=
# APPLE_TEAM_ID=
# APPLE_KEY_ID=
# APPLE_KEY_SECRET=
# Passkeys (WebAuthn): derived from APP_URL when unset. Changing the RP ID
# invalidates enrolled passkeys, so keep it stable per deployment.
# WEBAUTHN_RP_ID=app.example.com
# WEBAUTHN_RP_ORIGINS=https://app.example.com
# === Worker (each worker process) ===
# Workers hold no relational DB; they reach DEKs over the backend internal API.
# ENCRYPTED_KEYS_PROVIDER=http
# ENCRYPTED_KEYS_BACKEND_URL=http://backend:8080
# ENCRYPTED_KEYS_WORKER_TOKEN=<same as INTERNAL_API_TOKEN>
# WORKER_ID=<uuid> # stable identity; otherwise derived from hostname
# === Platform email (login codes, resets, invitations, digests) ===
#
# MAIL_TRANSPORT is the primary switch: smtp | log | ses.
#
# log — writes every message to the backend logs and delivers nothing. The
# default for a fresh install so first login works with no relay.
# smtp — a real submission relay. Set SMTP_HOST plus the credentials below.
# ses — AWS SES; needs AWS credentials and a verified identity.
MAIL_TRANSPORT=log
EMAIL_NAME=Warmbly
EMAIL_ADDRESS=noreply@example.com
TRACKING_DOMAIN=localhost:3000
# SMTP_SECURITY is starttls | tls | none. starttls is submission on 587 and the
# right answer for almost every provider; tls is implicit TLS on 465; none is
# cleartext and only legitimate for a sink on the same host. The port defaults
# per mode, so setting either one alone is enough.
#
# Credentials are never sent over an unencrypted connection.
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_USERNAME=
# SMTP_PASSWORD=
# SMTP_SECURITY=starttls
# SMTP_AUTH=auto # auto | plain | login | cram-md5 | none
# SMTP_EHLO_NAME= # defaults to the sender domain
# SMTP_TLS_INSECURE_SKIP_VERIFY=false # only for a relay with a private CA
# === Auth policy ===
#
# AUTH_LOGIN_CODE: always | new_device | off. Self-host defaults to off, so a
# login never depends on outbound mail. Registration and password reset still
# use emailed codes, which is what NIST SP 800-63B permits email for.
# AUTH_LOGIN_CODE=off
# REQUIRE_EMAIL_VERIFICATION=false
#
# DISABLE_REGISTRATION: true | false | invite_only. Self-host defaults to
# invite_only, with a first-launch exemption so the very first signup works.
# DISABLE_REGISTRATION=invite_only
# DISABLE_PASSWORD_LOGIN=false # OIDC-only deployments
# AUTH_IP_RATE_LIMIT=60 # unauthenticated auth requests per IP per 15 min
#
# CIDRs allowed to set X-Forwarded-For. Empty trusts nothing, which is correct
# for a directly exposed backend. Set it when a reverse proxy sits in front, or
# rate limits and audit IPs are attacker-controlled.
# TRUSTED_PROXIES=10.0.0.0/8
# First owner, read only while the users table is empty. Leave unset and the
# backend prints a single-use setup link to its logs on first boot.
# WARMBLY_BOOTSTRAP_EMAIL=you@example.com
# WARMBLY_BOOTSTRAP_PASSWORD_HASH= # argon2 PHC string; preferred
# WARMBLY_BOOTSTRAP_PASSWORD= # plaintext convenience; warns at boot
# WARMBLY_BOOTSTRAP_ORG=My Organization
# === Single sign-on (generic OpenID Connect) ===
#
# The only sign-in path with no dependency on outbound mail, so it is the
# recommended posture for a deployment with no relay. Works with Authentik,
# Keycloak, Zitadel, Pocket ID, Dex and anything else that publishes a
# discovery document. RS256 ID tokens only.
# OIDC_ISSUER_URL=https://id.example.com/application/o/warmbly/
# OIDC_CLIENT_ID=
# OIDC_CLIENT_SECRET=
# OIDC_REDIRECT_URL= # defaults to API_PUBLIC_URL/api/v1/auth/oidc/callback
# OIDC_SCOPES=openid,profile,email
# OIDC_ALLOWED_DOMAINS=example.com
# OIDC_DEFAULT_ORG= # org every SSO user joins; without it each
# # new user gets their own single-member org
# OIDC_PROVIDER_NAME=Single sign-on
# === Transactional email branding ===
# A self-hosted install should not send mail attributed to another company.
# EMAIL_BRAND_NAME=Acme
# EMAIL_BRAND_LEGAL_ENTITY=Acme Ltd
# EMAIL_BRAND_COMPANY_NUMBER=
# EMAIL_BRAND_PLACE_OF_REG=
# EMAIL_BRAND_ADDRESS=
# EMAIL_BRAND_WEBSITE_URL=https://acme.example.com
# EMAIL_BRAND_SUPPORT_EMAIL=support@acme.example.com
# EMAIL_BRAND_TERMS_URL=
# EMAIL_BRAND_PRIVACY_URL=
# === Tracking service (open/click) ===
TRACKING_HOST=0.0.0.0
TRACKING_PORT=3000
# Resolves opaque /c/<id> click tickets via the backend internal API.
# BACKEND_INTERNAL_URL=http://backend:8080
TRACKING_RATE_LIMIT_PER_MIN=300
# === Realtime service (Elixir/Phoenix) ===
PHX_HOST=localhost
PORT=4000
JWT_SECRET=change-me-min-32-characters-long # MUST equal the backend AUTH_SECRET
SECRET_KEY_BASE=change-me-phoenix-secret-key-base-min-64-characters-long
DATABASE_URL=postgres://warmbly:warmbly@localhost:5432/warmbly_dev?sslmode=disable
REDIS_URL=redis://localhost:6379
# Realtime transport. false (default): Redis bridge, no cloud. Read identically by
# backend, consumer, and realtime — never set true on one side only.
PUBSUB_ENABLED=false
CHECK_ORIGIN=false
# When PUBSUB_ENABLED=true (Google Pub/Sub): also set GCP_PROJECT_ID +
# GOOGLE_APPLICATION_CREDENTIALS_JSON on every service.
# GCP_PROJECT_ID=
# === AI provider (optional; omit all to run with AI features off) ===
# Set on the backend AND consumer. Self-host bills your provider directly, so the
# credit ledger is bypassed (unlimited) when BILLING_PROVIDER=none.
# AI_PROVIDER= # openai | openrouter | groq | ollama | anthropic | custom
# AI_API_KEY=
# AI_MODEL=
# AI_BASE_URL= # required for custom
# SEARCH_PROVIDER= # optional web-search tool
# SEARCH_API_URL=
# SEARCH_API_KEY=
# === Observability ===
# Optional in dev; REQUIRED (fatal at boot) on every Go service when APP_ENV=prod.
# SENTRY_DSN=