Files
HealthLog/.env.production.example
T
Marc-André Bombeck 266109f501 docs(self-hosting): VAPID generate button, env names, backup and whitelist notes
Document the new Generate keys button in notifications.md as the easiest
Web Push setup path, demoting the CLI to an alternative. Note that
clientManaged medication-reminder suppression is an iOS-app-only contract
so a PWA-only operator does not go hunting for a web toggle.

Add the VAPID env-var names and APNS_KEY_B64 to .env.production.example
(the loader and APNs sender read them, but the example omitted them),
both as commented placeholders, with a note that Web Push needs no Apple
account and that these vars are not in the compose whitelist.

Add a Backup and restore section to getting-started.md: a local pg_dump
one-liner and the load-bearing warning that the ENCRYPTION_KEYS /
ENCRYPTION_KEY must be backed up alongside the dump or the encrypted data
is unrecoverable, cross-linking the off-host backup and key-rotation
runbooks. Add troubleshooting entries for the compose env-var whitelist
and pull_policy: always.
2026-06-15 00:58:39 +02:00

189 lines
9.3 KiB
Bash

# =============================================================================
# HealthLog -- production .env example
# =============================================================================
# Mirrors `scripts/env-manifest.json`. The `pnpm check-env --file
# .env.production.example` CI step (see `.github/workflows/env-check.yml`)
# enforces every variable declared `required: true` in the manifest has a
# non-empty placeholder here, so a contributor who introduces a new
# required var without updating this file fails the workflow.
#
# Placeholders are syntactic only -- replace with real secrets before
# deploying. The check-env presence rule treats any non-empty,
# non-whitespace string as "set", so the `<placeholder>` style below
# satisfies the manifest without leaking a usable credential.
#
# Generate the two crypto secrets locally with:
# $ openssl rand -hex 32
# =============================================================================
# -----------------------------------------------------------------------------
# Core -- required for boot
# -----------------------------------------------------------------------------
DATABASE_URL="postgresql://healthlog:<password>@db:5432/healthlog?schema=public"
POSTGRES_PASSWORD="<openssl rand -hex 32>"
NEXT_PUBLIC_APP_URL="https://healthlog.example.com"
APP_URL="https://healthlog.example.com"
ENCRYPTION_KEY="<openssl rand -hex 32>"
API_TOKEN_HMAC_KEY="<openssl rand -hex 32>"
# -----------------------------------------------------------------------------
# Withings OAuth -- optional integration
# -----------------------------------------------------------------------------
# WITHINGS_CLIENT_ID=""
# WITHINGS_CLIENT_SECRET=""
# WITHINGS_WEBHOOK_SECRET=""
# -----------------------------------------------------------------------------
# WHOOP -- optional integration
# -----------------------------------------------------------------------------
# The WHOOP app client id/secret are per-user BYO-keys: each user registers
# their own WHOOP developer app and pastes the client id/secret into Settings
# (stored encrypted in the DB). There is therefore no WHOOP_CLIENT_ID/SECRET.
#
# WHOOP_WEBHOOK_SECRET is the instance-level secret carried as the trailing
# path segment of the webhook URL AND used to verify the WHOOP HMAC body
# signature. WHOOP_REDIRECT_URI overrides the derived
# `${NEXT_PUBLIC_APP_URL}/api/whoop/callback` when the public URL differs.
# WHOOP_WEBHOOK_SECRET="<openssl rand -hex 32>"
# WHOOP_REDIRECT_URI=""
# -----------------------------------------------------------------------------
# Polar AccessLink -- optional integration (full OAuth, shared app from env)
# -----------------------------------------------------------------------------
# Register an app at https://admin.polaraccesslink.com and set its redirect URI
# to `${NEXT_PUBLIC_APP_URL}/api/polar/callback`. Both vars together enable the
# Polar connect button; set neither to leave the integration disabled. Polar
# access tokens do not expire and carry no refresh token. POLAR_REDIRECT_URI
# overrides the derived callback when the public URL differs.
# POLAR_CLIENT_ID=""
# POLAR_CLIENT_SECRET=""
# POLAR_REDIRECT_URI=""
# -----------------------------------------------------------------------------
# Oura Cloud v2 -- optional integration (full OAuth, shared app from env)
# -----------------------------------------------------------------------------
# Register an app at https://cloud.ouraring.com/oauth/applications and set its
# redirect URI to `${NEXT_PUBLIC_APP_URL}/api/oura/callback`. Both vars together
# enable the Oura connect button. Oura uses refresh tokens; the sync refreshes
# reactively on a 401. OURA_REDIRECT_URI overrides the derived callback.
# OURA_CLIENT_ID=""
# OURA_CLIENT_SECRET=""
# OURA_REDIRECT_URI=""
# -----------------------------------------------------------------------------
# Web Push -- VAPID keys (optional; the admin panel is the easier path)
# -----------------------------------------------------------------------------
# Web Push needs NO Apple account and no paid service -- just a VAPID keypair.
# The EASIEST path is the admin panel: /admin -> Web Push VAPID -> "Generate
# keys" mints and stores the pair for you (private key encrypted at rest). Use
# these env vars only if you prefer env-config over the DB. The loader reads
# DB first, then env (src/lib/notifications/vapid-config.ts). NOTE: these are
# NOT in docker-compose.yml's `environment:` whitelist -- if you go the env
# route under compose, add them there too or the values never reach the
# container (see docs/self-hosting/getting-started.md Troubleshooting).
# VAPID_PUBLIC_KEY=""
# VAPID_PRIVATE_KEY=""
# VAPID_SUBJECT="mailto:you@example.com"
# -----------------------------------------------------------------------------
# APNs -- iOS push (optional, all-or-none)
# -----------------------------------------------------------------------------
# Only the native iOS app uses APNs, and it needs a paid Apple Developer
# Program membership. Web Push (above) needs NO Apple account -- it covers
# every browser and the installed PWA on iPhone. Leave this whole block unset
# unless you ship the native app. Key-source precedence when more than one is
# set: APNS_KEY_B64 > APNS_KEY > APNS_KEY_FILE.
# APNS_KEY_ID=""
# APNS_TEAM_ID=""
# APNS_BUNDLE_ID=""
# APNS_KEY_B64="" # PEM body base64-encoded -- recommended (escapes newlines)
# APNS_KEY="" # OR APNS_KEY (raw PEM) OR APNS_KEY_FILE -- one satisfies the manifest
# APNS_KEY_FILE=""
# -----------------------------------------------------------------------------
# SMTP -- email notification channel (optional, all-or-none)
# -----------------------------------------------------------------------------
# Operator SMTP transport for the email channel. SMTP_HOST + SMTP_PORT +
# SMTP_FROM together enable it; SMTP_USER/SMTP_PASS are optional (omit for an
# unauthenticated relay). SMTP_SECURE=true uses implicit TLS (port 465);
# unset/false uses STARTTLS (port 587). Per-user recipient address is set in
# Settings -> Notifications, not here. Missing the core trio disables the
# channel silently. Listed in docker-compose.yml's environment whitelist so
# these reach the container.
# SMTP_HOST=""
# SMTP_PORT="587"
# SMTP_FROM="HealthLog <noreply@example.com>"
# SMTP_USER=""
# SMTP_PASS=""
# SMTP_SECURE="false"
# -----------------------------------------------------------------------------
# Deploy webhook -- Coolify -> HealthLog auto-deploy feedback
# -----------------------------------------------------------------------------
# DEPLOY_WEBHOOK_SECRET=""
# Optional: deployment-dashboard URL (e.g. the Coolify UI) linked in
# deploy-failure notifications. When unset the notification omits the link.
# DEPLOY_LOGS_URL=""
# Optional replay hardening: require a fresh X-Deploy-Webhook-Timestamp
# header (unix seconds / ms or ISO-8601, +/- 5 minutes) on every webhook
# call. Leave unset for Coolify's stock sender (it attaches no
# timestamp); see docs/ops/deploy.md.
# DEPLOY_WEBHOOK_REQUIRE_TIMESTAMP="false"
# -----------------------------------------------------------------------------
# TLS leaf pin monitor -- alarm when the pinned leaf certificate rotates
# -----------------------------------------------------------------------------
# The native client SPKI-pins the served TLS leaf. Set this to the
# known-good leaf pin(s) so the monitor can alarm on a rotation. The value
# is base64(sha256(DER subjectPublicKeyInfo)); comma-separate to hold both
# the current and the next pin during a dual-pin renewal window. See
# docs/ops/tls-cert-pin.md for extraction + the re-pin runbook.
# TLS_LEAF_SPKI_PINS=""
# -----------------------------------------------------------------------------
# Off-host backups -- all-or-none
# -----------------------------------------------------------------------------
# BACKUP_S3_ENDPOINT=""
# BACKUP_S3_BUCKET=""
# BACKUP_S3_ACCESS_KEY=""
# BACKUP_S3_SECRET_KEY=""
# BACKUP_ENCRYPTION_KEY=""
# -----------------------------------------------------------------------------
# FHIR health-record export -- optional
# -----------------------------------------------------------------------------
# Per-export ceiling on the number of MedicationAdministration rows in the
# FHIR Bundle. The report window already scopes the set; this is a coarse
# safety ceiling on the serialised Bundle size. Bounded 1..50000; any unset
# or out-of-range value falls back to the default of 5000. Raise it only for
# an unusual multi-year, many-medication workload.
# FHIR_MAX_MEDICATION_ADMINISTRATIONS="5000"
# -----------------------------------------------------------------------------
# Dashboard snapshot -- BUILD-TIME toggle (default ON)
# -----------------------------------------------------------------------------
# The dashboard hydrates every above-the-fold tile from the single
# `GET /api/dashboard/snapshot` cell so the whole strip shares one
# completion moment. This is the default and the desired behaviour.
#
# `NEXT_PUBLIC_*` vars are inlined into the client bundle at BUILD time,
# not read at runtime -- so this has NO effect on the prebuilt GHCR
# image and is intentionally absent from the `docker-compose.yml`
# runtime `environment:` whitelist. To fall back to the legacy
# four-cell path you must rebuild the image with the var set to "false":
#
# $ NEXT_PUBLIC_DASHBOARD_SNAPSHOT=false pnpm build # or a Dockerfile ARG
#
# NEXT_PUBLIC_DASHBOARD_SNAPSHOT="false"