All docs
Docs · Reference
Chassis runtime
Operator reference for txco serve: personalities, listeners, data on disk, and the flags that matter in production.
Every flag can also be set as an environment variable using the TXCO_ prefix and underscores (--ingress-config ↔ TXCO_INGRESS_CONFIG). --env (default dev) names the environment
and is embedded in database filenames. This page covers the flags that
matter together; every flag lists all of them, generated
from the code. Running several chassis as one: fleet.
Personalities
--personalities selects which heads the chassis boots. Default: cron,tcp,web,admin. The rest are opt-in:
| Personality | What it adds |
|---|---|
lmtp | Inbound mail (lmtp) |
mailmap | A Postfix tcp_table responder that tells the edge mail server which domains are accepted, from the tenants’ hostnames. Off until --mailmap-listen-addrs gives it an address; it is unauthenticated, so bind it only where that Postfix reaches it |
dns | Authoritative DNS for delegated zones; required for the built-in ACME TLS path (dns) |
imap | Mailboxes stacks fill, served to mail clients (imap) |
websocket | Sessions on the web head (websocket) |
calendar, contacts | CalDAV and ICS feeds, CardDAV, on the web head (calendar, contacts) |
webdav | The drive as a mountable folder, on the web head (webdav) |
ipp | A stack as a printer (ipp) |
scheduled | The poller that fires txco://schedule events (scheduled) |
state | The dispatcher that presents state transitions (state) |
source | The poller that watches remote mailboxes (source) |
grant | The socket txco sandbox reaches the chassis on (grants) |
| Head | Flag | Default | Notes |
|---|---|---|---|
| web | --web-addr | :8080 | Event inlet, plain HTTP. TLS terminates at a front proxy by default. |
| tcp | --tcp-listen-addrs | :5050 | Line-delimited JSON. Comma list of name=addr or bare addr; a named entry sets _txc.tcp.listener for ingress routing. ;tls after the address terminates TLS and routes by SNI hostname (;self-signed for dev); ;handler=NAME speaks another protocol on the listener (see tcp). |
| admin | --admin-addr | :8081 | Mutating API + admin UI — see admin-api.md. |
| lmtp | --lmtp-listen-addrs | :2424 | Only binds when lmtp is in --personalities. |
| cron | --cron-period | 60 | Seconds between ticks. |
| grant | --grant-socket | under the system’s temp dir | A Unix socket, not a port: txco sandbox asks the chassis to open a sandbox on it. See grants. |
--web-tls-addr (e.g. :8443) makes the chassis terminate TLS itself,
obtaining wildcard certificates via ACME DNS-01 against its own DNS
head — requires the dns personality and --acme-email. Empty
(default) leaves TLS to your reverse proxy.
Behind a reverse proxy: whose address is it?
Behind a proxy, every HTTP request arrives from the proxy’s address. Name
your proxies and the chassis reads the client’s address from X-Forwarded-For instead:
txco serve --web-trusted-proxies "10.0.0.0/8 fd00::/8" # or TXCO_WEB_TRUSTED_PROXIES - Trust is the socket peer, never the header. The header is read only when the connection comes from one of those CIDRs (or bare IPs). From anyone else it is ignored, so a client cannot choose its own address.
- Read from the right. Each proxy appends the address it accepted the request from, so the header runs from the client towards the chassis. The client is the first address from the right that is not one of your proxies. Anything a client typed into the header sits to the left of that and is never reached — two proxy hops work, and so does a proxy that passes an incoming header along.
- What uses it. The per-IP login limit (
--login-rate) of the calendar, contacts, webdav and ipp heads and their login lines; the client address those heads and the websocket head put on their events;@client.ipon every web request, which is what a rule should key a rate limit on; theipof the web access log. - Left empty (the default) nobody is trusted and the client is the socket peer. Behind a proxy that means every client shares ONE per-IP login budget, and one app retrying a dead password can use it up for everybody — set the flag on any node that sits behind a proxy. Only list addresses that your proxies alone can connect from.
- An entry that does not parse is logged at start and not trusted. The chassis logs the trusted list once when the web head starts.
IMAP and the TCP head get the same fact from the PROXY protocol instead
(--imap-proxy-protocol; imap.md). The admin API
never reads the header.
Data on disk
All state is local files — back these up.
| Path | What |
|---|---|
./chassis/data/db/runtime-$env.db | Runtime SQLite DB (rules, tenants, hostnames) |
./chassis/data/db/auth-$env.db | Auth SQLite DB: admin actors, keys, invitations — and your stacks’ users and credentials, which every node opens |
./chassis/data/kv/ | KV store (BoltDB by default) |
./chassis/data/secrets/txco-master.key | Secret-store master key — back up separately; see the secret-store runbook |
./chassis/data/continuations/ | Suspended-run state (--continuation-store=file) |
./chassis/data/artifacts/ | Compute artifacts (wasm modules) |
./chassis/data/filecas/ | The content-addressed store: blob bytes, dataset artifacts, retained mail |
./data/trace/ | Trace output when --trace-mode ≠ off |
A personality’s store exists once that personality runs (with its SQLite backend, the default):
| Path | What | Personality |
|---|---|---|
./chassis/data/state.db | State records and their transition events | state |
./chassis/data/scheduled.db | Scheduled events not yet fired | scheduled |
./chassis/data/imap.db | IMAP accounts, mailboxes and message index | imap |
./chassis/data/calendar.db | Calendars and their events | calendar |
./chassis/data/contacts.db | Address books and their cards | contacts |
./chassis/data/drive.db, ./chassis/data/drive/ | The drive’s index and its bytes | webdav |
./chassis/data/ipp.db | Printers and print jobs | ipp |
./chassis/data/notebook.db | Notebooks | any |
./chassis/data/vector.db | The vector store | any |
./chassis/data/search/ | The search store, one index directory per collection | any |
./chassis/data/workspaces/ | Files of workspaces on the local provider | any |
./acme/ | Issued TLS certificates and the ACME account (--cert-storage-path) | web with ACME |
Disposable, rebuilt as needed: ./chassis/data/datasets/ (a cache of
dataset artifacts in front of the content store) and the DB mirror below.
Roots are configurable (--db-root-dir, --kvstore-addrs, --secret-master-key, --trace-dir, and a --<store>-db-path or --<store>-path flag per store above).
The request path never reads the runtime DB directly: it reads a SQLite mirror of it, rebuilt on every txco apply and swapped in atomically.
By default that mirror lives in memory. --db-mirror-mode=file keeps it as
a disposable file under --db-root-dir instead (mirror-<pid>-<gen>.db),
which makes its pages reclaimable page cache rather than fixed RAM and
lets a reload build the next generation on disk instead of holding two
mirrors at once — the trade is a disk read on a cold page, so it suits a
small, single-purpose node (a dedicated DNS head) rather than a busy web
node. Stale files are swept at boot.
Dispatch limits
| Flag | Default | Meaning |
|---|---|---|
--op-timeout | 5s | Per-op timeout when a rule sets none |
--op-timeout-max | 10m | Ceiling for any rule’s WITH timeout |
--op-payload-max | 4194304 | Max op payload, bytes (4 MiB) |
--max-fuel-per-request | 100000 | Fuel budget per request — see fuel.md |
--compute-max-memory-mb | 32 | Memory cap per sandboxed nano-op |
--compute-max-wall | 250ms | Wall-clock cap per nano-op invocation |
Network policy
--egress-policy controls what ops may dial out to: private (default)
blocks loopback, RFC 1918, link-local, CGNAT, cloud-metadata ranges, and
anything in --egress-deny-cidrs; open allows any address. The default
is private so a chassis running tenant-authored ops can’t be steered
into an SSRF against 169.254.169.254 (cloud metadata), the admin API,
or other internal services. Set --egress-policy=open (or whitelist a
specific range with --egress-allow-cidrs) only when your own rules must
reach internal/localhost services. txco dev and start.sh opt into open for local development.
Shutdown
On SIGTERM or SIGINT the chassis drains before it stops. New work from
peers that come back on their own is refused: web requests get 503 with Retry-After (and /healthz answers 503, so a load balancer moves on),
and LMTP deliveries get 451, so the sending MTA requeues. The scheduled and source pollers stop claiming. Work already in flight keeps running —
requests, a continuation’s detached tail, a worker callback’s resume — and
so does internal work nobody would retry, such as a scheduled event already
claimed. The chassis waits up to --shutdown-grace for all of it to finish,
then cancels whatever is left and stops.
| Flag | Default | Meaning |
|---|---|---|
--shutdown-grace | 25s | How long in-flight work gets before it is cancelled; 0 cancels at once |
Give the container a stop timeout above the grace. Docker’s default is 10s, after which it sends SIGKILL mid-drain; in Compose, set stop_grace_period (for example 60s).
SIGUSR1 turns the same drain on without stopping the process, and SIGUSR2 turns it off — for taking a node out of rotation by hand.
AI gateway
The web head also serves the AI-gateway inlet (POST /v1/messages), which forwards to
an upstream model provider after running the request through the tenant’s _llm stack.
It is per-tenant opt-in — a tenant enables it by authoring an _llm stack, so there is no
enable flag. Full reference: llm-gateway.md.
| Flag | Default | Meaning |
|---|---|---|
--llm-upstream-url | https://api.anthropic.com | Base URL forwarded to; invalid fails at boot |
--llm-context-max-tokens | 2000 | Estimated-token cap on injected context |
--llm-context-max-items | 8 | Item cap on injected context |
Context injection needs both caps positive. Setting either to 0 disables injection
entirely, and stack-emitted items are then dropped with only a debug log.
Routing and tenancy
--ingress-config— path to a staticingress.yaml; empty (default) disables the YAML layer. Hostname bindings in thetenant_hostnamestable work either way — see ingress.md.--ingress-miss-action—fallthrough(default) sends unmatched events to theboot/%/0entry;rejectreturns a clean 404 without invoking the processor. Userejectwhen everything routes viatenant_hostnames.--require-hostname-verification—falseby default; settruein production so unverified hostname bindings don’t route. (--dev-auto-verify-local-hostnames, defaulttrue, auto-verifieslocalhost-style names for development.)
Set --require-hostname-verification=true in production. It’s false by default
(convenient for dev), which means unverified hostname bindings still route — a
tenant could route a hostname it hasn’t proven it owns.
Admin auth
--auth-mode is one of basic, signed, or both (default both); --admin-user / --admin-pass set the basic credentials. With basic or both, no basic credentials, and a dev --env (the default) or --admin-allow-open, the chassis runs in open-dev mode: a request with no
credentials gets an admin:all context, source: "open" — local
development only. Outside a dev --env the same setup fails closed. Details
and the enrolment flow: admin-api.md.
The admin API’s unauthenticated endpoints (dev enrolment, accepting an
invitation, cloud OAuth enrolment) share one rate limit per client IP: 10 attempts a minute by
default. TXCO_THROTTLE_RATE (attempts) and TXCO_THROTTLE_WINDOW (a
duration such as 30s) change it, and any value in TXCO_THROTTLE_DISABLED turns it off. These three are read from the environment only, not as flags.
Observability
--trace-modeoff(default) |summary|full;--trace-dir(default./data/trace);--trace-async(defaultfalse) moves trace writes off the request path — see trace.md.- The chassis’s own metrics and spans (request counts and timings,
per-op durations) go out over OTLP to whatever the standard
OTEL_EXPORTER_OTLP_ENDPOINTnames — see telemetry. (--prom-namespaceand--prom-periodare not read: there is no Prometheus endpoint.) - One usage record per completed request, for metering
(
--usage-enabled, default on). --log-ops(defaultdisabled) writes per-op logs to--log-ops-dir.