Skip to main content

Precedence

Suchi first resolves boot configuration, last wins:
Environment values therefore beat file values. This is useful for container overrides where the file lives in an image layer and the environment comes from the run command. A small, explicit set of administrator-managed values can also be changed live through the web UI. For those values only, the effective chain is:
Configuration is resolved per field. A stored value fills an otherwise unconfigured field, while a config-file or process-environment value pins that field and wins. Stored changes to unpinned fields apply without a restart. File and environment changes are read at process start. See Live database settings for the complete stored list. Command flags, where available, override environment values for that command. Mailbox rows and the Email intake or member Mailboxes screen are authoritative for account-specific IMAP configuration. Filing systems are introduced by taxonomy import, never a boot flag or writable enable setting. Tree, Inbox, mode and last-import provenance belong to each system. User/group directories, roles/capabilities, OCR/LLM settings, OAuth client registration, backup and server configuration remain instance-wide.

Config file (optional)

Recommended format: TOML. Explicit typing and no indentation surprises. HUML is the documented alternative. Format is picked from file extension:

Search paths

The first file found wins:
  1. $SUCHI_CONFIG — explicit path override (highest priority)
  2. $XDG_CONFIG_HOME/suchi/config.<ext>
  3. $HOME/.config/suchi/config.<ext>
  4. /etc/suchi/config.<ext>
  5. ./suchi.<ext> (cwd)
Within each directory, Suchi’s documented formats are probed in the order toml → huml. Setting SUCHI_CONFIG is an explicit instruction: a missing or unreadable path, or a path to a directory, stops startup with an error. When SUCHI_CONFIG is unset, finding no file in the normal search paths is valid and built-in defaults plus environment values are used.

File shape

Keys mirror env var names in lower_snake_case. PUBLIC_URL becomes public_url; BODY_LIMIT becomes body_limit. Nested tables flatten with underscores:
is equivalent to:
admin_email stays top-level because its environment name is ADMIN_EMAIL; putting it inside [oidc] would produce the unused OIDC_ADMIN_EMAIL key. Arrays become CSV strings: ocr_languages = ["eng", "deu"] matches OCR_LANGUAGES=eng,deu. Full samples: SUCHI_CONFIG opts the operator into a specific file and bypasses the search order. Environment values still override that file. Direct secrets and their _FILE forms are treated as one setting, so a direct secret in the environment overrides a file-side secret-file path.

Environment variables — boot configuration

Required

Storage & runtime

Ingest pipeline timeouts

Every ingest stage runs inside core/sandbox with a hard per-invocation deadline. Defaults are tuned for small home-servers processing typical 1–20 MB PDFs; larger files (multi-hundred-page reports, big scan sets) routinely need higher caps. Each knob accepts any Go duration string — 60s, 2m, 1h30m. An invalid value logs a warning and falls back to the default so a typo doesn’t kill ingest. Set these when the job dispatcher parks a doc in state='dead' with a ... timeout after 30s last_error. Retries are capped at 5 by MaxAttempts, so a permanently-slow stage burns ~5 × timeout of wall clock before the doc dead-letters — raising the cap once beats the retry churn.

Ingest pipeline max-sizes

The user-facing content-extraction caps (PDF_MAX_CONTENT_BYTES, ANYDOC_MAX_CONTENT_BYTES, DJVU_MAX_CONTENT_BYTES above) bound what gets written to documents.content. The knobs here bound sandbox-level outputs — the intermediate PDFs and shell payloads. Same failure mode as the timeouts (typo warns + falls back). Byte suffixes K/M/G are base-2; bare numbers are raw bytes.

TLS

If a reverse proxy terminates TLS (recommended), leave these unset. For proxy-less installs, set both:

Local auth

No config needed. On first boot with no admin user, suchi prints a one-time setup token to the log at WARN. POST it to /setup with an email + password to create the admin.

Dev mode

Suchi runs in exactly one of three modes. Dev, demo, and production are mutually exclusive by design — no half-blends, no cross-cutting overrides, one login codepath. Dev-mode’s only effect on the running system is guaranteeing the fixed admin row exists with a known password. Login uses the same endpoints production uses — no dev-only mint helper, nothing new to learn. The auto-provisioned admin is fixed: email [email protected], password devdevdev. That’s not a placeholder — the plan is that you copy those two strings into /login (browser) or a POST /api/token/ curl. Rotating the credentials is not a dev-mode concern; if you need a different admin, use production mode. Boot log line to grep for:
Guardrails (all refusals log at main.dev.refused and exit non-zero):
  • Narrows the wildcard listener to loopback — the normal :8000 default becomes 127.0.0.1:8000 in dev mode. Explicit wildcard, public-IP, and hostname listeners are refused.
  • Requires a deliberate LAN opt-in — physical-device testing uses, for example, PUBLIC_URL=http://192.168.1.50:8000, LISTEN_ADDR=192.168.1.50:8000, and SUCHI_DEV_ALLOW_LAN=1. The two IP literals must match; the server never binds every interface in dev mode.
  • Refuses non-local PUBLIC_URL — allows localhost, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, ::1, bare hostnames without dots, and any *.local name. Anything else exits non-zero.
  • Refuses when OIDC_ISSUER_URL is set — single auth path only. Drop OIDC env vars for the dev iteration or use a separate DATA_DIR.
  • Refuses when SUCHI_DEMO_MODE=1 is set — dev and demo are mutually exclusive; both signalled together is a misconfiguration, not a valid combination.
  • Refuses if any admin other than [email protected] already exists — closes the accidental-prod lateral-takeover path. An operator with a legit [email protected] admin who happens to boot with SUCHI_DEV=1 plus a loopback URL would otherwise gain a second admin row carrying the public default credentials; instead the boot exits with localauth: refusing to arm dev-mode — 1 other admin(s) already exist.
  • Refuses to promote existing non-admin accounts — if a member row with email [email protected] exists, boot fails rather than silently making them admin.
  • Does not resurrect a disabled admin — a manually quarantined admin stays quarantined across dev restarts.
  • Refuses normal startup while the public dev admin is enabled — a data directory armed by SUCHI_DEV=1 cannot later be exposed by starting without dev mode. Disable [email protected] from another active administrator’s session first, or use a different data directory; disabling the account also invalidates its existing tokens.
  • Emits an audit event (dev_admin.provision) on every arm so post-hoc “was this row dev-created?” queries are easy.
Multi-account testing: dev-mode only touches the [email protected] row. Every other users-table row ([email protected], [email protected]) survives restarts untouched. Create additional users in Settings, or call POST /api/admin/users/ with the dev admin’s authenticated session cookie; admin routes do not accept API tokens. These users persist across dev-mode restarts as normal. Second admin rows will block the next SUCHI_DEV=1 boot — the right behaviour; demote the extra admin to a member before restarting dev mode. A normal boot also refuses this data directory until the public [email protected] account is disabled.
Dev-mode is a DX shortcut, not a production feature. The refusals above are the primary defence; the credentials are public and any network-reachable attacker who lands on a misconfigured prod box would gain admin. If you see main.dev.ready in a prod log, something is misconfigured.

OIDC

All-or-nothing group. Set the whole set or none: The provider must support discovery and return a signed ID token containing email and the boolean email_verified: true. Suchi requests openid email profile; configure the provider’s ID-token claim mapping accordingly. Claims available only from UserInfo are not sufficient. Missing, false, or non-boolean verification claims are rejected in both browser and bearer sign-in. A provider that cannot truthfully supply verified-email claims is not supported. Suchi binds this verified email to its local account, including existing accounts and ADMIN_EMAIL. Only configure an issuer you trust to control email assignment; never map arbitrary user-editable attributes to verified email. Email can change or be reassigned and is not a stable OIDC subject identifier, so review account ownership before changing issuers or reassigning an address. See the OIDC standard claims and identity limits. When OIDC is configured, it owns interactive sign-in: anonymous browser entry points redirect to the provider, and local bootstrap/password-login endpoints are disabled. Existing Suchi sessions and scoped API tokens remain valid; API tokens are still the supported path for unattended clients. The health and readiness endpoints remain public for container orchestration. Both Authorization: Token <suchi-token> and Authorization: Bearer <suchi-token> remain supported: Suchi’s 64-lowercase-hex token shape goes to local token authentication, while other Bearer values must validate as OIDC ID tokens. Invalid credentials never fall back to a browser session.

Filesystem-watch ingest

The setup wizard exposes the same values as database-backed overrides. Saving a new directory, owner or system validates the destination, stops the old watcher, drains the new directory, and starts watching immediately. Intake rechecks owner entry; sidecars cannot redirect the configured system. Optional sidecar jd_system must agree, and jd_address never allocates or routes a document.

Email ingest (IMAP polling)

Mailboxes live in the email_accounts table: one row per mailbox, immutable system, per-account owner, provider preset, auth mode (password or xoauth2), sealed secret at rest, per-account folder / poll interval, and typed intake policy. The supervisor picks up UI and API changes without a restart. Administrators configure accounts in Settings > Archive configuration > Email intake. Members with Manage mailboxes configure their own accounts in Settings > My account > Mailboxes. The equivalent API is /api/email-accounts; there is no boot-time single-mailbox seed. Mailbox lists and editing are restricted to the selected system as well as the existing owner/admin rules. Message and attachment documents inherit the mailbox system, including files-only intake. Microsoft OAuth flows capture actor/system; completion and the sealed_secret_b64 creation handoff cannot cross that boundary. How polling works. For every enabled row, every poll interval (new accounts default to 10 min), suchi opens the folder, searches for UIDs above its durable high-water mark, fetches each message’s raw body, stores it in the CAS as message/rfc822, and enqueues a post-ingest job. core/pipeline/eml then fans out one child document per attachment. A configured processed folder moves completed mail; otherwise server read state stays untouched unless mark_seen is explicitly enabled. A failed UID holds the cursor before that message so a later success cannot silently skip it. The intake policy is evaluated locally after the normal UID/date search. Accepted messages may retain the email and its files or files only. Rejected messages advance the local cursor but are not stored, moved, or marked read. Dedup is by Message-ID — a doc with the same Message-ID under the same owner and system is skipped on re-polls. Safe across restarts and transports within that system; equal mail filed into another system is a separate document, though its immutable CAS bytes can be shared. Bridge tip. The built-in Proton relay preset uses protonmail-bridge:143 without TLS because that hop stays on the local container network; Bridge owns the encrypted upstream connection. Enter the Bridge-generated password in the same modal. Use the custom preset and a CA file only when your own relay exposes a TLS listener.

Model assistance (optional; egress on)

Off by default. Setting these turns on model-backed classification, authorized archive research, and extracted facts for review; non-local endpoints require the egress ack. Archive research sends only the question, bounded transient history, and authorized source title, sensitivity, snippet, plus capability-gated extracted fact value/evidence. It stores no live conversation. Administrators can choose the bounded source-text depth independently of provider configuration; see Archive research: Research context presets. The setup wizard AEAD-seals API keys before writing them to settings and never reads plaintext keys back. It exposes masked status, a synthetic connection test with validated fields and latency, and an explicit key-clear action. Automatic handling appears after the connection controls in two boxes: Works without a model for local archive matching, and Uses the configured model for the model confidence slider and automatic Calendar dates. Save matching options preserves the model’s current enabled state. After a successful connection test, Save model and options saves and enables the model settings. Compose installs can reach host Ollama at http://host.suchi.local:11434/v1. As described under Precedence, config-file and environment values pin their individual classifier fields. Unpinned activation, configuration changes, key rotation or clearing, disable, and re-enable apply to the running process immediately. The MCP subcommand acts as a client to a running suchi instance — these tell it how to reach the API. Flags override env.

Config file loader itself

Live database settings

The operator-facing settings below live in the settings table and are set via the setup wizard or Archive configuration. This is the complete list of stored values that participate in runtime configuration. A stored value fills a field only when that field is absent from the config file and process environment. The running LLM, filesystem watcher, OCR configuration, and backup schedule reload after a successful save; pinned fields retain their boot value. Filing mode, Inbox and last-import author ID/version/hash/snapshot are stored on jd_systems, not global settings.preset or taxonomy setting aliases.

Secrets convention

Every secret env var has a _FILE variant. The file’s contents (trimmed of trailing newlines) becomes the value. Matches docker + k8s secret mounts. Secrets are never logged. Confirmed _FILE variants today:
  • OIDC_CLIENT_SECRET_FILE
  • LLM_API_KEY_FILE
If both variants are set in the same source, _FILE wins. Either environment variant overrides either config-file variant.

Egress surface

A stock install makes zero outbound connections (principle 8). At boot, suchi logs main.egress.surface listing every effective outbound path — OIDC discovery, database-backed IMAP polling, and cloud LLM endpoints. Destinations are redacted to safe origins. outbound: none is the safe default; suchi doctor reads the same inventory from the current database.
See privacy for the full posture.