One ihasmail in front of several Stalwarts, from #238. STALWART_URL stays required and stays the default, so an installation that sets nothing behaves exactly as it always has -- the mapping only adds domains that go elsewhere. An unlisted domain goes to the default. So does a bare username, which Stalwart accepts and which has no domain to map at all. A listed domain never falls back. If its server is unreachable that sign-in fails rather than retrying against the default, because falling back would authenticate somebody against a server their domain was deliberately routed away from -- and if the same account name existed there, they would land in another tenant's mailbox. The fallback is a decision about unmapped domains, taken before any network call, not a recovery path. Smaller than it sounds because only four places read config.stalwartUrl, all in upstream.ts. The upstream session now records which server issued it, since the relative URLs inside it only mean anything against that server, and every route already holding a session gets the right upstream without a second lookup. The client is untouched: it talks to one proxy and never learns there is more than one server behind it, which is exactly why this is small and several-servers-at-once is not. The upstream is derived from the username rather than stored on the session, so a mapping change takes effect on restart instead of being frozen into sessions that outlive it. Validated at boot the way the settings policy is: malformed JSON, a duplicate domain once normalised, a missing file or a value that is not an http(s) URL all stop the server. Domains are lower-cased and stripped of a trailing dot, because that is how one arrives off a username and comparing them any other way means a mapping that silently never matches. The servers themselves are not contacted -- a mapping is a routing table, not a health check, and one customer's outage must not stop ihasmail starting for the other four. Eight tests on the routing, two on the shipped example, and the four refusals checked by hand against a real config load.
110 lines
4.6 KiB
Bash
110 lines
4.6 KiB
Bash
# ---- ihasmail server configuration ----
|
|
|
|
# Base URL of your Stalwart server (scheme + host, no path). ihasmail discovers
|
|
# the JMAP session at <STALWART_URL>/.well-known/jmap.
|
|
STALWART_URL=https://mail.example.com
|
|
|
|
# Random secret used to derive encryption keys for persisted sessions.
|
|
# Generate with: openssl rand -base64 48
|
|
APP_SECRET=change-me
|
|
|
|
# Listen address
|
|
HOST=0.0.0.0
|
|
PORT=8080
|
|
|
|
# Serve the app from a subpath instead of the domain root, for a reverse proxy
|
|
# that maps https://example.com/mail/ here. Leave it unset for the root, which
|
|
# is what every deployment gets unless it asks otherwise. "/mail", "mail" and
|
|
# "/mail/" all mean the same thing.
|
|
#
|
|
# The prefix must reach ihasmail intact -- do not strip it in the proxy -- and
|
|
# it has to be set for the *build* as well as the run: the web bundle writes
|
|
# its own asset URLs, so a build that does not know the prefix produces an app
|
|
# that cannot load itself under one. With Docker that means
|
|
# `--build-arg BASE_PATH=/mail` alongside `-e BASE_PATH=/mail`.
|
|
# BASE_PATH=/mail
|
|
|
|
# Set to "1" when running behind a TLS-terminating reverse proxy (trusts
|
|
# X-Forwarded-* and marks cookies Secure). Set to "0" for plain-HTTP dev.
|
|
TRUST_PROXY=1
|
|
# Peers whose X-Forwarded-* headers are believed. Unset means loopback and the
|
|
# private ranges, which covers a reverse proxy on the same host or Docker
|
|
# network. A request from anywhere else is attributed to its socket address,
|
|
# whatever the headers claim -- otherwise anyone could pick their own key for
|
|
# the login rate limiter.
|
|
# TRUSTED_PROXIES=10.0.0.0/8,192.168.1.5
|
|
SECURE_COOKIES=auto
|
|
|
|
# Session lifetime (idle timeout) in seconds. "Remember me" extends to SESSION_REMEMBER_TTL.
|
|
SESSION_TTL=43200
|
|
SESSION_REMEMBER_TTL=2592000
|
|
|
|
# Where to persist sessions so restarts don't log everyone out (optional).
|
|
# Leave it empty to hold sessions in memory only, which is what an immutable
|
|
# instance does -- see IMMUTABLE below.
|
|
SESSION_FILE=./data/sessions.json
|
|
|
|
# Assert that this instance is running as an immutable container: read-only
|
|
# root filesystem, no durable state of its own. It is checked rather than
|
|
# taken on trust -- the server refuses to start if SESSION_FILE is set, or if
|
|
# the filesystem it is installed on turns out to be writable. Off by default.
|
|
# Running one looks like:
|
|
# docker run --read-only --tmpfs /tmp -e IMMUTABLE=1 -e SESSION_FILE= ...
|
|
# The cost today is that a restart signs everyone out, since there is nowhere
|
|
# left to keep the sessions. Removing that cost is what the OAuth work is for.
|
|
# IMMUTABLE=1
|
|
|
|
# Upstream timeouts / limits
|
|
UPSTREAM_TIMEOUT=30000
|
|
MAX_UPLOAD_BYTES=52428800
|
|
|
|
# Remote-image privacy proxy (Gmail-style). Set to 0 to load remote images directly.
|
|
IMAGE_PROXY=1
|
|
|
|
# Branding
|
|
APP_NAME=ihasmail
|
|
|
|
# Where this instance's source can be had. ihasmail is AGPL-3.0-or-later, which
|
|
# asks whoever runs a modified version to offer *that* version's source -- so if
|
|
# you have patched it, point this at your own tree. Shown on the sign-in page
|
|
# and in Settings > About.
|
|
SOURCE_URL=https://github.com/Coffey-Labs/ihasmail
|
|
|
|
# ---- Settings this installation decides (all optional) ----
|
|
#
|
|
# Seed what a new account starts on, lock what nobody may change, and turn
|
|
# something on once for accounts that already exist. Setting none of these --
|
|
# the default -- behaves exactly as ihasmail always has.
|
|
#
|
|
# A file is easier once there are `changes` in it. See the shipped
|
|
# settings-policy.example.json, and mount it read-only:
|
|
#
|
|
# -v /srv/ihasmail/policy.json:/etc/ihasmail/policy.json:ro
|
|
#
|
|
# SETTINGS_POLICY_FILE=/etc/ihasmail/policy.json
|
|
#
|
|
# Or inline, which is what an immutable deployment with no volume wants. These
|
|
# are ignored entirely when SETTINGS_POLICY_FILE is set, so a file and a stray
|
|
# variable cannot half-apply between them.
|
|
#
|
|
# SETTINGS_DEFAULTS={"externalSenderBanner":true}
|
|
# SETTINGS_ENFORCED={"externalRecipientConfirm":true}
|
|
# SETTINGS_CHANGES=[{"version":"20260902084513","settings":{"externalSenderBanner":true}}]
|
|
#
|
|
# Read once at startup: editing a policy means restarting the container.
|
|
# Docs: https://docs.ihasmail.org/configure/#settings-your-installation-decides
|
|
|
|
# ---- Several Stalwart servers (optional) ----
|
|
#
|
|
# Choose the upstream by the domain someone signs in with. STALWART_URL above
|
|
# stays required and stays the default; this only adds domains that go
|
|
# elsewhere. See the shipped stalwart-servers.example.json, and mount it
|
|
# read-only:
|
|
#
|
|
# -v /srv/ihasmail/servers.json:/etc/ihasmail/servers.json:ro
|
|
#
|
|
# STALWART_SERVERS_FILE=/etc/ihasmail/servers.json
|
|
#
|
|
# An unlisted domain, or a username with no domain, goes to STALWART_URL. A
|
|
# listed domain never falls back. Read once at startup: editing means a restart.
|