Documentation for v2.10.0. Read the latest, v2.12.0.
Operator
Configuration
Everything the service takes from its environment is in one table below, one row per variable. What an administrator changes at runtime (cleanup ages, the cache size, the authentication mode, the webhook allow-list) lives in the database instead and is listed at the end.
What changed in 2.6.0
Environment variables#
A variable that is unset takes its default. A variable set to something the service cannot use stops the start (below); the service never falls back silently.
| Variable | Default | Effect |
|---|---|---|
DATABASE_URL |
sqlite:sanshain.db?mode=rwc |
The database. postgres://user:pass@host:5432/db (or postgresql://)
selects PostgreSQL, anything else SQLite. The image sets
sqlite:/data/sanshain.db?mode=rwc. Set but empty counts as unset.
|
BIND_ADDRESS |
0.0.0.0:3000 |
Address and port to listen on. Binding all interfaces logs
SECURITY WARNING: binding to all interfaces; 127.0.0.1:3000
restricts to localhost.
|
MAX_POSTGRES_CONNECTIONS |
20 |
Pool size on PostgreSQL. |
MAX_SQLITE_CONNECTIONS |
5 |
Pool size on SQLite (WAL mode: readers beside one writer). |
SQLITE_BUSY_TIMEOUT_MS |
5000 |
How long a SQLite writer waits for the lock before failing. |
CACHE_MEMORY_MB |
256 |
Memory bound of the in-memory data cache; 0 disables it. The admin page can
change it at runtime (below).
|
CLEANUP_INTERVAL_SECS |
3600 |
Period of the background pass that expires snapshots, prunes stale dependencies, closes
stale trunk data, removes expired sessions and tokens, settled webhook deliveries and old
CSRF tokens. See Administration.
Must be at least 1; 0 is refused at startup.
|
CSRF_MAX_AGE_HOURS |
24 |
Age after which an unused CSRF token is pruned by that pass. |
LOG_BUFFER_SIZE |
100 |
Lines kept per level (error, warn, info, debug) in the in-memory log buffer the observability page shows. |
SPEC_UPDATED_CHANNEL_SIZE |
100 |
Capacity of the broadcast channel behind the SSE and WebSocket update streams; a slow subscriber lags past it. |
MAX_SPEC_BODY_BYTES |
4194304 |
Largest request body (4 MiB); a larger one is refused with 413 before parsing.
0 is refused at startup.
|
RUST_LOG |
sanshain_service=info,tower_http=warn |
The filter for the log written to stdout, in
tracing_subscriber::EnvFilter syntax.
|
CAPTURE_LOG_FILTER |
sanshain_service=debug,tower_http=debug |
The filter for the in-memory buffer, independent of stdout. |
LOG_FORMAT |
text |
json writes one JSON object per line to stdout. |
OTEL_ENABLED |
false |
true exports traces over OTLP/gRPC; a failure to build the exporter stops the
start. See Observability.
|
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4317 |
The collector the traces go to. |
OTEL_EXPORTER_OTLP_HEADERS |
unset |
Read by the OpenTelemetry SDK, not by the service. Headers sent with every export,
key=value,… (an auth token for the collector, for example).
|
OTEL_EXPORTER_OTLP_COMPRESSION |
unset |
Read by the OpenTelemetry SDK, not by the service. gzip or
zstd compresses the export.
|
OTEL_EXPORTER_OTLP_TIMEOUT |
10000 |
Read by the OpenTelemetry SDK, not by the service. Export timeout in milliseconds. |
OTEL_EXPORTER_OTLP_PROTOCOL |
grpc |
Read by the OpenTelemetry SDK, not by the service. The exporter is built for gRPC; another value is ignored by this build. |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
unset |
Read by the OpenTelemetry SDK, not by the service. Trace-specific endpoint, overrides
OTEL_EXPORTER_OTLP_ENDPOINT.
|
OTEL_EXPORTER_OTLP_TRACES_HEADERS |
unset |
Read by the OpenTelemetry SDK, not by the service. Trace-specific headers, override
OTEL_EXPORTER_OTLP_HEADERS.
|
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION |
unset |
Read by the OpenTelemetry SDK, not by the service. Trace-specific compression, overrides
OTEL_EXPORTER_OTLP_COMPRESSION.
|
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT |
unset |
Read by the OpenTelemetry SDK, not by the service. Trace-specific timeout, overrides
OTEL_EXPORTER_OTLP_TIMEOUT.
|
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL |
unset |
Read by the OpenTelemetry SDK, not by the service. Trace-specific protocol, overrides
OTEL_EXPORTER_OTLP_PROTOCOL.
|
STATIC_DIR |
static |
Directory of the web UI's files, served as the fallback route. |
INSTANCE_ID |
a random UUID | Identifies this process in the log and in GET /version. |
INITIAL_ADMIN_USERNAME |
root |
Name of the account created on the first start, when no user exists. Also the fallback for
SANSHAIN_ROOT_USERS.
|
INITIAL_ADMIN_PASSWORD |
random, printed to stderr | Password of that account, read on the first start only. Set but empty counts as unset: the random password is generated and printed. |
INITIAL_AUTH_MODE |
Off |
The auth mode of the first start, when no user exists: off, dev,
local, ldap or oidc. Unset, a fresh instance starts
in Off, where only the admin signs in and picks the mode. dev needs
ALLOW_INSECURE_DEV_MODE=true. See Off.
|
SANSHAIN_ROOT_USERS |
INITIAL_ADMIN_USERNAME, else root |
Comma-separated usernames that hold every permission by configuration. See Root users. |
SANSHAIN_DIRECTORY_GROUP_TTL_SECS |
300 |
How long a user's directory group membership is cached; 0 re-reads it on every
check. See Directory group caching.
|
SANSHAIN_DEV_MODE |
unset | Ignored since 2.10; startup logs a warning while it is set. Developer mode is the Dev auth mode. |
ALLOW_INSECURE_DEV_MODE |
false |
The safety gate: developer mode only takes effect when this is true; without it
a requested dev mode is refused and logged. Never set in production.
|
EXTRA_CA_CERTS_DIR |
unset | Directory of PEM certificates added to the trust store for LDAPS. See Additional CA certificates. |
LDAP_OPERATION_TIMEOUT_SECS |
10 |
How long one LDAP exchange (TLS or StartTLS handshake, bind, search, unbind) may take once the TCP connection is up. A value that is not a positive whole number is ignored and the default used. |
MCP_ENABLED |
true |
Mounts the MCP endpoint at /mcp;
false leaves it unmounted (404).
|
MCP_CALLS_PER_MINUTE |
60 |
Tool calls one API token may make per minute: a token bucket of that size, refilled
continuously, so a burst of 60 is allowed. 0 is refused at startup.
|
MCP_MAX_RESULT_BYTES |
65536 |
A tool answer above this size is replaced by a summary. 0 is refused at
startup.
|
WEBHOOKS_ENABLED |
true |
Writes webhook events to the outbox and runs the delivery worker. false writes
and sends nothing; hooks can still be managed, and a test event is refused with
409.
|
WEBHOOK_WORKER_INTERVAL_SECS |
5 |
How often the webhook worker looks for due deliveries. Must be greater than 0. |
WEBHOOK_DELIVERY_TIMEOUT_SECS |
10 |
How long one webhook POST may take before it counts as a failed attempt. Must
be greater than 0.
|
PUBLIC_BASE_URL |
unset |
The URL people open Sanshain at (https://sanshain.corp), for the deep links in
webhook messages and for the admin page named in the first-start message; a trailing slash
is dropped and it must start with http:// or https://. Unset, the
deep links use the origin of the OIDC redirect URL (with neither, they are omitted) and the
first-start message names the bind address.
|
There is no variable for the metrics path or the session length: metrics are always at
/metrics, a session always lasts 7 days.
Invalid values#
A numeric variable that is not a number, or a fixed-vocabulary variable (LOG_FORMAT,
OTEL_ENABLED, MCP_ENABLED, WEBHOOKS_ENABLED) with an
unrecognised value, makes the service exit before it binds, with the variable named on stderr:
CONFIGURATION ERROR: LOG_BUFFER_SIZE="1OO" is not a valid number. Correct it or unset it to use the default (100).
CONFIGURATION ERROR: LOG_FORMAT="yaml" is not recognised. Valid values: json, text. Unset it to use the default (text).
The same for values that would make the service useless: MAX_SPEC_BODY_BYTES=0,
MCP_CALLS_PER_MINUTE=0, MCP_MAX_RESULT_BYTES=0,
WEBHOOK_WORKER_INTERVAL_SECS=0, WEBHOOK_DELIVERY_TIMEOUT_SECS=0,
CLEANUP_INTERVAL_SECS=0, a PUBLIC_BASE_URL without a scheme, and an
unparsable BIND_ADDRESS or DATABASE_URL. Two variables are the exception
and fall back quietly: LDAP_OPERATION_TIMEOUT_SECS and
SANSHAIN_DIRECTORY_GROUP_TTL_SECS use their default when the value is not a whole
number (and, for the LDAP timeout, when it is zero). Boolean-like variables read with a plain
comparison (ALLOW_INSECURE_DEV_MODE) are true only
for the exact string true.
Root users#
SANSHAIN_ROOT_USERS names the accounts that hold every
permission, including ones a later release adds.
Root is deliberately not stored: its authority comes
from configuration, so no UI action, role change, migration or SQL update can revoke it. That
makes it the recovery path when an administrator locks themselves out.
The list is read once at startup; changing it means a restart. Unset or empty, it falls back to
INITIAL_ADMIN_USERNAME, then to root, so an existing deployment keeps a
root account without being reconfigured. The resolved set is logged:
Root users (configuration-held, cannot be revoked in-app): root.
Naming an account grants full authority whether or not it exists, is approved or holds a role, so
keep the list short. Because the grant is a pure username match, an unclaimed root name would be a
standing invitation: startup therefore creates a placeholder account for every configured name
that has none, and registration refuses reserved names with 409
This username is reserved. The placeholder has no usable local password; the first
LDAP or
OIDC login under that name signs in as it, and it
becomes that directory user's shadow account. The log says so:
Claimed root username 'breakglass' with a placeholder account: ….
SANSHAIN_ROOT_USERS=root,breakglass
Directory group caching#
With LDAP, a user's group membership
is read from the directory through the configured service account on every authorisation check and
cached for SANSHAIN_DIRECTORY_GROUP_TTL_SECS. Sanshain stores which
roles a directory group confers, never who is in it. The
TTL is the dial between load on the directory and how long a revocation takes to bite;
0 disables caching, so every check reads the directory.
When the directory is unreachable, directory-derived roles are omitted, and the
empty answer is cached for the TTL too, so an outage does not cost a directory timeout per
request. Directory roles are purely additive, so leaving them out can only remove privilege. An
outage degrades a directory-backed administrator to whatever Sanshain itself granted them;
SANSHAIN_ROOT_USERS, held in configuration, still works. The alternative, a stale
answer for the duration of the outage, would keep a revoked administrator in exactly that long.
The data cache#
Every data cache (resolved endpoints, endpoint lists per version, spec content, reports, Producer and Consumer lists, version metadata) holds an entry for at most 10 seconds. The value is fixed. A write invalidates the caches at once, and Sanshain runs as one instance, so the limit is never noticed; it is also why a second instance would serve stale data. See Scaling.
CACHE_MEMORY_MB is the initial memory bound. The admin page's
In-Memory Cache section shows hit rate, entries and memory used, changes the
bound at runtime (POST /admin/settings/cache, which rebuilds the caches) and clears
them (POST /admin/cache/clear). The runtime value is not persisted; a restart returns
to the variable.
Additional CA certificates#
Set EXTRA_CA_CERTS_DIR to a directory of PEM files when Sanshain must trust a private
or corporate certificate authority, typically for LDAPS against an internal directory. Every
*.pem, *.crt and *.cer file in the directory is read at
startup and added to the platform trust store, never substituted for it. A file
may hold one certificate or a chain; .crt is accepted because a Kubernetes Secret is
conventionally keyed ca.crt. Files with any other name are ignored, so the entries a
projected volume creates (..data and friends) do not interfere.
Startup fails rather than continuing with an incomplete store when:
CA certificate directory '…' does not existCould not read CA certificate directory '…': …-
Could not read CA certificate file '…': …: the file cannot be parsed, or holds no certificate at all, which means something other than a bundle was mounted.
On success the log says Trusting N additional CA certificate(s) from M file(s) in …;
certificate contents are never logged. Certificates are read once, so rotating them means a
restart. Unset, nothing changes: the platform bundle, which the image ships, is used.
This applies to LDAP connections. The database and the OTLP exporter have their own TLS configuration and do not consult this directory. Sanshain builds the LDAP trust store itself in every case, configured or not: left to its own, the LDAP library substitutes an empty root store when reading the platform certificates fails, which rejects every certificate and reports nothing.
Logging#
Two filters, two sinks. RUST_LOG governs stdout (LOG_FORMAT=json for
machine-readable lines); CAPTURE_LOG_FILTER governs the in-memory buffer of
LOG_BUFFER_SIZE lines per level that the
observability page shows, so the buffer
can hold debug lines while stdout stays at info. Two debug flags (business logic, admin/user
activity) widen the buffer further at runtime without a restart. Secrets, tokens and passwords are
never logged.
Settings in the database#
These are changed on the admin page or through /admin/settings/* and survive a
restart. Each route needs a signed-in session; API tokens are not accepted on administrative
routes.
| Setting | Default | Route and permission |
|---|---|---|
auth_mode |
Off on the first start, or INITIAL_AUTH_MODE |
GET/PUT /admin/auth-config, manage_auth_config. With
the LDAP and OIDC configurations. See Authentication.
|
dev_mode |
off | Follows the Dev mode of the same form; gated by the environment. |
auth_mode_before_off |
none | Written when the mode becomes Off: the sign-in source Off keeps for administrators. Not set directly. |
registration_enabled |
false |
POST /admin/settings/registration, manage_settings. Applies in
Local mode only.
|
auto_approve_users |
false |
GET/POST /admin/settings/auto-approve,
manage_settings.
|
snapshot_max_age_days |
30 |
GET/POST /admin/settings/snapshot-max-age,
manage_settings. 0 disables.
|
dependency_max_age_days |
30 |
GET/POST /admin/settings/dependency-max-age,
manage_settings. Must be greater than 0.
|
trunk_max_age_days |
90 |
GET/POST /admin/settings/trunk-max-age,
manage_settings. 0 disables.
|
webhook_allowed_hosts |
[] |
GET/POST /admin/settings/webhook-allowed-hosts,
manage_settings. See Webhooks.
|
Every *_max_age_days accepts at most 36500 days; a larger value answers
400 days must be at most 36500.
Checked against v2.6.1.