1- # FileForge Configuration
2- # Copy this file to .env and adjust values
1+ # FileMorph Configuration
2+ # Copy this file to .env and adjust values for your deployment.
3+ #
4+ # This file is structured top-down: required for every deployment first,
5+ # then Cloud-overlay (account / payment / email features), then Compliance-
6+ # Edition tunables, then operational knobs. A Community-Edition self-host
7+ # only needs the first section.
8+ #
9+ # ──────────────────────────────────────────────────────────────────────
10+ # === Required for every deployment ===
11+ # ──────────────────────────────────────────────────────────────────────
312
4- # Server
13+ # Server bind. APP_HOST=0.0.0.0 makes the app listen on every interface
14+ # inside the container; the reverse proxy (Caddy/nginx) is what limits
15+ # external reach. APP_DEBUG=true enables FastAPI's interactive docs at
16+ # /docs and verbose tracebacks — leave off in production.
517APP_HOST = 0.0.0.0
618APP_PORT = 8000
719APP_DEBUG = false
820
9- # Security
10- # Path to the JSON file storing hashed API keys
21+ # Path to the JSON file holding hashed API keys for anonymous-tier
22+ # clients. The file is created on first key generation; bind-mount the
23+ # parent directory into your container if you want keys to survive
24+ # restarts.
1125API_KEYS_FILE = data/api_keys.json
1226
13- # Limits
27+ # Maximum HTTP request body size accepted by the upload endpoints.
28+ # 100 MB is a sane default that fits typical convert/compress workloads
29+ # without inviting OOM on small hosts. Operators with bigger files raise
30+ # this; tier quotas (anonymous/free/pro/business) still apply on top via
31+ # app/core/quotas.py.
1432MAX_UPLOAD_SIZE_MB = 100
1533
16- # CORS: comma-separated list of allowed origins (* for all)
34+ # CORS: comma-separated list of allowed origins. `*` is fine for dev or
35+ # a single-origin same-domain deployment. Production deployments behind
36+ # a real domain should list explicit origins (e.g.
37+ # `https://files.example.com,https://www.files.example.com`) so a
38+ # malicious page on a different domain can't talk to your API.
1739CORS_ORIGINS = *
1840
19- # Optional: route heavy upload POSTs (convert/compress, single + batch) through
20- # a separate subdomain. Empty string = same-origin (default, simplest). Set
21- # only when the main site sits behind a proxy that caps request bodies and
22- # uploads must bypass it. See docs/self-hosting.md for CORS implications.
41+ # Public canonical URL of the deployment. Used by the canonical/og:url
42+ # meta tags, the sitemap, and the JSON-LD structured data. Localhost is
43+ # fine for dev; set to your domain for production so search engines
44+ # index the right URLs.
45+ APP_BASE_URL = http://localhost:8000
46+
47+ # Optional: route heavy upload POSTs (convert/compress, single + batch)
48+ # through a separate subdomain. Empty string = same-origin (default,
49+ # simplest). Set this only when the main site sits behind a proxy that
50+ # caps request bodies and uploads must bypass it via a tunnel
51+ # subdomain. See docs/self-hosting.md for CORS implications.
2352API_BASE_URL =
2453
25- # Public canonical URL for the deployment — used for canonical/og:url meta
26- # tags, sitemap entries, and JSON-LD structured data. Default localhost is
27- # fine for dev; set to your domain for prod (e.g. https://files.example.com).
28- APP_BASE_URL = http://localhost:8000
54+ # ──────────────────────────────────────────────────────────────────────
55+ # === Cloud-overlay (optional — accounts, payments, transactional email) ===
56+ # ──────────────────────────────────────────────────────────────────────
57+ # A Community Edition deployment can leave everything below empty.
58+ # Setting any of these activates the corresponding sub-processor (see
59+ # docs/sub-processors.md). The application disables each feature
60+ # automatically when its primary key/url is empty — no further toggle
61+ # needed.
62+
63+ # JWT signing secret. Required for the user-account features
64+ # (registration, login, refresh, role checks). Must be at least 32
65+ # characters; rotate by changing this value (all sessions invalidate
66+ # on the next request).
67+ JWT_SECRET = dev-secret-change-me-min-32-chars-long
2968
30- # Whether to expose /pricing as a commercial offer surface. Self-hosted
31- # Community deployments leave this off — there's no commercial offer to
32- # advertise. Set to `true` only on a SaaS deployment that has (or will
33- # soon have) Stripe configured. The page renders a "Coming Soon" banner
34- # automatically when STRIPE_SECRET_KEY is empty.
69+ # PostgreSQL DSN for the Cloud overlay (users, api_keys, file_jobs,
70+ # audit_events tables). Use the asyncpg driver. Empty string disables
71+ # the database completely — registration/login routes return 503.
72+ # DATABASE_URL=postgresql+asyncpg://user:pass@host:5432/filemorph
73+
74+ # Stripe (leave empty to disable billing). When present, the /pricing
75+ # page shows live upgrade buttons gated behind the BGB §356 (5)
76+ # withdrawal-waiver checkbox; without these values the page renders a
77+ # "Coming Soon" banner.
78+ STRIPE_SECRET_KEY =
79+ STRIPE_WEBHOOK_SECRET =
80+ STRIPE_PRO_PRICE_ID =
81+ STRIPE_BUSINESS_PRICE_ID =
82+
83+ # Whether to expose /pricing as a commercial offer surface at all.
84+ # Self-hosted Community deployments leave this off — there is no
85+ # commercial offer to advertise. Set to `true` only on a SaaS
86+ # deployment that has (or will soon have) Stripe configured.
3587PRICING_PAGE_ENABLED = false
3688
37- # S10-lite analytics — per-day counters for page views, conversions,
38- # registrations, and failures. Visible at /cockpit (admin-only). Counters
39- # are aggregates, not personal data: no cookie banner needed. Default on;
40- # set to `false` if you don't want the daily_metrics table populated at
41- # all (the cockpit Analytics tab then shows an empty-state notice).
42- METRICS_ENABLED = true
89+ # Transactional email (password-reset, billing receipts, account
90+ # verification). Empty SMTP_HOST disables outgoing mail; the routes
91+ # that need it (forgot-password, register) return a graceful 503.
92+ # SMTP_HOST=smtp.zoho.eu
93+ # SMTP_PORT=587
94+ # SMTP_USER=no-reply@example.com
95+ # SMTP_PASSWORD=
96+ # SMTP_FROM=no-reply@example.com
97+
98+ # ──────────────────────────────────────────────────────────────────────
99+ # === Compliance-Edition tunables (audit log, retention, output integrity) ===
100+ # ──────────────────────────────────────────────────────────────────────
101+ # These knobs target operators in regulated environments (DACH
102+ # Behörden, healthcare, legal). The defaults below are Cloud-edition
103+ # safe (fire-and-forget audit, no retention beyond the request);
104+ # Compliance customers tighten them per their privacy/audit policy.
43105
44- # NEU-B.1: Compliance-Edition tamper-evident audit log. Default off
45- # (Cloud-edition fire-and-forget — failures are logged at WARNING and
46- # never break the request). Set to `true` for ISO 27001 A.12.4.1 /
47- # BORA §50 / BeurkG §39a compliance — the convert/compress route
48- # refuses to serve a result it could not log to audit_events. Requires
49- # DATABASE_URL.
106+ # NEU-B.1 — Compliance-Edition tamper-evident audit log gate . Default
107+ # off (Cloud-edition: failures are logged at WARNING and never break
108+ # the request). Set to `true` for ISO 27001 A.12.4.1 / BORA §50 /
109+ # BeurkG §39a compliance — the convert/compress route then refuses
110+ # to serve a result it could not log to audit_events. Requires
111+ # DATABASE_URL above .
50112AUDIT_FAIL_CLOSED = false
51113
52- # NEU-B.2 retention policy. The Cloud edition is zero-retention by
53- # design (every conversion flushes its temp dir on completion or
54- # failure; there is no S3/R2 storage layer active). RETENTION_HOURS
55- # is an informational knob for self-hosters running a future
56- # storage-key-backed pipeline (FileJob.expires_at). Compliance-edition
57- # operators with an eDiscovery / GoBD retention requirement set this
58- # to the value their privacy policy declares; Cloud / Community keep
59- # it at 0.
114+ # NEU-B.2 — Retention policy in hours. Cloud edition is zero-retention
115+ # by design (every conversion flushes its temp dir on completion or
116+ # failure; no S3/R2 storage layer is active). This knob is informational
117+ # for self-hosters running a future storage-key-backed pipeline
118+ # (FileJob.expires_at). Compliance-edition operators with an
119+ # eDiscovery / GoBD retention requirement set this to the value their
120+ # privacy policy declares; Cloud / Community keep it at 0.
60121RETENTION_HOURS = 0
61122
123+ # ──────────────────────────────────────────────────────────────────────
124+ # === Operational knobs (sweep cadence, concurrency, metrics) ===
125+ # ──────────────────────────────────────────────────────────────────────
126+
127+ # S10-lite analytics — per-day counters for page views, conversions,
128+ # registrations, and failures. Visible at /cockpit (admin-only).
129+ # Counters are aggregates, not personal data — no cookie banner needed.
130+ # Default on; set to `false` to leave the daily_metrics table empty
131+ # (the cockpit Analytics tab then shows an empty-state notice).
132+ METRICS_ENABLED = true
133+
62134# Background sweep that removes orphaned `fm_*` temp dirs left behind
63135# by crashes mid-conversion. The request path always cleans its own
64136# temp dir in a `finally` block, so this only catches crash-recovery
65137# cases. Set to 0 to disable the periodic sweep (the startup sweep
66- # still runs).
138+ # still runs once on boot regardless ).
67139TEMP_SWEEP_INTERVAL_MINUTES = 60
68140
69141# How old (minutes) an `fm_*` temp dir must be before the sweep
@@ -72,10 +144,10 @@ TEMP_SWEEP_INTERVAL_MINUTES=60
72144# Operators with very long batch pipelines raise this to match.
73145TEMP_SWEEP_MAX_AGE_MINUTES = 10
74146
75- # NEU-D.1 capacity guard. Total parallel conversions across all
76- # callers. Default 4 is sized for a 4 GB host with the existing
77- # per-tier output caps; raise to ~ CPU-count on a bigger box.
78- # Past the cap → 503 + Retry-After.
147+ # NEU-D.1 — global concurrency cap across all callers. Default 4 is
148+ # sized for a 4 GB host with the existing per-tier output caps; raise
149+ # to roughly CPU-count on a bigger box. Past the cap, requests get
150+ # 503 + Retry-After.
79151MAX_GLOBAL_CONCURRENCY = 4
80152
81153# How long a request waits for a free slot before giving up. Small
0 commit comments