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

Administrators can change ASN mode, prefix, and rasterization DPI under Settings > Archive configuration > Advanced > Scan processing. Saves affect future processing jobs immediately. An explicitly configured SCAN_ASN_MODE, SCAN_ASN_PREFIX, or SCAN_SPLIT_DPI remains authoritative and appears read-only instead of silently accepting an ineffective edit. Removing an override reveals the previously stored value. Legacy disposable-separator enablement and token remain boot configuration.

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. A SHARE_PUBLIC_URL can therefore be HTTPS while its proxy target is loopback HTTP. For proxy-less installs, set both: See Public sharing for isolated Tailscale Funnel and Cloudflare Tunnel examples.

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 stores the verified issuer and subject as the stable OIDC account key. Verified email remains a current provider-managed attribute, not the returning identity lookup. A browser callback with an unknown issuer/subject can create a new account only when its verified email is unused. It never adopts an existing email-matched row, and direct Bearer authentication never provisions or mutates an account. ADMIN_EMAIL does not locate, bind, or merge returning users. To bind a pre-existing unbound account after enabling OIDC, preserve one live browser session and choose Sync from identity provider. The forced provider login must return the same verified email before Suchi records the issuer/subject pair. Without that live session, ordinary login sees the occupied email as a conflict rather than risking an account takeover. Changing issuer, subject assignment, or verified-email ownership likewise fails closed; Suchi has no provider-migration or account-merge fallback. Only configure an issuer you trust to control email assignment, and never map arbitrary user-editable attributes to verified email. See the OIDC standard claims and identity limits. OIDC binding and account-email changes create retained security audit rows. Ordinary AUDIT_RETENTION_DAYS pruning does not remove them; an operator must deliberately delete them. 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

Archive Configuration exposes the directory, owner, target and effective recursion state. Saving a new directory, owner, or system validates all three and stores the resolved owner’s numeric user ID in one transaction. The directory must already exist. The supervisor then stops the old reconciler, scans the new source and starts polling it every five seconds. If runtime reload fails, Suchi restores the exact prior settings only when no newer save has replaced them. Reconciliation is deliberately polling-based: it keeps filesystem descriptors bounded instead of registering one kernel watch per subdirectory, recovers from network filesystems that do not forward local events, and cannot overflow an event queue. It processes one file at a time, never follows symbolic links, ignores hidden paths and the legacy top-level errors/ directory, skips special files and mounted filesystems beneath the source, and stops descent after 64 levels. A database checkpoint avoids rehashing unchanged files. Changed files and sidecars are reconsidered. An oversized file remains checkpointed while it exceeds the current BODY_LIMIT; restarting Suchi with the limit raised above its size or disabled retries the unchanged source. Successful checkpoints remain final across later limit changes. Transient failures retry after five minutes; deterministic sidecar/schema failures are blocked and retried hourly. Changing the sidecar retries that file at once, and creating, updating or deleting a custom-field definition under Metadata retries blocked files in that filing system. Blocked files are logged as fswatch.ingest_blocked and recorded as document.ingest.blocked archive events with the reason. Sidecars (<name>.json) remain read-only and apply metadata atomically. Failures stay at their source path and appear in logs and archive events; Suchi never moves them into the old errors/ directory or silently drops invalid fields. Intake rechecks owner entry; sidecars cannot redirect the configured system. Optional sidecar jd_system must agree. jd_address is informational, category-level, and potentially repeated; it never allocates, identifies, or routes a document. The API and UI continue to accept and display the owner’s current email, but the database setting is ingest.fs_watch_owner_id. Because the running watcher owns files by user ID, a successful local or OIDC email change neither rewrites this setting nor reloads the watcher. INGEST_FS_OWNER_EMAIL remains an address-shaped boot configuration boundary. Suchi resolves it to an active user ID before starting the watcher. An email change keeps the running watcher attached to that ID; update an old external email value before the next restart, when it will be resolved again. Import --owner-email is separate invocation-time input and must use the account’s current address on every future run.

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)

The model is off by default. These settings turn on model-backed classification, Ask your documents, and suggested dates and details. Non-local endpoints require the egress acknowledgement. The separate application mode defaults to Apply when confident without enabling a model. Ask your documents sends only the question, bounded recent conversation, and selected document title, sensitivity, text, plus permitted reviewed details. It stores no live conversation. Administrators can choose the bounded reading depth independently of provider configuration; see Ask your documents: Reading-depth presets. Archive Configuration 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. The classification options separate model-free archive matching from model/date thresholds. Save matching options preserves the model’s enabled state. After a successful connection test, Enable model activates the configuration. Apply when confident is the default application mode and covers new dates, model metadata, and local matches. Choose Review first to send inferred changes to Approvals. Save application mode saves only this choice, independently of model activation, credentials, egress, and explicit rules written by a user. Neither startup nor settings saves bulk-accept pending suggestions; new ingests and explicit rescans use the current policy. 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 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. Migration 0003 converts a retained old date opt-out into the shared application mode and preserves a custom local automatic threshold.

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, including the fixed provider destination for Account subscription (https://chatgpt.com for OpenAI ChatGPT). 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.