Skip to content

Configuration

Writ edited this page Jul 28, 2026 · 2 revisions

Configuration

Everything is read from environment variables. docker compose loads them from .env in the repository root, which ./scripts/gen-env.sh creates for you.

Never commit a filled-in .env — the shipped .gitignore already excludes it.

Required secrets

With the default ENVIRONMENT=production the coordinator refuses to boot if any of these is missing, blank, or shorter than 32 characters. That is deliberate: HS256 signs happily with an empty key, so a half-filled template would otherwise leave your token-signing secret publicly known — anyone could forge an owner session.

./scripts/gen-env.sh generates all of them.

Variable Generate with
WRIT_JWT_SECRET openssl rand -hex 32
API_SECRET_KEY openssl rand -hex 32
HMAC_SECRET_KEY openssl rand -hex 32
RECORDER_AUTH_SECRET openssl rand -hex 32
INTERNAL_API_SECRET openssl rand -hex 32
GATEWAY_SECRET openssl rand -hex 32
DOC_EXTRACT_SECRET openssl rand -hex 32
SECRET_ENCRYPTION_KEY python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

WRIT_JWT_SECRET is the operator-facing name; compose maps it onto the JWT_SECRET_KEY the app reads. Set one, not both.

Back up SECRET_ENCRYPTION_KEY separately from your data volume

It encrypts stored credentials, TOTP seeds and cookies at rest. A database backup without it cannot be decrypted. Replacing it is as destructive as losing it — every persona and secret already stored becomes unreadable, with no error at boot. See Backup and restore.

Deployment identity

Variable Default What it does
WRIT_PUBLIC_URL Public base URL, e.g. https://writ.example.com. Agents dial back using this, so it must be reachable from wherever they run. Getting it wrong is the most common cause of an agent that never connects.
ALLOWED_HOSTS Comma-separated hostnames this coordinator answers on, no scheme or port. Spoofed Host headers are rejected in production.
CORS_ORIGINS http://localhost:8000 Comma-separated allowed origins. * is refused in production.
WRIT_SOURCE_URL https://github.com/usewrit/writ Where the corresponding source of this build can be obtained. Change it if you deploy a modified build — AGPL §13.
ENVIRONMENT production production enforces strong secrets, a Host allowlist and non-wildcard CORS.
FORWARDED_ALLOW_IPS 127.0.0.1 Which proxy IPs may set X-Forwarded-For. Never set this to * — it lets any client spoof its IP and bypass every per-IP control.

Document and OCR extraction

Variable Default What it does
DOC_EXTRACT_URL http://127.0.0.1:8092 Where the extractor is as your agents see it. Agents call it directly; the coordinator never does. Blank turns the lane off.
DOC_EXTRACT_SECRET Shared secret. Required in production.
DOC_EXTRACT_OCR_MODE auto auto reads the text layer and OCRs only true scans. off never OCRs. force OCRs everything.

The coordinator hands these to each agent at connect time, so co-located agents need no configuration. Agents on other machines cannot reach a loopback address — see Documents and OCR.

First-boot admin

Variable What it does
WRIT_ADMIN_EMAIL / WRIT_ADMIN_PASSWORD Provision the owner automatically on first boot. Leave unset to register through the UI instead.
WRIT_ADMIN_NAME Display name for that account.

Storage and retention

Variable Default What it does
WRIT_DB_PATH /data/writ.db (in Docker) The SQLite file. Pinned to the volume by compose.
WRIT_FILES_DIR /data/files (in Docker) Where screenshots and uploads are written. Leave it set — without it they are base64-encoded into the database and bloat it.
FILE_MAX_BYTES 104857600 (100 MB) Per-file hard cap.
RUN_RETENTION_DAYS 90 Days before run records are purged.
LOG_RETENTION_DAYS 90 Days before audit/event logs are purged.
DETECTED_CHANGE_RETENTION_DAYS 90 Days before detected-change records are purged.

Safety and limits

Variable Default What it does
ALLOW_PRIVATE_TARGETS false Whether monitored/crawled/webhook targets may resolve to private or loopback addresses. Leave false unless you deliberately monitor an internal host — it is the SSRF guard.
TARGET_DOMAIN_RATE_LIMIT 30 Max runs dispatched against one target host per window.
TARGET_DOMAIN_RATE_WINDOW_SECS 60 That window, in seconds.
RATE_LIMIT_REQUESTS / RATE_LIMIT_WINDOW 100 / 60 Generic API rate limit.
MAX_CONCURRENT_RUNS 5 Scheduled runs dispatched to the fleet at once.
QUORUM 2 Agents that must agree before a change is confirmed.

Access control

Variable Default What it does
REQUIRE_ADMIN_MFA false Require TOTP or a passkey for admin endpoints. The coordinator warns at boot while this is off. Enrol a second factor first, then enable it — otherwise you lock yourself out.
REGISTRATION_ENABLED true The coordinator is single-owner; registration self-closes once the owner exists.
LOGIN_ENABLED true Set false for an emergency lockdown.
MAINTENANCE_MODE false Non-admin requests get 503.

Developer-facing

Variable Default What it does
LOG_LEVEL info criticaltrace.
WRIT_EXPOSE_OPENAPI false in production Serve /openapi.json, /docs and /redoc. Off in production because the schema enumerates every route to anyone who reaches the host, and nothing in the product reads it at runtime.
ALLOW_INSECURE_DEV false With ENVIRONMENT=development, permits the insecure default secrets. Local throwaway only.
WRIT_AGENT_REPO usewrit/writ-agent Where generated install commands fetch the agent. Point at your fork.
WRIT_AGENT_DOCKER_IMAGE ghcr.io/usewrit/writ-agent:latest Image used in the generated Docker command.

Notifications

Optional; configure whichever you use. SMTP (SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM_EMAIL, SMTP_USE_TLS), Pushover (PUSHOVER_APP_TOKEN, PUSHOVER_USER_KEY) and Twilio (TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_PHONE, TWILIO_TO_PHONE) are driven against your own accounts — no third party sits in between.

Verifying what is live

GET /api/fleet/connect-info (admin) reports the agent WS URL and whether the document-extraction lane is enabled. GET /health reports database and cache health without authentication.

Clone this wiki locally