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
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 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. ASHARE_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:
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 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 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)
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.
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
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_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, 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.