-
-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy path.env.convex.example
More file actions
218 lines (206 loc) · 12.1 KB
/
Copy path.env.convex.example
File metadata and controls
218 lines (206 loc) · 12.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
# Convex DEPLOYMENT env (the application secrets), applied automatically by the
# in-stack `deployer` service via `convex env set`. Copy to .env.convex
# (gitignored) and keep it safe.
#
# THIS FILE ≠ .env.beta. Everything here is a Convex *deployment* env var read by
# the backend functions (process.env.*) and pushed by the deployer. Compose-level
# config (ports, INSTANCE_SECRET, POSTGRES_PASSWORD, the VITE_* build pins, backup
# creds) lives in .env.beta instead. If a var is read by convex/ code, it belongs
# HERE; putting it in .env.beta will NOT reach the deployment.
#
# To apply a change after the first deploy: edit this file, then re-run the
# deployer — `docker compose -f docker-compose.beta.yml --env-file .env.beta up -d
# --no-deps --force-recreate deployer` — and watch its log for `env set <KEY>`.
#
# The five pure-random secrets below may be LEFT AS CHANGE_ME: the deployer
# generates each one ONCE (if the deployment doesn't already have it) and never
# regenerates it. Fill them only if you want specific values. EXTERNAL
# credentials (CAP_*, REMNAWAVE_*, processor keys) are NOT auto-generated — a
# CHANGE_ME for those still aborts the deploy.
#
# To set your own instead: generate 32-byte secrets with `openssl rand -hex 32`.
# Do this BEFORE the first deploy. On a RUNNING deployment, replacing a value the
# deployer already generated overwrites the live secret: for ACCOUNT_ID_PEPPER
# that PERMANENTLY LOCKS OUT every existing member (their account number is the
# only credential — there is no email reset), and for the SIGNING_KEYs it signs
# everyone out. A CHANGE_ME here does NOT mean the deployment is unconfigured:
# check what is actually live with `convex env list`, which must be run THROUGH
# THE DEPLOYER CONTAINER (the host shell has no Convex credentials, so a bare
# `bunx convex env …` fails with "No CONVEX_DEPLOYMENT set") — copy the recipe in
# docs/beta-deploy.md § "One-off functions".
# The auto-generated ADMIN_BOOTSTRAP_SECRET (for the first admin passkey) is
# printed in the deployer's log on every deploy — easiest source; otherwise read
# it with `convex env get ADMIN_BOOTSTRAP_SECRET` via that same recipe.
SESSION_SIGNING_KEY=CHANGE_ME_hex32
ADMIN_SESSION_SIGNING_KEY=CHANGE_ME_hex32
# Auto-generated if left as CHANGE_ME; paste it in the browser to register the
# first admin passkey (step 4) — read it from the deployer's log, or via the
# deployer-container recipe in docs/beta-deploy.md § "One-off functions".
ADMIN_BOOTSTRAP_SECRET=CHANGE_ME_hex32
IP_HASH_SALT=CHANGE_ME_hex32
# Edges (docs/edges.md), all optional. EDGE_MARK_PEPPER keys the
# per-member-per-window dedupe mark behind the block detector (falls back to
# IP_HASH_SALT). The probe credentials can also be set in the admin panel
# (write-only settings); env is the fallback.
# EDGE_MARK_PEPPER=CHANGE_ME_hex32
# EDGE_PROBE_GLOBALPING_TOKEN=
# EDGE_PROBE_RIPEATLAS_KEY=
# SET ONCE. Leaving this as CHANGE_ME is CORRECT on a deployed stack (the
# deployer generated a real value on the first deploy and skips this line
# forever after). Never replace it on a running deployment: every member's
# account number stops working, irreversibly.
ACCOUNT_ID_PEPPER=CHANGE_ME_hex32
# --- CDN-blinding (HPKE) SECRET keys ------------------------------------------
# Generated by `bun run bootstrap` (the matching PUBLIC pins go to
# .env.beta as VITE_FS_*). The deployer `convex env set`s these. Left commented =
# the backend runs "dark" (no body-sealing); the bootstrap uncomments + fills them
# so CDN-blinding is ON. Do NOT hand-edit individually — regenerate the whole set
# so the secrets match the .env.beta pins (`bun scripts/gen-hpke-keys.mjs`).
# FS_SERVER_HPKE_SK=CHANGE_ME_run_bootstrap
# FS_MANIFEST_SK=CHANGE_ME_run_bootstrap
# FS_MANIFEST_SK_PQ=CHANGE_ME_run_bootstrap
#
# HPKE enforcement (H1): once the deployed SPA was built with the HPKE public
# pins (VITE_FS_SERVER_HPKE_PK/KID), set this to REJECT unsealed member
# requests on the seal/reveal routes — closing the last plaintext leg of the
# account number across TLS-terminating infrastructure. Without it the backend
# is dual-mode (accepts sealed AND plaintext), which is the rollout default.
# Admin `fsv1_`/Ansible callers are unaffected (only member routes are gated).
# Check the admin dashboard status card (hpke.required) after flipping.
# FS_HPKE_REQUIRED=true
#
# Admin-side counterpart: REJECT unsealed requests on the sealed ADMIN routes
# (backend-server / mirror / edges credential writes and reveals) from
# COOKIE-session (passkey CMS) callers. `fsv1_` bearer callers (IaC/Ansible)
# cannot seal and keep plaintext; the caller class is the Authorization
# header. Flip only once the deployed SPA is built with the HPKE pins.
# FS_HPKE_ADMIN_REQUIRED=true
# --- CDN-blinding Phase 2: proof-of-possession (PoP) enforcement --------------
# PoP binds each session to a non-extractable browser key, so a captured cookie
# alone cannot be replayed. Sessions that carry a key are ALWAYS enforced; the
# flag below only controls whether LEGACY cookie-only sessions are still allowed.
# - Fresh deploy: safe to enable from day one (there are no legacy sessions).
# - Existing deploy: enable only after the admin dashboard's "Session
# protection" card reports zero cookie-only sessions (readyToEnable), or those
# sessions are logged out on their next request. Rollback = remove this var.
# Runbook: docs/threat-model-cdn-blinding.md ("Enabling POP_REQUIRED").
# POP_REQUIRED=true
#
# Optional PoP host allowlist for the cross-vhost check (comma-separated). When
# unset it falls back to WEBAUTHN_ORIGIN's host(s) — correct for a single
# hostname. Set this only if the app is reachable on additional hostnames.
# POP_EXPECTED_HOST=beta.freesocks.org
# Self-hosted Cap captcha (W1). Create a site key + secret in the Cap dashboard
# (the `cap` service; reach it via SSH tunnel like the Convex dashboard).
# - CAP_API_ENDPOINT: backend-internal URL for siteverify (compose service name).
# - CAP_SITE_KEY: the public site key (echoed to the SPA via /api/v1/config).
# - CAP_SECRET: the site key's secret (server-side verify only).
# - CAP_PUBLIC_ENDPOINT: same-origin path the browser widget uses (Caddy /cap).
CAP_API_ENDPOINT=http://cap:3000
CAP_SITE_KEY=CHANGE_ME_cap_site_key
CAP_SECRET=CHANGE_ME_cap_secret
CAP_PUBLIC_ENDPOINT=/cap
# Local dev only (double-gated): with ENVIRONMENT=development, treat every
# captcha token as valid so the flows work without a Cap server. NEVER in prod.
# CAP_DEV_BYPASS=true
# Local dev only (double-gated with ENVIRONMENT=development): issue keys from an
# in-memory mock backend instead of a real Remnawave/Outline. Ignored in prod.
# DEV_MOCK_BACKEND=true
# Local dev only (double-gated with ENVIRONMENT=development): shadow one L4 and
# one L7 edge provider adapter with an in-memory fake so the edges setup flow can
# be walked without cloud credentials (docs/edges.md). Ignored in prod.
# DEV_FAKE_EDGE_PROVIDER=true
# Optional delay (ms, capped at 10000) the fake adds to each create, to exercise
# the async rotation paths.
# DEV_FAKE_EDGE_SLOW_MS=0
# WebAuthn (admin passkeys) bound to the beta domain.
WEBAUTHN_RP_ID=beta.freesocks.org
WEBAUTHN_ORIGIN=https://beta.freesocks.org
# Display name shown in the passkey prompt (optional; defaults to "FreeSocks Admin").
# WEBAUTHN_RP_NAME=FreeSocks Admin
# Member-facing RP display name for the OPTIONAL member passkey login (optional;
# defaults to "FreeSocks"). Same RP id/origin as admin — isolation is by table.
# WEBAUTHN_RP_NAME_MEMBER=FreeSocks
# Production posture: Secure cookies + trust Caddy's X-Forwarded-For.
ENVIRONMENT=production
# Topology (a) — Caddy is the public edge: TRUSTED_PROXY=true (≡ TRUSTED_PROXY_HOPS=1).
TRUSTED_PROXY=true
# Topology (b) — something fronts Caddy (Pangolin / CF Tunnel / ngrok / LB): count the
# trusted appending hops from the RIGHT of X-Forwarded-For instead (2 for one fronting
# proxy; +1 per extra hop). Wins over TRUSTED_PROXY. Also set CADDY_TRUSTED_PROXIES on
# the web service to that peer's IP/CIDR. Verify via GET /api/v1/admin/client-ip.
# TRUSTED_PROXY_HOPS=2
# Leave CF_FRONTED unset behind Caddy: there's no Cloudflare edge, so a
# client-supplied cf-connecting-ip would be spoofable. Set it to true ONLY with
# a real CF edge in front AND the origin locked to CF-only traffic — and then
# ALSO set CADDY_TRUST_CF_HEADER=true on the web service (.env.beta), or the
# stock Caddyfile strips cf-connecting-ip AND the CF-IPCountry/CF-Region-Code/
# CF-IPCity geo headers (mirror country tiering + analytics geo) before they
# reach the backend.
# CF_FRONTED=true
# Optional: seed the first Remnawave instance at cutover (else add it in the
# admin CMS -> Backend servers). The slug becomes "remnawave-primary".
# REMNAWAVE_BASE_URL=https://panel.example.org
# REMNAWAVE_API_TOKEN=CHANGE_ME_remnawave_token
# Outbound-URL SSRF guard: backend-server and mirror-provider URLs pointing at
# loopback / link-local / cloud-metadata addresses are REJECTED at write time
# (RFC1918 private space is allowed — panels legitimately live there). Set to
# true ONLY for local dev with a panel on the same host (never in prod).
# ALLOW_INTERNAL_BACKENDS=true
# Renew/upgrade callout links (surfaced in /api/v1/config). Lapsed/expiring
# members are pointed at DONATE_URL with CONTACT_URL as the secondary CTA.
DONATE_URL=https://unredacted.org/donate
CONTACT_URL=https://unredacted.org/contact
# Optional member-facing links also surfaced in /api/v1/config (e.g. for an
# external landing page or native app). Omit to leave them unset; no effect on
# the SPA's own get-account / account flows.
# MEMBERS_JOIN_URL=https://beta.freesocks.org/get
# MEMBERS_ACCOUNT_URL=https://beta.freesocks.org/account
# Billing webhook HMAC secret. REQUIRED once a billing portal posts to
# /api/webhooks/billing; while unset the endpoint answers a distinct
# `503 webhook.not_configured` (never a misleading 400). Day-1 paid upgrades
# use admin-minted membership codes and do not need this.
# WEBHOOK_SIGNING_SECRET=CHANGE_ME_hex32
# --- Self-service membership billing (docs/billing.md) ---
# The deployment's public origin; the backend builds absolute IPN + success/
# cancel URLs from it. Required for any billing rail.
# PUBLIC_BASE_URL=https://beta.freesocks.org
#
# NOWPayments (crypto rail). Each rail's secret is REQUIRED once its rail is
# enabled in Admin → Billing; while unset that rail's webhook answers a distinct
# `503 billing.not_configured`. NOWPAYMENTS_API_URL defaults to the prod API.
# NOWPAYMENTS_API_KEY=CHANGE_ME_nowpayments_api_key
# NOWPAYMENTS_IPN_SECRET=CHANGE_ME_nowpayments_ipn_secret
# NOWPAYMENTS_API_URL=https://api.nowpayments.io
#
# BTCPay (self-hosted Bitcoin rail). API_URL is your BTCPay Server origin; the
# store webhook (registered in BTCPay) points at /api/webhooks/btcpay with
# WEBHOOK_SECRET. The API key needs BOTH btcpay.store.cancreateinvoice AND
# btcpay.store.canviewinvoices: the read-back powers the settle-state cross-check
# (a store settle-tolerance can't grant a partial payment) and the admin
# credential probe. Minimum server version: BTCPay 2.4.2 (see docs/billing.md).
# BTCPAY_API_URL=https://pay.example.org
# BTCPAY_STORE_ID=CHANGE_ME_btcpay_store_id
# BTCPAY_API_KEY=CHANGE_ME_btcpay_api_key
# BTCPAY_WEBHOOK_SECRET=CHANGE_ME_btcpay_webhook_secret
#
# Stripe (card rail, Phase 2).
# STRIPE_API_KEY=CHANGE_ME_stripe_secret_key
# STRIPE_WEBHOOK_SECRET=CHANGE_ME_stripe_whsec
#
# PayPal (Phase 3). PAYPAL_API_BASE = https://api-m.paypal.com (live) or
# https://api-m.sandbox.paypal.com (sandbox).
# PAYPAL_CLIENT_ID=CHANGE_ME_paypal_client_id
# PAYPAL_SECRET=CHANGE_ME_paypal_secret
# PAYPAL_WEBHOOK_ID=CHANGE_ME_paypal_webhook_id
# PAYPAL_API_BASE=https://api-m.paypal.com
#
# Order sweep windows (optional): BILLING_PENDING_TTL_HOURS (48),
# BILLING_ORDER_RETENTION_DAYS (365).
# Optional extras (uncomment as needed):
# FREE_TIER_DAILY_CAP is gone: the free-account daily cap is now the
# admin-tunable `freetier.create` rate-limit policy (default 3), edited in the
# admin CMS -> Rate limits without a redeploy.
# FREE_TIER_EXPIRY_DAYS is gone: the free-account lifetime is now the
# admin-tunable `freetier.expiryDays` setting (default 90), edited in the admin
# CMS -> Settings. It drives both the issued key's expiry and the cleanup sweep.