mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Add the opt-in Environment module (default off), its Settings surface (home location search, travel overrides, backfill, with Open-Meteo attribution), the gated API routes (overview, home, geocode, travel CRUD, backfill), the nightly pg-boss fetch job with a lookback window plus on-demand backfill, the query keys, six-locale strings, and the regenerated OpenAPI. The module performs an outbound weather fetch tied to a coarse location, so it ships dark and the user turns it on deliberately; off means no job, no row, no egress.
250 lines
13 KiB
Bash
250 lines
13 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=""
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Fitbit -- optional integration (classic Web API, per-user BYO-keys)
|
|
# -----------------------------------------------------------------------------
|
|
# Each user registers their own app at https://dev.fitbit.com (self-serve, no
|
|
# brand verification) and pastes the OAuth Client ID + Client Secret into
|
|
# Settings (stored encrypted in the DB). There is therefore no
|
|
# FITBIT_CLIENT_ID/SECRET here. The OAuth flow uses Authorization Code + PKCE.
|
|
#
|
|
# FITBIT_REDIRECT_URI overrides the derived
|
|
# `${NEXT_PUBLIC_APP_URL}/api/fitbit/callback` when the public URL differs; it
|
|
# must exactly match the Callback URL registered in the dev.fitbit.com app.
|
|
# FITBIT_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=""
|
|
# APNS_CRITICAL_ENTITLEMENT="" # "true" only if your build has Apple's Critical Alerts entitlement; else urgent = time-sensitive
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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=""
|
|
|
|
# Local-AI SSRF opt-in (optional). The local AI provider rejects private/
|
|
# internal hosts by default. Set to an exact host allowlist (comma-separated,
|
|
# e.g. "ollama.lan,10.0.0.5") or "true" for any private host — intended for a
|
|
# self-hosted Ollama / LM Studio endpoint. See docs/integrations/ai-providers.md.
|
|
# ALLOW_LOCAL_AI_PRIVATE_HOSTS=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Client-IP resolution -- optional (required behind Cloudflare)
|
|
# -----------------------------------------------------------------------------
|
|
# When the stack sits behind Cloudflare, the real visitor IP arrives in the
|
|
# `cf-connecting-ip` header; the X-Forwarded-For chain otherwise yields the
|
|
# Cloudflare edge IP, so the audit-log overview stores the CF datacentre
|
|
# address and the geo lookup resolves the wrong location. Set this to "1"
|
|
# ONLY when Cloudflare is in front -- a direct-exposed self-host must leave
|
|
# it unset, or an attacker could forge the header. Off by default.
|
|
# TRUST_CF_CONNECTING_IP="1"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# IP geolocation lookup -- optional
|
|
# -----------------------------------------------------------------------------
|
|
# The admin login/audit overview enriches auth events with a coarse
|
|
# "City, CC" location + carrier. The default online provider is ip-api.com.
|
|
# Override the base URL to point at a different provider; the response must
|
|
# match either the ipwho.is shape (`success` + `country_code`) or the
|
|
# ip-api.com shape (`status` + `countryCode`). HTTPS only -- a non-HTTPS URL
|
|
# is refused (audit-event IPs must never egress over plaintext HTTP). The
|
|
# `/<ip>` segment is appended automatically.
|
|
# IP_GEO_LOOKUP_URL="https://ip-api.com/json"
|
|
#
|
|
# Set to "1" to disable all third-party IP egress (GDPR Art. 44). With egress
|
|
# off the resolver uses only the offline GeoLite2 MMDB tier, if present.
|
|
# IP_GEO_LOOKUP_DISABLED="1"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Environmental-context module (Open-Meteo) -- optional
|
|
# -----------------------------------------------------------------------------
|
|
# The opt-in Environment module fetches daily weather / daylight for the user's
|
|
# coarse home (or a travel override) from Open-Meteo. The hosted endpoints are
|
|
# keyless and free for non-commercial use; data is CC BY 4.0. Both URLs are
|
|
# optional -- unset uses the hosted defaults below. A privacy-maximalist or
|
|
# commercial self-hoster can run their own Open-Meteo instance and point these
|
|
# at it (a private/LAN host is allowed -- the fetch is server-side and does NOT
|
|
# force the public-host guard, so an internal instance is not blocked).
|
|
# OPENMETEO_BASE_URL="https://archive-api.open-meteo.com"
|
|
# OPENMETEO_GEOCODING_URL="https://geocoding-api.open-meteo.com"
|