-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path.env.example
More file actions
262 lines (237 loc) · 12.2 KB
/
.env.example
File metadata and controls
262 lines (237 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# === CORE ===
# Set a strong POSTGRES_PASSWORD below and replace every `<strong-password-here>`
# placeholder. Docker Compose now refuses to start if POSTGRES_PASSWORD
# is unset, so there is no dev-mode default.
#
# DATABASE_URL shape:
# - Docker compose stack (default — app/migrator services read this
# file via `env_file: .env` so it must resolve on the compose
# bridge network):
# postgresql://equismile:<strong-password-here>@postgres:5432/equismile
# - Local dev on the host (compose NOT running in front of it, or
# you're running `npm run dev` while only the `postgres` service
# is up). Swap `postgres:5432` for `localhost:5433` (the port the
# compose postgres service publishes to the host):
# postgresql://equismile:<strong-password-here>@localhost:5433/equismile
#
# The default below is the compose-internal shape so `docker compose
# up` works out of the box after you fill in the password.
#
# Production tuning: see `docs/OPERATIONS.md` §2 for the source of
# truth. Prisma's default pool size is `num_physical_cpus * 2 + 1`
# (5 on a 2-vCPU VPS), which is fine for a single-app / single-replica
# deployment. Append explicit pool params when any of these become
# true: multiple app containers, long-running queries or migrations,
# or `max_connections` tuned below the Postgres 16 default of 100.
# Recommended values for that case:
# postgresql://equismile:<strong-password-here>@postgres:5432/equismile?connection_limit=10&pool_timeout=20
# `pool_timeout=20` keeps a brief spike queueing instead of 500-ing.
# When scaling to multiple replicas, the sum of per-app
# `connection_limit` must stay within `max_connections - 10`,
# reserving 10 connections for n8n, migrator/operator, and ad-hoc
# psql sessions (see §2.3).
#
# Phase 21 (HIGH-05): the boot-time env check warns when DATABASE_URL
# lacks `connection_limit` / `pool_timeout` query params in production
# mode. Append `?connection_limit=10&pool_timeout=10` (or higher if
# your Postgres `max_connections` allows it) to silence the warning
# and bound Prisma's pool predictably under concurrent load.
DATABASE_URL=postgresql://equismile:<strong-password-here>@postgres:5432/equismile?connection_limit=10&pool_timeout=10
NODE_ENV=development
# Demo mode: when true, all integrations return mock responses.
# Set to false and fill in credentials below to use live APIs.
# NOTE: DEMO.bat always forces DEMO_MODE=true. Use LAUNCH.bat for live mode.
DEMO_MODE=false
# Per-integration override: forces the Google Maps client to call the
# real Geocoding + Route Optimization APIs even when DEMO_MODE=true.
# WhatsApp and email stay simulated under DEMO_MODE regardless. Use
# this for client demos that want a live route-optimisation visual.
# Requires GOOGLE_MAPS_API_KEY + GCP_PROJECT_ID below.
# WARNING: real Google billing applies on every call. Unset after the
# demo to avoid leaving live-API spend on indefinitely.
EQUISMILE_LIVE_MAPS=false
# === WHATSAPP (Meta Cloud API) ===
# Get from: https://developers.facebook.com > Your App > WhatsApp > API Setup
WHATSAPP_VERIFY_TOKEN=equismile-verify-2026
WHATSAPP_ACCESS_TOKEN=EEAAfaIMBDD1sBRiZBkIxGPFwWiQS43Mijcm10PD7WReCo2nktok1Hg50rLYNtMjZBx95d6OdhhUlC5ggBO80D4RF42klBbTN6M5P0zt5WHZA2XybWVLXAPZCBgC6hoHRimt2xwFiwtViq2oFQjYrVPTzuCz9K62EOyDYJLlV5AaC4NNDaCJtJ3l1KHBZAjHfTCUmOPR1oHpH4mhLa1IcErvD0xw6SyF39uyTsMTF2d1bwWgwl2Y7k2eL4HIdBp486PB5eFDy487zPphICD3OJCDUGg
WHATSAPP_PHONE_NUMBER_ID=1024999164038819
WHATSAPP_BUSINESS_ACCOUNT_ID=940284085555511
WHATSAPP_APP_SECRET=bfc3ba6fd5011785716b1a04142e61ea
# === GOOGLE MAPS (Server-side) ===
# Get from: https://console.cloud.google.com > APIs & Services > Credentials
GOOGLE_MAPS_API_KEY=AIza...your-server-key
GCP_PROJECT_ID=your-project-id
# === GOOGLE MAPS (Browser — only if rendering maps in frontend) ===
NEXT_PUBLIC_GOOGLE_MAPS_BROWSER_KEY=AIza...your-browser-key
# Phase 20 — Map route-line rendering mode.
# directions (default) → use client-side DirectionsService for real
# road-following polylines. No server quota cost; client-side
# calls are included in the Maps JavaScript API base load. Best
# for production with real geocoded yard coordinates.
# straight → use straight-line geodesic polylines. Use this in
# demo/dev where the seed coordinates are synthetic and don't snap
# to real roads.
NEXT_PUBLIC_MAP_ROUTING_MODE=directions
# === EMAIL / SMTP ===
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_FROM=EquiSmile <noreply@equismile.com>
# === n8n ===
# NOTE: Inbound email intake is handled by n8n workflows, not direct IMAP polling.
# See docs/ARCHITECTURE.md for details.
#
# N8N_API_KEY is REQUIRED in production (DEMO_MODE=false). Every
# /api/n8n/* and /api/reminders/check route fails closed (HTTP 500)
# when this is unset. Generate with: openssl rand -base64 32
N8N_API_KEY=your-n8n-internal-key
N8N_WEBHOOK_URL=http://localhost:5678/webhook
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
# REQUIRED. Do not leave as "changeme" — the n8n admin UI is reachable
# at n8n.<your-domain> via Caddy and on 127.0.0.1:5678 on the host.
N8N_BASIC_AUTH_PASSWORD=changeme
N8N_PORT=5678
N8N_PROTOCOL=http
N8N_HOST=localhost
# Host interface the n8n container binds :5678 to. Default 127.0.0.1
# (loopback-only) so the n8n admin UI is not on the public internet by
# default. Set to `0.0.0.0` only if you genuinely need direct public
# access; prefer the Caddy `n8n.<domain>` subdomain or an SSH tunnel.
N8N_BIND_ADDR=127.0.0.1
# === DATABASE (Docker Compose) ===
POSTGRES_USER=equismile
# REQUIRED. Generate with: openssl rand -base64 24
POSTGRES_PASSWORD=<strong-password-here>
POSTGRES_DB=equismile
# === BACKUPS (Phase 16) ===
# The `backup` compose service runs pg_dump on this cron schedule
# inside its own container (no host cron needed). 5-field cron syntax,
# UTC. The service rotates files older than BACKUP_RETENTION_DAYS.
BACKUP_CRON=30 2 * * *
BACKUP_RETENTION_DAYS=14
# === CADDY / DOMAIN ===
DOMAIN=localhost # Your production domain (e.g., equismile.example.com)
# === APP ===
HOME_BASE_LAT=46.4553
HOME_BASE_LNG=6.8561
HOME_BASE_ADDRESS=Blonay, Switzerland
# Browser-side home-base coordinates. Read by the route-runs detail
# page (`/route-runs/[id]`) to anchor the route map's "H" marker.
# These MUST match HOME_BASE_LAT/LNG above — the duplication exists
# because Next.js only ships NEXT_PUBLIC_* env vars to the client
# bundle. If unset, the map falls back to a hardcoded Blonay default.
NEXT_PUBLIC_HOME_BASE_LAT=46.4553
NEXT_PUBLIC_HOME_BASE_LNG=6.8561
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_DEFAULT_LOCALE=en
# CORS allow-list for `/api/*`. Comma-separated list of origins
# (scheme + host + optional port; no trailing slash, no path) that may
# call the JSON API from a browser context. Same-origin requests work
# regardless of this list — set it only when an additional frontend
# (admin subdomain, separate PWA host, mobile-app webview) needs to
# call `/api/*`.
#
# Webhooks (/api/webhooks/*), Auth.js (/api/auth/*) and n8n callbacks
# (/api/n8n/*, /api/reminders/check) are deliberately excluded from
# CORS — they have their own server-to-server auth and must not
# advertise CORS headers (Allow-Origin, Allow-Credentials, etc.) to
# arbitrary browser origins.
#
# Defaults to `[NEXT_PUBLIC_APP_URL]` when unset.
# Example: APP_ALLOWED_ORIGINS=https://admin.equismile.example,https://pwa.equismile.example
APP_ALLOWED_ORIGINS=
# === CLINICAL ATTACHMENTS (Phase 12) ===
# Filesystem directory where uploaded horse attachments (PDFs, clinical images)
# are stored. The Prisma row keeps a relative path so switching to S3/GCS is
# a storage-backend change only. Must be writable by the app process.
ATTACHMENT_STORAGE_DIR=./data/attachments
# === VISION / CLINICAL AI (Phase 13) ===
# Anthropic API key — required by the vision pipeline. Without it,
# POST /api/attachments/[id]/analyse returns 503.
# Get one at https://console.anthropic.com.
ANTHROPIC_API_KEY=sk-ant-api03-...
# Override the model used for vision analysis. Defaults to claude-opus-4-7.
# EQUISMILE_VISION_MODEL=claude-opus-4-7
# === AUTH (Auth.js) ===
# Generate a strong random value: `openssl rand -base64 32`
AUTH_SECRET=replace-with-openssl-rand-base64-32
# Public URL of the app — must match the OAuth app callback host.
AUTH_URL=http://localhost:3000
#
# Provider 1: GitHub OAuth (optional — set both AUTH_GITHUB_ID + AUTH_GITHUB_SECRET to enable)
# Create at https://github.com/settings/developers > New OAuth App
# Authorisation callback URL: ${AUTH_URL}/api/auth/callback/github
AUTH_GITHUB_ID=your-github-oauth-client-id
AUTH_GITHUB_SECRET=your-github-oauth-client-secret
#
# Provider 2: Email magic-link (optional — set AUTH_EMAIL_ENABLED=true AND configure SMTP_* above)
# When enabled, users can sign in with a one-time email link (valid 15 minutes).
# Useful for teammates who don't have a GitHub account.
AUTH_EMAIL_ENABLED=false
#
# Comma-separated GitHub logins and/or email addresses allowed to sign in.
# Matching is case-insensitive. Leave empty to deny all (useful for lockdown).
# Example: rjk134,second.vet@example.com
ALLOWED_GITHUB_LOGINS=rjk134
# === OBSERVABILITY (Phase 16) ===
# Point this at any JSON-POST endpoint to receive structured error
# events from the app (Slack incoming webhook, a self-hosted log
# collector, a Sentry relay, etc.). Leave unset to run without remote
# error tracking — stderr logs still work.
#
# Enabling this wires `logger.error(...)` → webhook via
# `instrumentation.ts` at startup. Calls are fire-and-forget with a
# 2-second timeout, in-process dedupe (60s window), and PII scrubbing.
# EQUISMILE_ERROR_WEBHOOK_URL=https://hooks.slack.com/services/...
# Optional bearer token for collectors that need auth.
# EQUISMILE_ERROR_WEBHOOK_TOKEN=
# Free-text environment label attached to every event ("production",
# "staging", "uat"). Falls back to NODE_ENV.
# EQUISMILE_ENV=production
# Phase 21 (HIGH-02) — OPTIONAL: Sentry error sink. Set this to your
# Sentry project's DSN AND `npm install @sentry/nextjs` to enable a
# second error sink alongside the webhook above. Both sinks fire in
# parallel — they are not mutually exclusive. The Sentry sink is
# dynamically imported at boot; if the package is not installed when
# SENTRY_DSN is set, the app logs a one-time warning to stderr and
# falls through to the webhook path. See docs/OPERATIONS.md §6.
# SENTRY_DSN=https://<hash>@oXXXXXX.ingest.sentry.io/<project-id>
# === VERCEL DEPLOY HOOK ===
# OPTIONAL — for triggering Vercel deploys via a deploy hook from local
# laptops or `scripts/trigger-vercel-deploy.sh`.
#
# The URL is a project-specific Vercel deploy hook. Get it from:
# Vercel dashboard → Project Settings → Git → Deploy Hooks.
# Anyone with this URL can trigger a deploy, so:
# - NEVER commit the real value to git.
# - On a developer laptop, add it to `.env.local` (gitignored).
# - For CI use, store as the GitHub repo secret of the same name
# and reference from `.github/workflows/vercel-deploy-trigger.yml`.
# See `docs/VERCEL.md` §9 for the full setup walkthrough.
# VERCEL_DEPLOY_HOOK_URL=https://api.vercel.com/v1/integrations/deploy/prj_.../...
# === PHASE 3 § 3.4 — VOICE TRANSCRIPTION (STT) ===
# OPTIONAL. Leave both unset to keep the deterministic mock transcript
# path used in DEMO_MODE + when no provider is configured.
# Currently supported provider: `openai` (Whisper-1, ~$0.006/min).
# EQUISMILE_STT_PROVIDER=openai
# OPENAI_API_KEY=sk-...
# === ELEVENLABS VOICE (TTS + STT) ===
# Powers /api/tts (streaming text-to-speech) and /api/stt (speech-to-text)
# as well as server-side voice note delivery via lib/services/whatsapp-voice.service.ts.
#
# Get your API key at: https://elevenlabs.io/app/developers
# Find your voice ID at: https://elevenlabs.io/app/voice-lab
#
# Leave ELEVENLABS_API_KEY unset to disable voice features gracefully (503).
# TTS and WhatsApp voice notes will be silently skipped.
ELEVENLABS_API_KEY=
ELEVENLABS_VOICE_ID=
# Optional — override the default models if you want a different quality/speed:
# ELEVENLABS_TTS_MODEL=eleven_multilingual_v2
# ELEVENLABS_STT_MODEL=scribe_v1
# Set to 'true' to expose the microphone record button in the demo VoicePanel.
# Requires ELEVENLABS_API_KEY + ELEVENLABS_VOICE_ID to be set.
# Default: false (off) for privacy compliance.
FEATURE_VOICE_INPUT=false