Environment variables
Applies to: SimpleTwo 0.9.x · Checked: 26.09.2026
Names and defaults are taken from the same files the services themselves read
(site/docs-gen/env-variables.py). The list is therefore complete by construction: a variable not
here is a variable no service reads.
These values are normally set by the coordinator when it installs a role: it writes the service file and fills the parameters in. The reference matters in two cases — when you are working out why a service behaves the way it does, and when you install one by hand.
1. The coordinator
The coordinator keeps a registry of its variables, and each row records what changing it costs:
| Class | Meaning |
|---|---|
restart | the value is read once at startup; editing it in a running process's environment does nothing |
hot | re-read while the process runs |
A name that is not in the registry is not treated as an error: the coordinator warns and keeps running. That decision was bought with an outage — the first version of the registry refused to start on an unknown variable, and an installation went down because of a variable the image itself shipped. Anything that runs before the service must not be able to stop it.
| Variable | Type | Applies | What it does |
|---|---|---|---|
ST_ADDR | string | restart | HTTP listen address: the domain-validation challenge (ACME) and the redirect to HTTPS |
ST_HTTPS_ADDR | string | restart | HTTPS listen address; empty disables HTTPS |
ST_TOKEN_TTL | duration | restart | session token lifetime. Kept short because revocation here is expiry |
ST_REFRESH_TTL | duration | restart | refresh token lifetime; long, and revocable server-side |
ST_GATEWAY_TTL | duration | restart | how old a VPN node's heartbeat may be before the node counts as stale |
ST_TRUSTED_PROXIES | csv | restart | networks whose X-Forwarded-For may be believed; empty means the header is ignored |
ST_ACME_STAGING | bool | restart | issue certificates from Let's Encrypt staging instead of production |
ST_TLS_DOMAIN | string | restart | the name the coordinator orders a Let's Encrypt certificate for; empty means self-signed |
ST_TLS_EMAIL | string | restart | contact address for the certificate authority; optional |
ST_AGENT_BIN_DIR | path | restart | the directory holding agent builds for each platform: the coordinator hands them to hosts when installing roles |
ST_DEPLOY_DRYRUN | bool | restart | print the installation commands instead of running them. The value is parsed strictly — False and Off are false, and a typo is refused with an error rather than silently becoming enabled |
ST_DB_PATH | path | restart | the coordinator's database file (SQLite); empty means an in-memory store, which is a test bench |
ST_KEYSTORE_PATH | path | restart | the file holding the master encryption key. A path, never the key itself: a key in an environment variable is a key in a log. The file must live outside the data directory, or a backup of the data carries the key to it |
ST_VERSION | string | restart | the build version; supplied by the image, never set by hand |
ST_TOKEN_SECRET 🔒 | string | restart | the token signing secret; shared by the coordinator and the product services |
2. The messaging service
| Variable | Default | What it does |
|---|---|---|
MSG_ACME_CACHE | /var/lib/simpletwo/msg-acme | the directory the issued certificate is kept in |
MSG_ACME_EMAIL | — | contact address for the certificate authority; optional |
MSG_ACME_STAGING | 0 | Let's Encrypt staging instead of production |
MSG_ADDR | :9090 | the address and port the service accepts clients on |
MSG_AI_PROVIDER_KEY_FILE | — | the path to the file holding the model provider's key. A file only: a value in the environment ends up in logs |
MSG_AI_PROVIDER_KIND | anthropic | the model provider's dialect: anthropic or OpenAI-compatible |
MSG_AI_PROVIDER_URL | — | the model provider's address; the default depends on the dialect |
MSG_CONTROL_ADDR | 127.0.0.1:9091 | the control API's address. Loopback by default: it does not face outward |
MSG_CONTROL_TOKEN 🔒 | — | the control API's token; injected by the coordinator |
MSG_CONVLOG_KEEP | 10000 | how many revisions of a conversation's log to keep |
MSG_COORDINATOR_CERT_SHA256 | — | the coordinator certificate's fingerprint (a pin); protects against substitution |
MSG_COORDINATOR_INSECURE | false | do not verify the coordinator's certificate. Test benches only |
MSG_COORDINATOR_URL | — | the coordinator's address |
MSG_DB_DSN | — | the Postgres connection string. Empty means an in-memory store, which is a test bench |
MSG_DEPROVISION_RETENTION_DAYS | 30 | how many days the data of a person whose chat and calls were withdrawn in identity is kept: they are disabled at once (refused, sockets and pushes stopped), and the data is deleted afterwards. 0 deletes on the next pass; the clock runs only while access assignment is on in identity |
MSG_DIRECTORY_SOURCE | — | not read since messaging 0.199.0: people and groups always come from the directory's feed when MSG_IDENTITY_URL is set, people's rights from the live RBAC projection. The 0.x coordinator's agent still sets it from the console switch |
MSG_DOMAIN | — | this installation's own domain for federation; empty means federation is off |
MSG_FEDERATION_ALLOW | — | the domains this installation accepts delivery from, comma-separated |
MSG_IDENTITY_URL | — | identity's private address: the service reads the assignments of the chat-and-calls feature there and reports what it created or deleted. Empty means no assignments are read: everybody is admitted, nobody is deprovisioned |
MSG_LIVEKIT_KEY | — | the media server's key — room passes are signed with it |
MSG_LIVEKIT_SECRET 🔒 | — | the media server's secret |
MSG_MAX_MEMBERS | 10000 | the cap on members of one conversation |
MSG_MAX_UPLOAD_MB | 50 | the size limit of a single attachment, in megabytes |
MSG_MEDIA_DIR | /var/lib/simpletwo/media | the attachment directory when attachments live on disk rather than in object storage |
MSG_PRESENCE_GRACE_SEC | 45 | how many seconds somebody counts as online after their last connection drops |
MSG_PSTN_CDR_RETENTION_DAYS | 0 | how many days a telephone call record (CDR) is kept after the call ended; 0 takes the default, 395 days |
MSG_REDIS_ADDR | — | the bus address; needed for the SFU pool, telephony and multiple replicas |
MSG_REDIS_DB | 0 | the Redis database number |
MSG_REDIS_PASSWORD 🔒 | — | the bus password |
MSG_RETENTION_DAYS | 0 | message retention in days; 0 means never delete |
MSG_S3_ACCESS_KEY 🔒 | — | the object store's access key |
MSG_S3_BUCKET | — | the bucket for attachments |
MSG_S3_ENDPOINT | — | the object store's address |
MSG_S3_PREFIX | — | a key prefix inside the bucket |
MSG_S3_REGION | — | the object store's region |
MSG_S3_SECRET 🔒 | — | the object store's secret |
MSG_SFU_POOL_PATH | /var/lib/simpletwo-messaging/sfu-pool.json | the file holding the SFU pool snapshot |
MSG_TLS_ADDR | :443 | the HTTPS listen address when the service terminates its own TLS |
MSG_TLS_HOST | — | the service's public name, and the name in its certificate |
MSG_TURN_HOST | — | the TURN server name handed to clients |
MSG_TURN_SECRET 🔒 | — | the TURN shared secret that temporary credentials are derived from |
3. The directory service — the directory and sign-in
| Variable | Default | What it does |
|---|---|---|
DIR_ACME_CACHE | /var/lib/simpletwo/identity-acme | the certificate cache directory: without it the certificate is re-issued on every start and runs into the CA's rate limit |
DIR_ACME_EMAIL | — | contact address for the certificate authority; optional |
DIR_ACME_STAGING | 0 | Let's Encrypt staging instead of production |
DIR_ADDR | :8086 | the private HTTP listen address — the coordinator writes to identity through it |
DIR_ALLOWED_ORIGINS | — | browser origins besides the coordinator's own that may read the directory's address book (/v1/directory/*): comma-separated, exact scheme://host[:port]. Needed when the web client is embedded in another site's page. Empty: the coordinator's origin only |
DIR_APNS_KEY_ID | — | the APNs key identifier |
DIR_APNS_KEY_P8_FILE | — | the path to notify's APNs key file (.p8) — iPhone and Mac pushes; the agent writes it from the coordinator's settings |
DIR_APNS_SANDBOX | 0 | send pushes to Apple's sandbox instead of production (Xcode builds) |
DIR_APNS_TEAM_ID | — | the Apple Developer team identifier |
DIR_APNS_TOPIC | — | the iPhone app's VoIP topic (bundle id plus .voip); the alert topic is the same id without the suffix |
DIR_APNS_TOPIC_MACOS | — | the macOS app's bundle id — its own topic; empty, no Mac is pushed to |
DIR_AUDIT_ALARM_BYTES | 1073741824 | the security event log size at which /healthz raises the storage alarm; 1 GiB by default |
DIR_BREACH_LIST | — | a file of leaked-password SHA-1s (a Pwned Passwords export), searched on disk and never loaded. Empty means the built-in list of common passwords alone; no third-party service is called |
DIR_COORDINATOR_CERT_SHA256 | — | the SHA-256 fingerprint of the coordinator's self-signed certificate |
DIR_COORDINATOR_INSECURE | 0 | trust the coordinator's self-signed certificate; only together with DIR_COORDINATOR_CERT_SHA256 |
DIR_COORDINATOR_URL | — | the coordinator's address the key for the integration secrets is fetched from. Empty means no key: the secrets stay as they are and /healthz says "secrets_sealed": false |
DIR_DATA_DIR | /var/lib/simpletwo/directory | the directory of the store's files on the role's host: directory.db, notify.db, calendar.db (embedded SQLite). Empty refuses to start: a directory without a store has nowhere to put a group |
DIR_EXPECTED_SHA256 | — | the digest of the binary the coordinator served and the agent verified. Set, the service hashes itself at start and refuses to run on a mismatch; empty, it only reports the digest in /healthz |
DIR_FCM_SA_JSON_FILE | — | the path to notify's Firebase service-account file (Android pushes) |
DIR_MAIL_URL | — | a mail node's private address: sign-in codes to a person's verified external address leave through its service door. The coordinator writes it when the installation has a mail host; empty means no e-mail codes, and the policy switch will not turn them on |
DIR_PLATFORM | — | this directory's platform (simpletwo, simplezero, simplethree): its schema extension's prefix — x-<platform>-… in LDAP and urn:ietf:params:scim:schemas:extension:<platform>:2.0:User in SCIM — under which its catalogue is seeded and a new attribute lands. Empty: simpletwo |
DIR_PLATFORM_ISSUER | — | the issuer name of platform tokens in this installation — the primary directory's public URL, the same the coordinator hands every verifying service; a replica signs tokens with its own keys under that name, and every instance publishes every instance's keys at /oauth/jwks. Set by the coordinator; empty: the instance's own URL |
DIR_PROFILES | — | the role's layers on this host, comma-separated: base (the line's store: accounts, groups, the installation's objects, the schema, the minimal sign-in, SCIM), simpletwo (SimpleTwo's extension: address books and the GAL, aliases, the rooms' legs, guests, bots, the move, access to the products), calendar. Empty is every one; base is always on; an unknown name refuses to start |
DIR_PUBLIC_URL | — | the service's origin as clients reach it; the coordinator publishes it as identity_url |
DIR_REPLICATION_PEERS | — | the other directory instances of this installation — their SCIM bases (https://<name>/scim/v2), comma-separated; the directory reads each one's feed every minute and applies the rows by the version rule (multi-master). The coordinator sets the list; empty — no peers |
DIR_REPLICATION_ROLE | — | this instance's standing among its peers: executor runs the outbound jobs (SCIM targets, the SIEM, HRMS, the upstream LDAP, SAML metadata), replica does not; what the directory serves (LDAP, OIDC/SAML, SCIM, sign-in) every instance serves. Empty — the coordinator does not manage replication here and the settings in the store stand |
DIR_SCHEMA_MASTER | — | the origin of the instance where the schema changes (https://<name>): the schema doors here answer 307 to it. Empty — the schema changes at this instance |
DIR_TLS_ADDR | :443 | the HTTPS listen address |
DIR_TLS_HOST | — | the service's public name. Set, HTTPS with a certificate it obtains itself, for sign-in, the portal and the console; empty, private HTTP only |
DIR_TOKEN_TTL | 12h | the lifetime of a session this service renews itself; kept equal to the coordinator's |
4. The directory service — the calendar half
| Variable | Default | What it does |
|---|---|---|
DIR_CAL_ADDR | :8085 | the calendar service's address and port |
DIR_CAL_CALDAV_ENABLED | 0 | allow full CalDAV access; off by default |
DIR_CAL_COORDINATOR_CERT_SHA256 | — | the SHA-256 fingerprint of the coordinator's self-signed certificate |
DIR_CAL_COORDINATOR_INSECURE | 0 | trust the coordinator's self-signed certificate; only together with DIR_CAL_COORDINATOR_CERT_SHA256 |
DIR_CAL_COORDINATOR_URL | — | the coordinator's address the key for the address books is fetched from. Empty means no key: contacts are stored in the clear and /healthz says "books_sealed": false |
DIR_CAL_DAV_AUTH | — | how a person signs in to CalDAV and CardDAV: calendar (the default) — the calendar's device password or identity's app password with the scope caldav/carddav; identity — identity's app password alone, device passwords are refused and no longer issued. Written by the agent from the coordinator's dav_auth switch; identity without DIR_CAL_DIRECTORY_URL, or any other value, refuses to start |
DIR_CAL_DB_PATH | — | the calendar's file; by default calendar.db in the DIR_DATA_DIR directory |
DIR_CAL_DEFAULT_TZ | UTC | the default timezone |
DIR_CAL_DEPROVISION_RETENTION_DAYS | — | how many days the calendars and books of a person who was deleted, or whose calendar was withdrawn (steps of the coordinator's tasks), are kept, closed, before they are deleted. 0 deletes on the next pass |
DIR_CAL_DIRECTORY_URL | — | identity's private address: the service verifies app passwords (CalDAV/CardDAV) there. Defaults to the directory's own private address. Since directory 0.58.0 it no longer reads the assignment feed |
DIR_CAL_MEET_URL | — | the origin a meeting's guest link is built from (the coordinator's host). Empty emits no link at all |
DIR_CAL_PUBLIC_URL | — | the base subscription links are built from; defaults to https://<DIR_TLS_HOST>/cal — the calendar answers under /cal on the directory's name. Behind a proxy it must be set, or subscriptions never refresh |
DIR_CAL_S3_ACCESS_KEY | — | the access key of the attachments' object store |
DIR_CAL_S3_BUCKET | simpletwo-calendar | the calendar's attachments bucket; simpletwo-calendar by default |
DIR_CAL_S3_ENDPOINT | — | the object store for the calendar's attachments; empty turns attachments off, and the service says so in /healthz |
DIR_CAL_S3_REGION | — | the attachments' object store region |
DIR_CAL_S3_SECRET | — | the secret of the attachments' object store |
DIR_CAL_SERIES_CAP_YEARS | — | how many years ahead a recurring series is expanded |
DIR_CAL_SERVICE_KEY_DIR | — | the calendar half's own service-key ring for the keys mode of the service token (it signs as svc:calendar); empty, the process-wide ST_SERVICE_KEY_DIR. Written by the agent |
DIR_CAL_TOKEN_TTL | — | the service token's lifetime |
DIR_CAL_WORK_DAYS | 1,2,3,4,5 | the organisation's working days (1 is Monday), comma-separated |
DIR_CAL_WORK_END | 18:00 | the end of the working day |
DIR_CAL_WORK_START | 09:00 | the start of the working day |
ST_SERVICE_KEY_DIR | — | the directory of this host's key (current.json, next.json, previous.json) and of the pinned copy of every service's keys (registry.json). The agent generates and rotates the key on the host itself and registers it with the coordinator; the key never leaves the host. In keys mode the service does not start without it |
5. Mail — the mail and mx roles
| Variable | Default | What it does |
|---|---|---|
MAIL_ACME_CACHE | /var/lib/simpletwo/mail-acme | the certificate cache directory: without it the certificate is re-issued on every start |
MAIL_ACME_EMAIL | — | contact address for the certificate authority; optional |
MAIL_ACME_STAGING | 0 | Let's Encrypt staging instead of production |
MAIL_ADDR | :8087 | the HTTP listen address: JMAP, push, /healthz, the internal API |
MAIL_AUDIT | shared | the mailbox access audit: off, shared (shared mailboxes — the default) or all |
MAIL_CALENDAR_URL | — | the calendar's scheduling feed: every invitation for somebody with a mailbox here arrives as mail |
MAIL_COORDINATOR_CERT_SHA256 | — | the SHA-256 fingerprint of the coordinator's self-signed certificate |
MAIL_COORDINATOR_INSECURE | false | trust the coordinator's self-signed certificate; only together with MAIL_COORDINATOR_CERT_SHA256 |
MAIL_COORDINATOR_URL | — | the coordinator's address the sealing keys are fetched from. Empty means no key can be fetched — acceptable only while encryption is off |
MAIL_DB_DSN | — | the Postgres connection string — both hosts of the pair with target_session_attrs=read-write. Empty means the in-memory store, i.e. a test bench |
MAIL_DIRECTORY_SOURCE | — | not read since mail 0.92.0: people and groups come from the directory alone (MAIL_IDENTITY_URL). The 0.x coordinator's agent still sets it |
MAIL_DMARC_ORG | — | the organisation's name in the DMARC reports (the first mail domain by default) |
MAIL_DMARC_REPORTS | true | whether the domains that ask for DMARC aggregate reports (rua) get them: daily, one per domain (on; off sends none) |
MAIL_DOMAINS | — | mail domains, comma-separated, for the first start: the list of accepted domains is filled from them once (the first is the primary); after that the list is kept in the console's Domains card and this variable is not read |
MAIL_IDENTITY_CERT_SHA256 | — | the SHA-256 fingerprint of identity's self-signed certificate |
MAIL_IDENTITY_INSECURE | false | trust identity's self-signed certificate; only together with MAIL_IDENTITY_CERT_SHA256 |
MAIL_IDENTITY_URL | — | identity's address: people with their addresses, groups with their address, senders and members |
MAIL_INTERNAL_TOKEN 🔒 | — | the token mx authenticates with on the internal API |
MAIL_LMTP_ADDR | :24 | where mx delivers over LMTP; answered only to the mx nodes and this host. off turns it off |
MAIL_MDN | off | read receipts: off, internal (inside the organisation only) or all |
MAIL_MX_NODES | — | the mx nodes outbound mail is relayed to: comma-separated host:port, round-robin |
MAIL_QUARANTINE_DAYS | 30 | how many days a held message (the filters' and the anti-virus's quarantine) is kept, then deleted without notice (30; 0 keeps it until an administrator decides) |
MAIL_RECALL_ALLOW_READ | false | allow recalling a copy that has already been read |
MAIL_RECALL_MAX_AGE | 0 | how old a message may be to be recalled; 0 is no limit |
MAIL_S3_ACCESS_KEY 🔒 | — | the object store's access key |
MAIL_S3_BUCKET | simpletwo-mail | the bucket for mail |
MAIL_S3_ENDPOINTS | — | the object tier: primary=https://…;secondary=https://…; this version uses the primary. Empty means an in-memory tier, for development only |
MAIL_S3_PREFIX | — | a key prefix inside the bucket |
MAIL_S3_REGION | — | the object store's region |
MAIL_S3_SECRET 🔒 | — | the object store's secret |
MAIL_TLSRPT_ORG | — | the organisation's name in the TLS-RPT reports (MAIL_DMARC_ORG by default, and without it the first mail domain) |
MAIL_TLSRPT_REPORTS | true | whether the domains that ask for TLS-RPT reports (RFC 8460) with their _smtp._tls record get them: daily, one per domain, to its mailto: addresses (on; off sends none) |
MAIL_TLS_ADDR | :443 | the HTTPS listen address |
MAIL_TLS_HOST | — | the public name this service answers JMAP on. Set, HTTPS with a certificate it obtains itself, and the coordinator announces mail_url once the name answers |
MAIL_TOKEN_TTL | 12h | the lifetime of a session this service renews itself |
MX_ADDR | 127.0.0.1:8088 | the mx admin/health listen address; never public |
MX_ARC_TRUSTED | — | the ARC sealers (RFC 8617) local policy trusts — comma-separated domains, each with its subdomains (lists.example,google.com); empty (the default) trusts none. A message that fails DMARC under p=reject or p=quarantine is delivered as any other only when its ARC chain verifies, every sealer from the newest down to the one that vouches is listed, and the one that vouches saw DMARC pass for this From:; Authentication-Results says dmarc=fail with the reason, the aggregate report local_policy. The chain is verified and written down (arc=) and a group's copies to its members outside are sealed whatever this says |
MX_DEVICES | — | connectors for machines that cannot AUTH (scanners, ERP, monitoring), a JSON list: name, networks (CIDR or addresses), senders (addresses or a local @domain), relay (local/any), rate (messages a minute per source address, 60), max_size, tls (optional/require); recognised on the :25 door by the source network; a list that does not parse stops the service |
MX_DKIM_DIR | /etc/simpletwo/mx-dkim | the DKIM keys: <domain>/<selector>.pem, PKCS#8 or PKCS#1, owner-only; every key of the From: domain (or its parent) signs; none there and mail leaves unsigned, which /healthz shows |
MX_DNSBL_ZONES | zen.spamhaus.org | the DNS block lists (RFC 5782) every client of the :25 door is checked against, comma-separated (`zone=127.0.0.2 |
MX_DNSWL_ZONES | — | the DNS allow lists: an address they list is not checked against the block lists (empty for none) |
MX_FILTERS | — | the content filters on mail from the internet — a JSON list [{"name":"rspamd","transport":"milter","address":"inet:127.0.0.1:11332","on_error":"open"}, {"name":"av","transport":"icap","address":"icap://av:1344/respmod","on_error":"closed"}] (empty for none); the transport is milter or icap; on_error is open (the message goes on without the filter) or closed (the sender is told 451); timeout 30s, eom_timeout 300s; a milter's spam_signal is the header rules that file a message as Junk; an ICAP service's verdict is how the anti-virus says it found something (headers, header_patterns, on_modified_body, status_codes); on_virus is quarantine (ICAP's default: the message is held, the sender told 250) or reject (the sender told 550); a milter counts as an anti-virus only when on_virus is set; a list that does not parse stops the service |
MX_FILTERS_FILE | — | a file holding the same JSON list as MX_FILTERS; when set, MX_FILTERS is not read, and a file that cannot be read stops the service. It is how the agent hands the list over: a systemd environment does not carry regular expressions with a \ intact |
MX_FILTER_QUARANTINE | held | what a filter's quarantine does: held (the default) keeps the message on the mail cluster until an administrator decides (console, Mail → Quarantine), its recipients told by a notice; junk files it as Junk. An anti-virus's find is always held |
MX_HOSTNAME | — | the edge's name in EHLO and Received; the host's name by default |
MX_INBOUND_AUTH | on | SPF, DKIM and DMARC on mail from the internet (on by default; off for none): the results in Authentication-Results, a refusal at DATA under p=reject, Junk under p=quarantine |
MX_INTERNAL_TOKEN 🔒 | — | the token the mail cluster gave this edge |
MX_MAIL_API | — | the mail nodes' internal API bases, comma-separated; empty takes the MX_MAIL_NODES hosts on MX_MAIL_API_PORT |
MX_MAIL_API_PORT | 8087 | the mail role's HTTP port for the internal API when MX_MAIL_API is not set (8087) |
MX_MAIL_NODES | — | the mail cluster's nodes for LMTP and the internal API: comma-separated host:port |
MX_MAX_CONNECTIONS | 500 | how many :25 connections a node holds at once; the next is told 421 (0 for no bound) |
MX_MAX_CONNECTIONS_PER_IP | 20 | how many :25 connections one address may hold at once; the next is told 421 (0 for no bound); devices connectors' networks are not counted |
MX_MAX_MESSAGE_BYTES | 52428800 | the largest message the MX door takes, in bytes (50 MiB) |
MX_RELAY_ADDR | :2525 | the door for the mail cluster's outbound mail (:2525; off for none): answered only to the addresses in MX_MAIL_NODES, STARTTLS required when there is a certificate |
MX_SMARTHOST | — | where all outbound mail goes instead of the recipients' MX (comma-separated host:port); empty is MX delivery |
MX_SMTP_ADDR | :25 | the public MX door (:25); off listens on none |
MX_SPOOL_DIR | /var/lib/simpletwo/mx/spool | the on-disk queue's directory; owner-only — it is mail in flight, in the clear |
MX_SUBMISSION_MAX_CONNECTIONS | 0 | how many connections to the submission ports 587 and 465 together a node holds at once, counted apart from :25; the next is told 421 on 587 and closed on 465, and the refusal is in the submission refusal log (0, the default, for no bound); devices connectors' networks are not counted |
MX_SUBMISSION_MAX_CONNECTIONS_PER_IP | 0 | how many connections to 587 and 465 together one address may hold at once (an IPv6 client by its /64); the next is refused as under MX_SUBMISSION_MAX_CONNECTIONS (0, the default, for no bound). Everyone behind one NAT is one address: an office sending through one public address shares the bound |
MX_SUBMIT_ADDR | :587 | SMTP submission with STARTTLS and an app password (:587; off for none); not opened without MX_TLS_CERT: AUTH is never offered in the clear |
MX_SUBMIT_TLS_ADDR | :465 | SMTP submission with TLS from the first byte (:465; off for none); not opened without MX_TLS_CERT |
MX_TLS_CERT | — | the certificate for STARTTLS on :25; with MX_TLS_KEY |
MX_TLS_KEY | — | the key to MX_TLS_CERT |
6. The drive — the drive role
| Variable | Default | What it does |
|---|---|---|
DRIVE_ACME_CACHE | /var/lib/simpletwo/drive-acme | where the obtained certificate is kept |
DRIVE_ACME_EMAIL | — | the Let's Encrypt contact |
DRIVE_ACME_STAGING | 0 | 1 — Let's Encrypt's staging directory |
DRIVE_ADDR | :8088 | the address and port the service accepts clients on |
DRIVE_BIN_DAYS | 30 | how many days a deleted file waits in the bin before the sweeper takes its bytes. The same number mail keeps. 0 means there is effectively no bin: the next sweep takes everything |
DRIVE_COORDINATOR_CERT_SHA256 | — | the SHA-256 fingerprint of the coordinator's self-signed certificate |
DRIVE_COORDINATOR_INSECURE | — | trust the self-signed certificate of the coordinator the drive asks for permissions in identity mode; only together with DRIVE_COORDINATOR_CERT_SHA256 |
DRIVE_DB_DSN | — | the Postgres connection string. Required: the drive has no in-memory mode, because a drive that loses its tree on restart is not a degraded mode |
DRIVE_DEFAULT_QUOTA_GB | 0 | the default space quota, in gigabytes; 0 means no ceiling. A space's own quota is set in the console and overrides this |
DRIVE_IDENTITY_URL | — | identity's private address: the provisioning feed that gives everybody who has the drive a personal home. Empty and nobody is provisioned, which /healthz says outright |
DRIVE_MAIL_URL | — | a mail node's private address: the leavers' retention policy, the person responsible for the leaver's mailbox and the warning letter — the same as for the mailbox. Empty and a leaver's home is kept, read-only, for ever |
DRIVE_MAX_UPLOAD_MB | 2048 | the ceiling on one file, in megabytes. Checked when an upload is opened, not after the bytes have crossed the network |
DRIVE_PUBLIC_URL | — | the drive's public address — the name clients reach it by |
DRIVE_S3_ACCESS_KEY | — | the object store's access key |
DRIVE_S3_BUCKET | simpletwo-drive | the drive's bucket; simpletwo-drive by default |
DRIVE_S3_ENDPOINT | — | the object store's address. Empty and the service still runs, holding only files small enough for a catalogue row, and says so in /healthz |
DRIVE_S3_REGION | us-east-1 | the object store's region |
DRIVE_S3_SECRET | — | the object store's secret |
DRIVE_TLS_ADDR | :443 | the HTTPS listener's address (default :443) |
DRIVE_TLS_HOST | — | the drive's public name, and the name in the certificate it obtains itself |
🔒 — the value is secret: do not print it into logs and do not pass it around in messages. Secrets that can be given as a file should be given as a file: a value in the environment is visible to anybody who can read the process's state.
6. Shared across the deployment
ST_TOKEN_SECRET and ST_MESSAGING_URL are read by several services: the first because tokens are
signed by the coordinator and verified by the others, the second because the calendar tells the
messaging service about changes. Their values are the same across the installation — otherwise the
services simply do not believe each other.
The platform token's variables are the same in every service with a person's door —
messaging, calendars, mail, identity; the shared library shared/auth reads them, and the agent
takes the values from the desired state. The coordinator does not read them: the switch is its own
setting. The service token's variables ST_SERVICE_* work the same way;
shared/svcauth reads them. ST_MOVE_STOPGAP_UNTIL reaches identity and the
calendar only — the services whose doors the migration tool calls.
| Variable | What it does |
|---|---|
ST_PLATFORM_TOKEN | which token the person doors take: hmac (the default) is the coordinator's session token, as before; identity is identity's platform token only, and a person's HMAC session is refused. Anything else and the service does not start. One switch for the whole installation, set at the coordinator (PUT /admin/api/settings/platform-token) |
ST_PLATFORM_ISSUER | the platform token's issuer — identity's public address, exactly as it writes iss |
ST_PLATFORM_JWKS_URL | identity's keys (JWKS) on its private address; an unknown kid refetches them at most every 30 seconds |
ST_PLATFORM_JWKS | the pinned JWKS copy (base64url): what verifies the token when identity is unreachable, a restart included. The coordinator fetches it from identity and refreshes it when the keys rotate |
ST_PLATFORM_REVOCATIONS_URL | identity's revocation feed (/v1/revocations/feed): ended sessions and disabled accounts, read every 30 seconds with a service token |
ST_PLATFORM_RBAC_URL | the coordinator's RBAC projection (/v1/service/rbac): in identity mode a person's roles and permissions come from here, never from the token; read every 15 seconds, the last good copy answers through a failure |
ST_PLATFORM_REVOCATIONS_STALE | the revocation feed's staleness bound in identity mode: once the feed has not been read for longer, tokens issued before the bound are refused and /healthz says revocations.stale: true; a duration from 1m to 24h, off, or empty for 10 minutes. Set in the coordinator's console (Settings → Directory, the "Moving the services to the directory" card) |
ST_SERVICE_TOKEN | which token the services call each other with: hmac (the default) is the token under the shared ST_TOKEN_SECRET, as before; keys is only a token signed with the calling host's own key, and a service token under the shared secret is refused. Anything else and the service does not start. One switch for the whole installation, set at the coordinator (PUT /admin/api/settings/service-token) |
ST_SERVICE_KEY_DIR | the directory of this host's key (current.json, next.json, previous.json) and of the pinned copy of every service's keys (registry.json). The agent generates and rotates the key on the host itself and registers it with the coordinator; the key never leaves the host. In keys mode the service does not start without it |
ST_SERVICE_KEYS_URL | the coordinator's registry of service keys (/v1/service/keys/jwks): an unknown kid refetches it at most every 30 seconds; while the coordinator is unreachable the pinned copy verifies |
ST_MOVE_STOPGAP_UNTIL | the instant (RFC 3339) until which identity and the calendar take a wave-less svc:move service token — the migration tool's stopgap. Empty or absent: shut (the default); the instant passed by the service's own clock: shut; more than 72 hours ahead or unreadable: shut; in the service token's keys mode: always shut. Every use is audited on the service with reason stopgap and counted in /healthz (move.stopgap). Set by an owner in the coordinator's console (Settings → Migration tool: svc:move stopgap, PUT /admin/api/settings/move-stopgap) with a required reason |