mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Five hotfix-ready findings from the v1.4.34.3 code-quality audit. SESSION_SECRET drop (F-3): The env var was required by `docker-entrypoint.sh`, listed in `.env.example`, `docker-compose.yml`, `CONTRIBUTING.md`, and the scaling guide — but `grep -rn SESSION_SECRET src/` returned nothing. Sessions are server-stored in `prisma.session` keyed by the cookie value; the secret signed nothing. Every self-hoster carried a ceremonial 64-char hex string that the app ignored. Removed everywhere except the README (separate batch). Each fresh install now generates 3 secrets, not 4. toJson helper (F-9): Seven sites cast Prisma JSON writes via `value as unknown as Prisma.InputJsonValue`. Each call re-invented the same escape hatch. Replaced with one helper in `src/lib/db.ts`: `export const toJson = <T>(v: T) => v as unknown as Prisma.InputJsonValue;` Migrated all 7 call sites: `auth/me/doctor-report-prefs`, `dashboard/chart-overlay-prefs`, `dashboard/widgets`, `integrations/healthkit`, and 4 sites in `apple-health-import-worker`. The helper has a one-line WHY comment + a unit test pinning the cast shape stays stable. db-compat docstring (F-10): `src/lib/db-compat.ts` looks like accumulated backwards-compat cruft on first read. It's actually the schema-bootstrap path that runs ALTER-TABLE-IF-NOT-EXISTS so a fresh container syncs without `prisma migrate deploy`. Added a header docstring naming the role and pointing at `prisma/migrations/` as the canonical history. Passkey types (F-11): `src/lib/auth/passkey.ts:103,169` used `response: any` with per-line `eslint-disable`. Imported `RegistrationResponseJSON` and `AuthenticationResponseJSON` from `@simplewebauthn/types` and dropped the disables. Zero-TODO CI gate (F-15): The audit confirmed product code has zero TODO / FIXME / XXX / HACK markers — genuinely rare and worth preserving. Added a tiny GitHub Actions workflow at `.github/workflows/no-todo-markers.yml` that fails the build on any future re-introduction. Test files are excluded; product code only.
181 lines
9.1 KiB
Bash
181 lines
9.1 KiB
Bash
# =============================================================================
|
|
# HealthLog — Environment Configuration
|
|
# =============================================================================
|
|
# Copy this file to .env and fill in the values.
|
|
# $ cp .env.example .env
|
|
#
|
|
# 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
|
|
# -----------------------------------------------------------------------------
|
|
# When using docker-compose.yml: the app container reaches Postgres via the
|
|
# service name "db", so the hostname is "db" — NOT "localhost".
|
|
# When developing without Docker (`pnpm dev` against a host-installed Postgres):
|
|
# change the hostname to "localhost" and adjust the credentials.
|
|
DATABASE_URL="postgresql://healthlog:CHANGE-ME@db:5432/healthlog?schema=public"
|
|
|
|
# Postgres password — used both by the db service AND by DATABASE_URL above.
|
|
# Pick a strong random password, e.g. `openssl rand -base64 24`.
|
|
POSTGRES_PASSWORD="CHANGE-ME"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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"
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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 paste the credentials
|
|
# here. Per-user OAuth credentials can also be configured in Settings.
|
|
# WITHINGS_CLIENT_ID=""
|
|
# WITHINGS_CLIENT_SECRET=""
|
|
# 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=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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.
|
|
|
|
# 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"
|
|
|
|
# Optional: ship Wide Events to Loki. Leave blank for stdout-only logging.
|
|
# LOKI_ENDPOINT="http://loki:3100"
|
|
# LOKI_USERNAME=""
|
|
# LOKI_PASSWORD=""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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.
|
|
# 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" # objects older than this are pruned (best-effort; prefer a bucket lifecycle rule)
|
|
# BACKUP_ENCRYPTION_KEY="" # 64-hex-char or base64 32-byte; SEPARATE from ENCRYPTION_KEY
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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. We verify the request via this shared secret
|
|
# carried in the `X-Deploy-Webhook-Secret` header (timing-safe compare).
|
|
# Generate with `openssl rand -hex 32`. Configure the SAME value in Coolify's
|
|
# Notifications → Webhook custom-headers panel. See docs/audit/v1415-auto-deploy.md.
|
|
# DEPLOY_WEBHOOK_SECRET=""
|