ALTER USER ... WITH LOGIN runs only when the user's SID is not the
login's, so an already-mapped user is left alone. The provisioner
enables a mailbox through its own method; the password tool an
operator uses keeps changing the password only.
create re-enables what holds refuses (mssql login, mosquitto client,
mailu mailbox, gitea user) and clears an expired postgres password, so
no disabled account loops. mssql and mongodb checks take the password
from the environment, never argv; mosquitto_ctrl failures no longer
repeat -P. mosquitto reads 'could not ask' as an error, not absence.
mailu checks existence and enabled only: its imap passdb cannot verify
a password. mssql checks the user's SID; gitea pages teams at 50.
holds() for postgres, mssql, mongodb, minio, lavinmq, mosquitto, mailu
and gitea, so the harness makes again a login the backend lost (hq issue
120). Each checks the mesh's password as the consumer presents it, or
compares it read-only, and returns false only when the backend says the
credential is absent or wrong; an unreachable backend throws.
Rebuild the mailu manifest from the running production deployment as the
source of truth, and finish wiring its tool runtime.
- Full 11-container topology: front, smtp, imap, admin, antispam, antivirus,
webmail, webdav, fetchmail, resolver, redis — real ghcr.io/mailu images
pinned by digest at tag 1.9 (clamav/radicale/fetchmail digests newly fetched).
- Admin DB now consumes the mesh postgres-database provider (requires +
contributes + binds + secrets, DB_* templated from ${bound}/${secret}),
replacing the bundled postgres:13 admindb the live stack still runs.
- Full config env from the live containers as a plain env-file; SECRET_KEY,
the admin API token and the initial-admin password become own-secrets;
DB password comes from the provider secret. No secret values hardcoded.
- Enable the Mailu admin REST API (API=true, WEB_API, API_TOKEN own-secret) so
the ported tools can reach it — the live deployment runs this API OFF.
- mesh-mailu tool runtime on the mailu network: admin API over the module
network, token from the mounted own-secret, docker.sock for the doveadm mail
reads, broker + mergeable config with restart-on.
- client.ts fromEnv reads the API token from its mounted own-secret file
(MESH_MAILU_API_KEY_FILE), matching the cloudflare-dns/umami pattern.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
Nineteen modules gain a broker-bound runtime container that serves the module's
tools under its own scoped account: bazarr, gitea, grafana, home-assistant,
icecast, influxdb, jackett, keycloak, mailu, nextcloud, nodered, nzbget, ombi,
photos, portainer, qbittorrent, searxng, tautulli, verdaccio.
Config is the assignment's, not the manifest's (ADR 0051): each client's fromEnv
overlays a settings-merged config file (MESH_<M>_CONFIG_FILE) over its env
fallbacks, so URL and credentials come from `settings set`, with the URL defaulting
to the server on the node. nextcloud and mailu also mount the docker socket for
their exec-based tools.
Proven in the mesh-lab: assigned-grafana green — settings deliver the URL and token,
the runtime reads the merged config and serves grafana's tools under the scoped
account, with nothing in the manifest. Two gaps this surfaced are filed as hq
issues 008 (a provider runtime's seal key) and 009 (a settings change does not
restart a container runtime).
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
Email server. Users/aliases/domains over the admin REST API, mail read via
doveadm in the imap container; ten tools. Emits user/alias created/deleted by
polling and diffing the admin API, so a change in the web admin is announced
as readily as one via a tool. Consumes nothing — deliberately, since mutating
mail accounts off another module's event could silently lose mail. Typechecks
against the sdk; manifest parses.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF