Precedence
Suchi first resolves boot configuration, last wins: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:$SUCHI_CONFIG— explicit path override (highest priority)$XDG_CONFIG_HOME/suchi/config.<ext>$HOME/.config/suchi/config.<ext>/etc/suchi/config.<ext>./suchi.<ext>(cwd)
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 inlower_snake_case. PUBLIC_URL becomes
public_url; BODY_LIMIT becomes body_limit. Nested tables
flatten with underscores:
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:
config.example.toml(recommended)config.example.huml
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 insidecore/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:
main.dev.refused and exit non-zero):
- Narrows the wildcard listener to loopback — the normal
:8000default becomes127.0.0.1:8000in 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, andSUCHI_DEV_ALLOW_LAN=1. The two IP literals must match; the server never binds every interface in dev mode. - Refuses non-local
PUBLIC_URL— allowslocalhost,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*.localname. Anything else exits non-zero. - Refuses when
OIDC_ISSUER_URLis set — single auth path only. Drop OIDC env vars for the dev iteration or use a separate DATA_DIR. - Refuses when
SUCHI_DEMO_MODE=1is 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 withSUCHI_DEV=1plus a loopback URL would otherwise gain a second admin row carrying the public default credentials; instead the boot exits withlocalauth: 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=1cannot 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.
[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.
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 theemail_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.
MCP client env (for suchi mcp / suchi-mcp symlink)
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 thesettings 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_FILELLM_API_KEY_FILE
_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.