-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy path.env.example
More file actions
306 lines (277 loc) · 14.1 KB
/
Copy path.env.example
File metadata and controls
306 lines (277 loc) · 14.1 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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# ============================================================================
# Quackback Environment Configuration
# ============================================================================
# Copy this file to .env and fill in your values.
# For self-hosted deployments, only the "Required" section needs configuration.
#
# IMPORTANT — do NOT wrap values in quotes, and do not put inline comments on a
# value line. Docker's `--env-file` / Compose `env_file` reads the entire text
# after `=` literally: it does NOT strip quotes or trailing `# comments`. So a
# quoted DATABASE_URL="postgresql://..." is passed with the quotes attached and
# fails with `ERR_INVALID_URL`. Plain `KEY=value` works everywhere (Docker
# --env-file, Compose, and Bun's dotenv loader).
# ============================================================================
# Required
# ============================================================================
# PostgreSQL connection string
DATABASE_URL=postgresql://postgres:password@localhost:5432/quackback
# Public URL for your Quackback instance (used for auth, emails, OAuth callbacks)
BASE_URL=http://localhost:3000
# Port for the application server
PORT=3000
# Docker Compose reads this from .env automatically and uses it to name the
# dev containers/network/volumes. Pinning it to a fixed value means `docker
# compose up` from a git worktree (whose directory name isn't "quackback")
# still targets the same shared Postgres/MinIO/Mailpit containers
# instead of trying to stand up a second, conflicting stack.
COMPOSE_PROJECT_NAME=quackback
# Secret key for authentication and encryption - MUST be at least 32 characters
# This single key is used for session signing and deriving encryption keys.
# Generate with: openssl rand -base64 32
# SECRET_KEY=replace-with-generated-value
# Database connection budget. Defaults are role-aware: 10 for web/all, 20 for workers.
# DB_POOL_MAX=10
# DB_IDLE_TIMEOUT=20
# Trusted reverse-proxy hops allowed to supply client-IP headers (0 = none).
# TRUSTED_PROXY_HOPS=0
# ============================================================================
# Email (optional)
# ============================================================================
# If not configured, emails are logged to console (useful for development).
# Priority: SES (if configured) → SMTP (if configured) → Console logging
#
# Option 1: SMTP (recommended for self-hosted, works with any provider)
# - Gmail: smtp.gmail.com (port 587, use app password)
# - Amazon SES: email-smtp.{region}.amazonaws.com
# - Mailgun: smtp.mailgun.org
# - SendGrid: smtp.sendgrid.net
EMAIL_SMTP_HOST=
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=
EMAIL_SMTP_PASS=
# Uncomment for port 465 (implicit TLS):
# EMAIL_SMTP_SECURE=true
# Option 2: Amazon SES API. All three are required; an IAM user with
# ses:SendEmail is enough. The region (e.g. us-east-1) is the one the sending
# identity is verified in — a verified identity is regional, so there is no safe
# default and a wrong one has every send rejected. The configuration set is
# optional and only needed to receive delivery events.
EMAIL_SES_ACCESS_KEY_ID=
EMAIL_SES_SECRET_ACCESS_KEY=
EMAIL_SES_REGION=
# EMAIL_SES_CONFIGURATION_SET=
# Verifying a customer-owned sending domain (optional). A SEPARATE credential
# from the one above, needing ses:CreateEmailIdentity, ses:GetEmailIdentity and
# ses:PutEmailIdentityMailFromAttributes in the same region. Do NOT grant it
# ses:DeleteEmailIdentity: one process able to delete identities can stop every
# workspace on the account from sending. Leave unset and adding a sending domain
# refuses out loud, naming these variables; nothing else is affected.
# EMAIL_SES_IDENTITY_ACCESS_KEY_ID=
# EMAIL_SES_IDENTITY_SECRET_ACCESS_KEY=
# Sender address (REQUIRED when using SMTP or SES)
# Format (no quotes): Your App <noreply@yourdomain.com>
EMAIL_FROM=
# ============================================================================
# Inbound email (optional) — conversation email channel
# ============================================================================
# Lets a visitor reply to an agent's email and have it thread back into their
# conversation. Two ways to receive:
#
# Option A: provider webhook (e.g. Resend inbound). Both must be set; until then
# the inbound webhook 404s and agent-reply emails carry no routable Reply-To.
# - EMAIL_INBOUND_DOMAIN: the receiving domain (e.g. a Resend inbound domain).
# Agent replies use reply+<conversationId>@<domain>. Exactly one domain: it
# is the domain every reply address is BUILT on. A value that is not one
# domain (a comma-separated pair being the easy typo) is refused rather than
# half-used: nothing is minted, the admin surface reports inbound email
# unconfigured, and the boot log names the variable. Mail already on its way
# is not thrown away for it - the webhook still ingests, and it costs the
# Reply-To rather than the message.
# - EMAIL_INBOUND_SIGNING_SECRET: the provider's webhook signing secret
# (Svix "whsec_..."). Point the provider's inbound webhook at
# <BASE_URL>/api/chat/email/inbound.
EMAIL_INBOUND_DOMAIN=
EMAIL_INBOUND_SIGNING_SECRET=
# Further domains to keep RECEIVING on, comma- or space-separated. Optional, and
# unset is the normal case: one domain, which is the one above.
#
# Set it when changing inbound domain. A reply address is minted once and read
# for as long as the mail carrying it survives, so every address issued before
# the change still routes replies to the old domain — and a domain the front
# door stops answering for leaves each of those arriving at a door that refuses
# them. Listing the old domain here keeps it answering while the new one mints,
# so the change costs nobody a reply:
#
# before EMAIL_INBOUND_DOMAIN=old.example EMAIL_INBOUND_EXTRA_DOMAINS=new.example
# after EMAIL_INBOUND_DOMAIN=new.example EMAIL_INBOUND_EXTRA_DOMAINS=old.example
#
# Both domains are accepted at every instant, and the retired one stays listed
# indefinitely. Membership is exact, so subdomains are not implied, and an entry
# that is not a domain is ignored and named in the boot log.
#
# What listing a domain does, exactly:
# - mail addressed to it is ACCEPTED. Nothing is ever minted on it; that is
# EMAIL_INBOUND_DOMAIN's job alone, and the right to send FROM an address on
# the shared domain follows the minting domain rather than this list.
# - inbound mail whose Message-ID sits on it is dropped as our own mail
# looping back, and is not retained.
# - no workspace may add it, its subdomains, or any zone ABOVE it as their own
# verified sending domain, for as long as it is listed.
# So list only a zone this install operates. A domain that has been released, or
# was never yours, silently costs other people mail.
#
# It is read from the environment on every request, so it survives a restart but
# not a config management tool that deletes what it does not declare. Declare it
# there even when it is empty: losing it narrows the accept-set back to the
# minting domain, and every reply address on the retired one starts being
# refused.
# EMAIL_INBOUND_EXTRA_DOMAINS=
# Option B: IMAP polling (self-hosted, no webhook). A background worker polls
# the mailbox ~every 60s for unseen mail, ingests it, and marks it seen. Leave
# EMAIL_INBOUND_PROVIDER unset to disable (the worker never connects). Replies
# route by plus-address when EMAIL_INBOUND_DOMAIN/SIGNING_SECRET are also set,
# and otherwise by the In-Reply-To/References threading chain.
# - EMAIL_INBOUND_PROVIDER: set to "imap" to enable the poller.
# - IMAP_HOST/IMAP_PORT/IMAP_USER/IMAP_PASSWORD: the mailbox connection.
# - IMAP_TLS: "false" for a plaintext connection (defaults to TLS, port 993).
# - IMAP_MAILBOX: mailbox to poll (defaults to INBOX).
# EMAIL_INBOUND_PROVIDER=imap
# IMAP_HOST=imap.example.com
# IMAP_PORT=993
# IMAP_USER=support@example.com
# IMAP_PASSWORD=
# IMAP_TLS=true
# IMAP_MAILBOX=INBOX
# ============================================================================
# AI (optional)
# ============================================================================
# Enable AI features like auto-categorization and summarization.
# AI (optional). All AI features are OFF unless configured below.
# Quackback talks to any OpenAI-compatible endpoint. You must set BOTH a key
# and an explicit base URL — there is no implicit provider default.
OPENAI_API_KEY=
# Endpoint examples:
# Direct OpenAI: https://api.openai.com/v1
# OpenRouter: https://openrouter.ai/api/v1
# Cloudflare AI Gateway: https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/openai
OPENAI_BASE_URL=
# Models. A feature is enabled only when its model resolves to a value.
# Two roles cover everything; per-feature overrides are optional.
# - Chat features: summary, sentiment, extraction, quality gate, interpretation, merge
# - Embedding features: duplicate detection, help-center semantic search
# Use model ids your endpoint accepts (direct OpenAI: e.g. gpt-4o-mini,
# text-embedding-3-small; a gateway: e.g. google/gemini-3.1-flash-lite-preview).
AI_CHAT_MODEL=
AI_EMBEDDING_MODEL=
# Optional per-feature overrides of AI_CHAT_MODEL. Set to "off" to disable just
# that feature while keeping the rest (e.g. AI_SUMMARY_MODEL=off).
AI_SUMMARY_MODEL=
AI_SENTIMENT_MODEL=
AI_EXTRACTION_MODEL=
AI_QUALITY_GATE_MODEL=
AI_INTERPRETATION_MODEL=
AI_MERGE_MODEL=
AI_HELP_CENTER_MODEL=
AI_ASSISTANT_MODEL=
# On OpenRouter, structured-output requests are routed only to providers that
# enforce the JSON schema (require_parameters). Set to false only if your model
# has no provider advertising response_format — output then relies on prompt
# adherence and salvage parsing.
AI_REQUIRE_PARAMETERS=
# Hide OpenRouter reasoning tokens on tool-less structured streams (Ask AI).
# Unset/false for models that do not advertise `reasoning` (require_parameters
# would 404). Set true for reasoning models such as DeepSeek v4 Flash.
AI_REASONING_EXCLUDE=
# ============================================================================
# File Storage (for image uploads)
# ============================================================================
# S3-compatible storage for image uploads in changelogs.
# Supports AWS S3, Cloudflare R2, Backblaze B2, MinIO, Railway Buckets, and other S3-compatible services.
# If not configured, image upload will be disabled in the rich text editor.
#
# S3_PUBLIC_URL is optional. If not set, files are served via BASE_URL/api/storage
# using presigned URL redirects (works with any provider, including private buckets).
# Set it explicitly to use a CDN or public bucket URL instead.
#
# For local development, MinIO is included in docker-compose and configured by default.
# Run `bun run setup` or `docker compose up -d minio` to start it.
# MinIO (local development - enabled by default)
S3_ENDPOINT=http://localhost:9000
S3_BUCKET=quackback
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_FORCE_PATH_STYLE=true
S3_PUBLIC_URL=
# Proxy all S3 traffic (uploads and downloads) through the server. Enable when
# the browser can't reach S3 directly (e.g. self-hosted Docker, ngrok):
# S3_PROXY=false
# Using an external S3 provider instead of the bundled MinIO?
# - The local `minio` and `minio-init` services in docker-compose.yml are
# then unused — stop/remove them (e.g. `docker compose stop minio minio-init`)
# so you aren't running a container you don't need.
# - Fill in the provider values below.
#
# For production, replace with your preferred S3-compatible provider:
#
# AWS S3 (public bucket):
# S3_ENDPOINT= # Leave empty for AWS S3
# S3_BUCKET=your-bucket-name
# S3_REGION=us-east-1
# S3_ACCESS_KEY_ID=your-access-key
# S3_SECRET_ACCESS_KEY=your-secret-key
# S3_FORCE_PATH_STYLE=false
# S3_PUBLIC_URL=https://your-bucket-name.s3.us-east-1.amazonaws.com
#
# AWS S3 (private bucket via presigned redirect):
# S3_ENDPOINT=
# S3_BUCKET=your-bucket-name
# S3_REGION=us-east-1
# S3_ACCESS_KEY_ID=your-access-key
# S3_SECRET_ACCESS_KEY=your-secret-key
# S3_FORCE_PATH_STYLE=false
# S3_PUBLIC_URL=https://your-app.example.com/api/storage
#
# Cloudflare R2:
# S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
# S3_BUCKET=your-bucket-name
# S3_REGION=auto
# S3_ACCESS_KEY_ID=your-access-key
# S3_SECRET_ACCESS_KEY=your-secret-key
# S3_FORCE_PATH_STYLE=true
# S3_PUBLIC_URL=https://your-custom-domain.com
#
# Railway Buckets (private — served via presigned URL redirects automatically):
# S3_ENDPOINT=https://your-bucket-endpoint.storageapi.dev
# S3_BUCKET=your-bucket-name
# S3_REGION=us-east-1
# S3_ACCESS_KEY_ID=your-access-key
# S3_SECRET_ACCESS_KEY=your-secret-key
# S3_FORCE_PATH_STYLE=true
# # No S3_PUBLIC_URL needed — files served via BASE_URL/api/storage automatically
# ============================================================================
# Telemetry (optional)
# ============================================================================
# Anonymous usage statistics are enabled by default to help improve Quackback.
# Uncomment to disable.
# DISABLE_TELEMETRY=true
# ============================================================================
# Logging (optional)
# ============================================================================
# The server emits structured JSON logs to stdout (one object per line), ready
# for any log shipper (Grafana Alloy, Promtail, Fluent Bit, Vector, ...).
# LOG_LEVEL controls verbosity: trace | debug | info | warn | error | fatal | silent
# Default: info in production, debug otherwise.
# LOG_LEVEL=info
# ============================================================================
# Server Roles (optional)
# ============================================================================
# QUACKBACK_ROLE controls whether this process runs background workers.
# Values: all | web | worker
# - all (default): serve HTTP and run background queue workers and sweepers.
# Use for single-container deployments.
# - web: serve HTTP only. Enqueues jobs but does not consume them.
# - worker: run background queue workers and sweepers. Also serves HTTP
# (health probes work), but do not route user traffic.
# Unset or omitted means all.
# QUACKBACK_ROLE=all