mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP had been changed to mean signed requests only, so an operator who set it for a sender that adds a timestamp but cannot sign would have had every deploy notification refused after the upgrade. It means the secret header plus a fresh timestamp again. Signature-only, the setting that stops a replay, is DEPLOY_WEBHOOK_REQUIRE_SIGNATURE, on the compose whitelist, in the env manifest and examples, and in the deploy guide.
407 lines
22 KiB
Bash
407 lines
22 KiB
Bash
# =============================================================================
|
|
# HealthLog — Environment Configuration
|
|
# =============================================================================
|
|
# Copy this file to .env and fill in the values.
|
|
# $ cp .env.example .env
|
|
#
|
|
# This is a short starter. Every variable the app reads, with what it does,
|
|
# is documented in .env.production.example.
|
|
#
|
|
# Required variables: DATABASE_URL or POSTGRES_PASSWORD,
|
|
# ENCRYPTION_KEY, API_TOKEN_HMAC_KEY
|
|
#
|
|
# Generate the two secrets with:
|
|
# $ openssl rand -hex 32 # any UNIX shell
|
|
# $ node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))'
|
|
#
|
|
# All secrets must be 64-character hex strings (= 32 bytes).
|
|
# =============================================================================
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Database
|
|
# -----------------------------------------------------------------------------
|
|
# Bundled docker-compose.yml path (the common case): you do NOT set DATABASE_URL
|
|
# here. Compose CONSTRUCTS it inside the `app` service from POSTGRES_PASSWORD
|
|
# (see docker-compose.yml — the `environment:` block wins over any value in this
|
|
# file), so a DATABASE_URL line here is ignored for that stack. Set only
|
|
# POSTGRES_PASSWORD below and compose wires the connection string for you.
|
|
#
|
|
# Bare-Node / custom-compose path (`pnpm dev` against a host Postgres, or your
|
|
# own compose that does NOT derive DATABASE_URL): uncomment DATABASE_URL and set
|
|
# it explicitly. Hostname is "db" on the bundled compose network, or
|
|
# "localhost" for a host-installed Postgres.
|
|
# DATABASE_URL="postgresql://healthlog:YOUR_PASSWORD@localhost:5432/healthlog?schema=public"
|
|
|
|
# Postgres password for the bundled `db` service — and the value compose splices
|
|
# into DATABASE_URL. The quick-start appends a generated one:
|
|
# echo "POSTGRES_PASSWORD=$(openssl rand -hex 32)" >> .env
|
|
# Left commented here so that append does not create a duplicate key. Uncomment
|
|
# and set it directly instead if you prefer to edit the file by hand.
|
|
# POSTGRES_PASSWORD="generate-with-openssl-rand-hex-32"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# App URL
|
|
# -----------------------------------------------------------------------------
|
|
# The public URL where users reach the app. Behind a reverse proxy with TLS,
|
|
# this looks like https://healthlog.example.com. For local dev, leave default.
|
|
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
APP_URL="http://localhost:3000"
|
|
|
|
# Node environment: "development" for `pnpm dev`, "production" for Docker.
|
|
NODE_ENV="development"
|
|
|
|
# Session cookie `Secure` flag (v1.5.1).
|
|
# Defaults to NODE_ENV === "production" — the safe choice for any
|
|
# deployment terminated by HTTPS (Caddy / Traefik / Nginx in front).
|
|
# Set to `false` if you intentionally serve plain HTTP on a LAN, VPN,
|
|
# or Tailscale-only surface — the browser otherwise drops the session
|
|
# cookie on HTTP origins and login round-trips fail silently. NEVER
|
|
# set to `false` for a deployment that serves plain HTTP to the open
|
|
# internet; the session cookie crosses the wire in cleartext.
|
|
# SESSION_COOKIE_SECURE="false"
|
|
|
|
# Where this server's environment is configured: compose, truenas, coolify,
|
|
# unraid or portainer. Only picks which instructions the admin's "Back up
|
|
# your encryption key" step shows first; unset means compose.
|
|
# HEALTHLOG_PLATFORM="compose"
|
|
|
|
# Start-up encryption key check: enforce (default) refuses to serve when the
|
|
# key does not open the stored data; warn only logs it and serves anyway. A
|
|
# last resort for a refusal you are certain is false (a first start, with no
|
|
# recorded key to reset). It disables a safety check: with a wrong key every
|
|
# encrypted value fails and new rows are written under that key.
|
|
# ENCRYPTION_KEY_CHECK="enforce"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Required Secrets — generate with `openssl rand -hex 32`
|
|
# -----------------------------------------------------------------------------
|
|
# AES-256-GCM key for encrypting Withings tokens, GitHub PATs, AI provider
|
|
# keys, and VAPID private keys at rest. Rotate carefully — old ciphertexts
|
|
# need re-encryption.
|
|
ENCRYPTION_KEY="REPLACE-WITH-OUTPUT-OF-openssl-rand-hex-32"
|
|
|
|
# HMAC-SHA256 key for hashing external-API Bearer tokens before storing them
|
|
# in the database. Rotating it invalidates all issued tokens.
|
|
API_TOKEN_HMAC_KEY="REPLACE-WITH-OUTPUT-OF-openssl-rand-hex-32"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Withings OAuth (optional — for automatic scale/BP/activity sync)
|
|
# -----------------------------------------------------------------------------
|
|
# Register an app at https://developer.withings.com/ and enter its client id
|
|
# and secret in Settings -> Integrations -> Withings. There is no environment
|
|
# variable for them; only the redirect override and webhook secret live here.
|
|
# WITHINGS_REDIRECT_URI="https://healthlog.example.com/api/withings/callback"
|
|
# WITHINGS_WEBHOOK_SECRET="" # any random string — used to harden the webhook URL
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Telegram (optional — webhook hardening)
|
|
# -----------------------------------------------------------------------------
|
|
# Set to verify incoming Telegram updates. The bot token itself is configured
|
|
# per-user in Settings (encrypted at rest).
|
|
# TELEGRAM_WEBHOOK_SECRET=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Notification targets on a private network (optional)
|
|
# -----------------------------------------------------------------------------
|
|
# Webhook (Gotify, Home Assistant, n8n) and ntfy targets on your own network,
|
|
# comma-separated exact origins, e.g. "https://gotify.example.com,http://ntfy.lan:8080".
|
|
# A target resolving to a private address is otherwise refused at send time.
|
|
# Link-local, metadata and unspecified addresses stay refused even when listed.
|
|
# See docs/self-hosting/notifications.md.
|
|
# NOTIFICATION_PRIVATE_ORIGINS=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Document picker: Paperless-ngx and Papra -- optional
|
|
# -----------------------------------------------------------------------------
|
|
# The Paperless-ngx and Papra instances people may connect to pick documents
|
|
# from, comma-separated exact origins (scheme://host[:port], no path), e.g.
|
|
# "https://paperless.example.com,http://papra.lan:1221". Unset, the document
|
|
# picker is switched off. Only a listed origin is ever contacted, public or
|
|
# private; requests are pinned to it, redirects are not followed, and
|
|
# link-local, metadata and unspecified addresses stay refused even when
|
|
# listed. A malformed entry is logged once and grants nothing.
|
|
# See docs/self-hosting/document-sources.md.
|
|
# DOCUMENT_SOURCE_ORIGINS=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# AI Provider (optional)
|
|
# -----------------------------------------------------------------------------
|
|
# Users bring their own API keys via Settings. No server-side key needed by
|
|
# default. The admin can configure a fallback key in the Admin Panel.
|
|
|
|
# AI endpoints on your own network (optional). An AI base URL on a private
|
|
# address is refused unless its exact origin is listed here, comma-separated
|
|
# (e.g. "http://ollama.lan:11434,http://10.0.0.5:4000") — for a self-hosted
|
|
# Ollama, LM Studio or LiteLLM gateway. Link-local, metadata and unspecified
|
|
# addresses stay refused even when listed. See docs/integrations/ai-providers.md.
|
|
# AI_PRIVATE_ORIGINS=""
|
|
# Older form, kept for existing installs: an exact host list still works
|
|
# (e.g. "ollama.lan,10.0.0.5"). "true" is deprecated: it now covers only the
|
|
# admin AI key and admin accounts' own AI settings, and will be removed.
|
|
# ALLOW_LOCAL_AI_PRIVATE_HOSTS=""
|
|
|
|
# Daily-briefing output-token ceiling (optional). Default 2500, clamped to
|
|
# 500-8000. Raise it when a verbose model's briefing JSON gets cut off
|
|
# ("AI response was cut off"). See docs/integrations/ai-providers.md.
|
|
# INSIGHTS_MAX_TOKENS=""
|
|
|
|
# Maximum number of comprehensive-insight generations a single user can
|
|
# trigger per hour (POST /api/insights/generate). Default 10. The 24h
|
|
# DB cache short-circuits this for read traffic; only force=true and
|
|
# expired-cache regenerations consume tokens. Lower this when running
|
|
# on a tight LLM budget.
|
|
# INSIGHTS_RATE_LIMIT_PER_HOUR="10"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Structured Logging — Wide Events (optional, sensible defaults)
|
|
# -----------------------------------------------------------------------------
|
|
# Levels: debug | info | warn | error
|
|
# LOG_LEVEL="info"
|
|
|
|
# Sample rate for successful requests (0.0 = sample nothing, 1.0 = log all).
|
|
# Errors and slow requests are ALWAYS logged regardless of this setting.
|
|
# LOG_SAMPLE_RATE="1.0"
|
|
|
|
# Slow-request threshold in milliseconds — anything slower is always kept.
|
|
# LOG_SLOW_THRESHOLD_MS="3000"
|
|
|
|
# Include stack traces on error events. Disable in environments where logs
|
|
# leave the host unencrypted.
|
|
# LOG_INCLUDE_STACK="true"
|
|
|
|
# Heap diagnostics, off by default. HEALTHLOG_HEAP_SNAPSHOT=1 writes ONE heap
|
|
# snapshot the first time the heap reaches 85 % of its limit, into
|
|
# HEALTHLOG_DIAG_DIR (default /tmp). A snapshot contains decrypted health data
|
|
# and keys; enable it only to chase a crash and delete the file afterwards.
|
|
# See docs/ops/heap-diagnostics.md.
|
|
# HEALTHLOG_HEAP_SNAPSHOT="1"
|
|
# HEALTHLOG_DIAG_DIR="/tmp"
|
|
|
|
# Optional: ship Wide Events to Loki. Leave blank for stdout-only logging.
|
|
# LOKI_ENDPOINT="http://loki:3100"
|
|
# LOKI_USERNAME=""
|
|
# LOKI_PASSWORD=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Reverse-proxy trust (optional, defaults match a single-proxy stack)
|
|
# -----------------------------------------------------------------------------
|
|
# When HealthLog sits behind Cloudflare (Tunnel, Workers, classic proxy),
|
|
# the visitor IP arrives in the `CF-Connecting-IP` header — the upstream
|
|
# `X-Forwarded-For` chain stops at the Coolify / Caddy loopback and the
|
|
# admin sign-in geo lookup has no signal to resolve from.
|
|
#
|
|
# Set this to "1" ONLY when every public request to HealthLog terminates
|
|
# at Cloudflare (Tunnel, Workers, classic proxy) before it reaches your
|
|
# origin, and the origin accepts connections from Cloudflare alone (a
|
|
# Tunnel, or a firewall that admits only Cloudflare's address ranges): the
|
|
# header is believed from every connection, before any other check.
|
|
# Deployments behind nginx, Caddy, Traefik, an AWS ALB, or any
|
|
# direct-to-Next.js exposure MUST leave this off.
|
|
#
|
|
# Security implications when the flag is mis-set:
|
|
# - `cf-connecting-ip` becomes the IP-rate-limit bucket key for
|
|
# login / chat / coach. An attacker who can set the header rotates
|
|
# it per request and defeats every IP-keyed throttle.
|
|
# - The same value lands in the `audit_logs.ipAddress` column for
|
|
# every `measurement.create`, `medication.intake`, sign-in, and
|
|
# admin action. A misconfigured flag lets any caller forge the
|
|
# IP recorded against their actions across the audit trail.
|
|
# - The admin sign-in overview's "Standort" column reads the same
|
|
# value via geo-backfill; a malicious caller can poison the column
|
|
# with a chosen IP per request.
|
|
#
|
|
# Code-level validation is strict (only "=== '1'" enables, value is
|
|
# regex- and length-bounded), but the gating decision is up to the
|
|
# operator — there is no way for the server to detect whether the flag
|
|
# matches the real deployment topology. When in doubt, leave it off.
|
|
# Number of trusted reverse-proxy hops in front of the app. The client IP is
|
|
# read that many entries from the right of the X-Forwarded-For chain. Set 0
|
|
# for a directly-exposed host; 1 (the default when unset) behind a single
|
|
# reverse proxy. X-Real-IP is read only at 1. Forwarding headers are read only
|
|
# from a connection whose own address is not public (loopback, private, CGNAT,
|
|
# unique-local, link-local, reserved or multicast; the full list is in
|
|
# docs/self-hosting/reverse-proxy.md). A wrong value makes the app
|
|
# refuse the headers, log one warning, and share one rate-limit bucket across
|
|
# all anonymous callers.
|
|
# TRUST_PROXY_HOPS="1"
|
|
# The addresses the reverse proxy connects from (comma-separated CIDRs). Set,
|
|
# only those connections may report the client's address; unset, any private
|
|
# address may, which behind Docker Desktop, OrbStack or docker-proxy includes
|
|
# every caller of the published port. Same host: 127.0.0.1/32,::1/128; Docker
|
|
# network: the proxy's subnet. See docs/self-hosting/reverse-proxy.md.
|
|
# TRUSTED_PROXY_CIDRS=""
|
|
# TRUST_CF_CONNECTING_IP=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Process split (optional, single-container deploys ignore this)
|
|
# -----------------------------------------------------------------------------
|
|
# `all` — single container runs HTTP + reminder worker (default, legacy).
|
|
# `web` — HTTP only; pair with a separate `app-worker` service.
|
|
# `worker`— pg-boss only; rejects HTTP traffic with a 503.
|
|
# See docs/self-hosting/scaling.md for the split-stack guide.
|
|
# HEALTHLOG_PROCESS_TYPE="all"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Encryption-key versioning (optional — for at-rest key rotation)
|
|
# -----------------------------------------------------------------------------
|
|
# JSON map of key id → 64-hex-char or base64 32-byte key. When set, it
|
|
# supersedes ENCRYPTION_KEY for new writes; old rows continue to decrypt under
|
|
# their original id. ENCRYPTION_ACTIVE_KEY_ID picks which entry encrypt() uses.
|
|
# Run `pnpm tsx scripts/rotate-encryption-key.ts <new-id>` to re-encrypt every
|
|
# existing row under the new key. See docs/ops/encryption-key-rotation.md.
|
|
# ENCRYPTION_KEYS='{"v1":"<old-key>","v2":"<new-key>"}'
|
|
# ENCRYPTION_ACTIVE_KEY_ID="v2"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Off-host backup (optional — daily encrypted dumps to an S3-compatible bucket)
|
|
# -----------------------------------------------------------------------------
|
|
# Required ALL-OR-NONE: setting any of these without setting the rest disables
|
|
# the backup silently. The backup runs at 02:30 Europe/Berlin via pg-boss cron.
|
|
# Restore with `pnpm tsx scripts/restore-backup.ts <userId> <YYYY-MM-DD>`.
|
|
# See docs/ops/backup-restore.md for the format spec + step-by-step.
|
|
#
|
|
# The worker never deletes a BACKUP object -- its grant is PutObject, GetObject
|
|
# and AbortMultipartUpload -- so BACKUP_RETENTION_DAYS is the number you set
|
|
# the bucket's own lifecycle rule to, not something the worker enforces. The
|
|
# one key it does delete is its own connection test's `_healthcheck/<ts>.bin`,
|
|
# and that delete is best-effort: without DeleteObject on that prefix every
|
|
# Test-connection press leaves one 1-byte orphan behind, silently.
|
|
#
|
|
# BACKUP_S3_ENDPOINT="https://<account>.r2.cloudflarestorage.com"
|
|
# BACKUP_S3_BUCKET="healthlog-backups"
|
|
# BACKUP_S3_ACCESS_KEY=""
|
|
# BACKUP_S3_SECRET_KEY=""
|
|
# BACKUP_S3_REGION="auto" # "auto" for Cloudflare R2; real region for AWS S3
|
|
# BACKUP_RETENTION_DAYS="30" # the number for your bucket lifecycle rule (see above)
|
|
# BACKUP_ENCRYPTION_KEY="" # 64-hex-char or base64 32-byte; SEPARATE from ENCRYPTION_KEY
|
|
# BACKUP_ENCRYPTION_PREVIOUS_KEYS="" # retired off-host keys, comma-separated, read-only during a rotation
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Database dumps (optional, on by default)
|
|
# -----------------------------------------------------------------------------
|
|
# The whole-database dump that rebuilds a lost server: nightly, and before
|
|
# every update that brings migrations. Written to DB_BACKUP_DIR with a
|
|
# manifest beside it (row counts, key fingerprints, versions); with the
|
|
# off-host bucket above set, each nightly dump is also uploaded under
|
|
# `database/`, sealed with BACKUP_ENCRYPTION_KEY. Local dumps are not
|
|
# encrypted: they sit beside the database volume, in the same trust zone.
|
|
# Restore: `docker compose run --rm db-restore latest`. See
|
|
# docs/ops/disaster-recovery.md.
|
|
#
|
|
# Set "false" to stop the nightly dump.
|
|
# DB_BACKUP_ENABLED="true"
|
|
# Where dumps are written. The bundled compose file mounts the `backups`
|
|
# volume here; change it only together with that mount. In a container a
|
|
# directory that is not on a mount of its own sits in the container's layer
|
|
# and is lost at the next update, so no dump is kept there.
|
|
# DB_BACKUP_DIR="/var/lib/healthlog/backups"
|
|
# Five-field cron, in Europe/Berlin time like every scheduled job.
|
|
# DB_BACKUP_SCHEDULE="30 1 * * *"
|
|
# Nightly dumps kept (at least 1). The last two pre-migration dumps are kept
|
|
# besides.
|
|
# DB_BACKUP_KEEP="7"
|
|
# Set "false" to start an update without the dump before its migrations.
|
|
# DB_BACKUP_BEFORE_MIGRATE="true"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Stored backups in the database (optional)
|
|
# -----------------------------------------------------------------------------
|
|
# The largest weekly or uploaded backup of one account the app keeps, in
|
|
# megabytes of encrypted copy. Default 2048. It does not depend on the app's
|
|
# memory: a copy is written and read in pieces of about a megabyte. The limit
|
|
# only stops a runaway copy from filling the database volume, which holds the
|
|
# old and the new copy side by side while a backup runs. An account with 2.6
|
|
# million readings stores a copy of about 100 MB.
|
|
# BACKUP_MAX_STORED_MB="2048"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Audit-trail retention (optional)
|
|
# -----------------------------------------------------------------------------
|
|
# How long rows in `audit_logs` are kept. Default 365 days; the floor is 7 and
|
|
# anything below it is ignored, so a window set in seconds by mistake cannot
|
|
# empty a fresh table. A daily pg-boss job does the pruning.
|
|
#
|
|
# The number is not internal. Settings -> Shared access states it under the
|
|
# activity list, and the dialog that ends somebody's access says how long
|
|
# "who entered what" stays answerable. Both read the value the server
|
|
# resolved, so an instance running 90 says 90 everywhere.
|
|
# AUDIT_LOG_RETENTION_DAYS="365"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# APNs — Apple Push Notification service (v1.4.23+, optional)
|
|
# -----------------------------------------------------------------------------
|
|
# All four core APNS_* vars must be set together for APNs to fan out to iOS
|
|
# devices. Leaving them unset disables the channel silently — the dispatcher
|
|
# falls back to Telegram → ntfy → Web Push as before.
|
|
#
|
|
# Provision an APNs auth key (.p8) at https://developer.apple.com/account/
|
|
# under Certificates, Identifiers & Profiles → Keys → APNs.
|
|
#
|
|
# APNS_KEY (preferred for 12-factor / Coolify env-block) and APNS_KEY_FILE
|
|
# (filesystem path) are mutually exclusive — pick one. APNS_KEY accepts the
|
|
# multi-line PEM body with literal newlines OR `\n` escapes.
|
|
# APNS_KEY_ID="" # 10-char Key ID from Apple Developer (e.g. ABCDE12345)
|
|
# APNS_TEAM_ID="" # 10-char Team ID from Apple Developer
|
|
# APNS_BUNDLE_ID="" # iOS app bundle id (e.g. dev.healthlog.ios)
|
|
# APNS_KEY="" # APNs auth key as PEM body (multi-line OK with \n escapes); preferred over APNS_KEY_FILE
|
|
# APNS_KEY_FILE="" # Alternate: filesystem path to .p8 file
|
|
# APNS_PRODUCTION="" # "true" to force the production gateway; default = mirror Device.apnsEnvironment
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Auto-deploy webhook (v1.4.15 — phase C2)
|
|
# -----------------------------------------------------------------------------
|
|
# Coolify (or any CI) pings POST /api/internal/deploy-webhook after a deploy
|
|
# attempt with success/failure. A request authenticates with this shared
|
|
# secret in the `X-Deploy-Webhook-Secret` header (timing-safe compare; what
|
|
# Coolify's stock sender does, with no replay protection), or by signing:
|
|
# `X-Deploy-Webhook-Signature: sha256=<hex>`, the HMAC-SHA256 under this
|
|
# secret of the `X-Deploy-Webhook-Timestamp` value, a dot and the raw body,
|
|
# the timestamp within 5 minutes. Generate with `openssl rand -hex 32`.
|
|
# Configure the SAME value in Coolify's Notifications → Webhook
|
|
# custom-headers panel. See docs/ops/deploy.md.
|
|
# DEPLOY_WEBHOOK_SECRET=""
|
|
# "true" also requires a fresh X-Deploy-Webhook-Timestamp on a secret-header
|
|
# request; leave unset for Coolify's stock sender, which sends none.
|
|
# DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP=""
|
|
# "true" accepts signed requests only (the setting that stops a replay);
|
|
# leave unset for Coolify's stock sender.
|
|
# DEPLOY_WEBHOOK_REQUIRE_SIGNATURE=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Outbound requests HealthLog makes on its own (optional switches)
|
|
# -----------------------------------------------------------------------------
|
|
# Each accepts "1", "true" or "yes". See .env.production.example for detail.
|
|
# PASSWORD_BREACH_CHECK_DISABLED="1" # no HaveIBeenPwned range query when a password is set
|
|
# UPDATE_CHECK_DISABLED="1" # no daily GitHub releases request
|
|
# IP_GEO_LOOKUP_DISABLED="1" # no online IP geolocation for login events
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# IP geolocation (optional)
|
|
# -----------------------------------------------------------------------------
|
|
# Opt-in for a plain-HTTP IP_GEO_LOOKUP_URL provider (e.g. free ip-api.com); off by default — looked-up IPs then travel unencrypted over your server's egress.
|
|
# IP_GEO_ALLOW_INSECURE="true"
|
|
#
|
|
# Directory the offline GeoLite2 databases are read from; default /opt/geolite2 (the path the image bakes its own copy into). Point it at a directory you mount yourself to bring your own MMDBs — see docs/self-hosting/geolite2.md.
|
|
# GEOLITE2_DIR="/var/lib/geolite2"
|
|
#
|
|
# MaxMind licence key for the runtime GeoLite2 fetch (issue #659): set it and the worker downloads both databases into GEOLITE2_DIR itself, no rebuild or manual mount. Free key at https://www.maxmind.com/en/geolite2/signup — see docs/self-hosting/geolite2.md.
|
|
# MAXMIND_LICENSE_KEY="your-maxmind-licence-key"
|