mailu: full multi-container stack from the live novox deployment

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
This commit is contained in:
2026-09-09 01:04:55 +02:00
parent 8f99b5bd42
commit b3309a0ce9
2 changed files with 134 additions and 41 deletions
+121 -37
View File
@@ -4,6 +4,20 @@
"capabilities": [
"container-runtime"
],
"requires": [
"postgres-database"
],
"contributes": {
"postgres-database": {
"name": "mailu"
}
},
"binds": {
"postgres-database": "/var/lib/mailu/database.json"
},
"secrets": {
"postgres-database": "/var/lib/mailu/database.secret"
},
"emits": [
"module.mailu.user.created",
"module.mailu.user.deleted",
@@ -43,13 +57,13 @@
"port": 7080,
"protocol": "tcp",
"from": "mesh",
"why": "the web interface, behind a proxy"
"why": "the web interface (admin, webmail, admin API), behind the route proxy"
}
],
"own-secrets": {
"secret-key": "/var/lib/mailu/secret-key.secret",
"database": "/var/lib/mailu/database.secret",
"admin": "/var/lib/mailu/admin.secret",
"api-token": "/var/lib/mailu/api-token.secret",
"broker": "/var/lib/mesh/mailu/broker"
},
"resources": [
@@ -65,6 +79,13 @@
"path": "/var/lib/mailu",
"mode": "0700"
},
{
"id": "config-env",
"type": "file",
"path": "/var/lib/mailu/mailu.env",
"mode": "0644",
"content": "DOMAIN=novox.be\nHOSTNAMES=mail.novox.be\nPOSTMASTER=admin\nSITENAME=Novox\nWEBSITE=https://novox.be\nTLS_FLAVOR=cert\nSUBNET=192.168.203.0/24\nCOMPOSE_PROJECT_NAME=mailu\nHOST_ADMIN=mailu-admin\nHOST_ANTISPAM=mailu-antispam:11332\nHOST_IMAP=mailu-imap\nHOST_SMTP=mailu-smtp\nHOST_WEBMAIL=mailu-webmail\nHOST_WEBDAV=mailu-webdav:5232\nHOST_REDIS=mailu-redis\nHOST_FRONT=mailu-front\nREDIS_ADDRESS=mailu-redis\nANTIVIRUS=clamav\nWEBMAIL=roundcube\nWEBDAV=radicale\nFETCHMAIL_ENABLED=True\nFETCHMAIL_DELAY=600\nADMIN=true\nWEB_ADMIN=/admin\nWEB_WEBMAIL=/webmail\nWEBROOT_REDIRECT=/webmail\nWEBMAIL_ADDRESS=webmail\nAPI=true\nWEB_API=/api\nAUTH_RATELIMIT_IP=6000/hour\nAUTH_RATELIMIT_USER=1000/day\nCREDENTIAL_ROUNDS=12\nPASSWORD_SCHEME=PBKDF2\nDISABLE_STATISTICS=True\nMESSAGE_SIZE_LIMIT=50000000\nMESSAGE_RATELIMIT=200/day\nRECIPIENT_DELIMITER=+\nPOSTFIX_MYNETWORKS=127.0.0.0/8 [::1]/128\nRELAYNETS=\nRELAYHOST=\nREJECT_UNLISTED_RECIPIENT=\nDB_FLAVOR=postgresql\nINITIAL_ADMIN_ACCOUNT=admin\nINITIAL_ADMIN_DOMAIN=novox.be\nINITIAL_ADMIN_MODE=ifmissing\nSMTP_PORT=25\nSMTPS_PORT=465\nSUBMISSION_PORT=587\nPOP3_PORT=110\nPOP3S_PORT=995\nIMAP_PORT=143\nIMAPS_PORT=993\nHTTP_PORT=7080\nHTTPS_PORT=7443\nAUTOMX_PORT=4243\nAMX_SMTP_ADDRESS=mail.novox.be\nAMX_SMTP_PORT=587\nAMX_IMAP_ADDRESS=mail.novox.be\nAMX_IMAP_PORT=143\nAMX_MAIL_DOMAINS=novox.be\nDMARC_RUA=admin\nDMARC_RUF=admin\nLETSENCRYPT_SHORTCHAIN=True\nTZ=Etc/UTC\nLOG_LEVEL=INFO\nWELCOME=false\nREAL_IP_HEADER=X-Real-IP\nREAL_IP_FROM=\nCOMPRESSION=\nCOMPRESS_LEVEL=\nCOMPRESSION_LEVEL=\nBIND_ADDRESS4=127.0.0.1\nBIND_ADDRESS6=::1\nMAILU_VERSION=1.9\nDOCKER_ORG=mailu\nDOCKER_PREFIX=\n"
},
{
"id": "secret-env",
"type": "file",
@@ -77,14 +98,14 @@
"type": "file",
"path": "/var/lib/mailu/database.env",
"mode": "0600",
"content": "POSTGRES_USER=mailu\nPOSTGRES_DB=mailu\nPOSTGRES_PASSWORD=${secret:database}\nDB_USER=mailu\nDB_NAME=mailu\nDB_PW=${secret:database}\n"
"content": "DB_FLAVOR=postgresql\nDB_HOST=${bound:postgres-database:at}:${bound:postgres-database:port}\nDB_USER=${bound:postgres-database:as}\nDB_NAME=${bound:postgres-database:as}\nDB_PW=${secret:postgres-database}\n"
},
{
"id": "admin-env",
"type": "file",
"path": "/var/lib/mailu/admin.env",
"mode": "0600",
"content": "INITIAL_ADMIN_PW=${secret:admin}\n"
"content": "INITIAL_ADMIN_PW=${secret:admin}\nAPI_TOKEN=${secret:api-token}\n"
},
{
"id": "data-certs",
@@ -104,12 +125,6 @@
"path": "/services/mailu/data/dkim",
"mode": "0700"
},
{
"id": "data-filter",
"type": "directory",
"path": "/services/mailu/data/filter",
"mode": "0700"
},
{
"id": "data-mail",
"type": "directory",
@@ -122,6 +137,12 @@
"path": "/services/mailu/data/mailqueue",
"mode": "0700"
},
{
"id": "data-filter",
"type": "directory",
"path": "/services/mailu/data/filter",
"mode": "0700"
},
{
"id": "data-redis",
"type": "directory",
@@ -135,21 +156,45 @@
"mode": "0700"
},
{
"id": "data-dovecot",
"id": "data-dav",
"type": "directory",
"path": "/services/mailu/data/overrides/dovecot",
"path": "/services/mailu/data/dav",
"mode": "0700"
},
{
"id": "data-nginx",
"id": "data-fetchmail",
"type": "directory",
"path": "/services/mailu/data/data/fetchmail",
"mode": "0700"
},
{
"id": "data-overrides-nginx",
"type": "directory",
"path": "/services/mailu/data/overrides/nginx",
"mode": "0700"
},
{
"id": "data-pgdata",
"id": "data-overrides-dovecot",
"type": "directory",
"path": "/services/mailu/data/data/psql_admindb/pgdata",
"path": "/services/mailu/data/overrides/dovecot",
"mode": "0700"
},
{
"id": "data-overrides-postfix",
"type": "directory",
"path": "/services/mailu/data/overrides/postfix",
"mode": "0700"
},
{
"id": "data-overrides-rspamd",
"type": "directory",
"path": "/services/mailu/data/overrides/rspamd",
"mode": "0700"
},
{
"id": "data-overrides-roundcube",
"type": "directory",
"path": "/services/mailu/data/overrides/roundcube",
"mode": "0700"
},
{
@@ -164,6 +209,7 @@
"image": "ghcr.io/mailu/unbound@sha256:142aaad82ad1b0d5b59a5f1303778dba61a3e0a540f5d969c48862bcc99f6f5d",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
]
},
@@ -177,22 +223,6 @@
"/services/mailu/data/redis:/data"
]
},
{
"id": "admindb",
"type": "container",
"name": "mailu-admindb",
"image": "postgres@sha256:7456ef82e5f5bc43d997f4781bbd7c0d6389bff397564649a356e206ba473aee",
"network": "mailu",
"env": {
"PGDATA": "/var/lib/postgresql/data/pgdata"
},
"env-file": [
"/var/lib/mailu/database.env"
],
"volumes": [
"/services/mailu/data/data/psql_admindb/pgdata:/var/lib/postgresql/data/pgdata"
]
},
{
"id": "admin",
"type": "container",
@@ -200,6 +230,7 @@
"image": "ghcr.io/mailu/admin@sha256:dcac20e9cbdad560faef9653b1b5ac0d9266f4098dc00f0e7f0d35f4e70ed8f1",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env",
"/var/lib/mailu/database.env",
"/var/lib/mailu/admin.env"
@@ -216,6 +247,7 @@
"image": "ghcr.io/mailu/dovecot@sha256:46d18ba51032be8ebd6841aa49c1ef8762c729038c5fd86a081b5b884d478af9",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"volumes": [
@@ -230,10 +262,12 @@
"image": "ghcr.io/mailu/postfix@sha256:bbf882880f68849511710b35237a933f3fe80c4b28bf48ff20205dbd1f1433d7",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"volumes": [
"/services/mailu/data/mailqueue:/queue"
"/services/mailu/data/mailqueue:/queue",
"/services/mailu/data/overrides/postfix:/overrides:ro"
]
},
{
@@ -243,23 +277,69 @@
"image": "ghcr.io/mailu/rspamd@sha256:e87ab93dd252cc69499caa5317dd10d445fd4291a7ecf6bca09793c7d475a0c8",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"volumes": [
"/services/mailu/data/filter:/var/lib/rspamd"
"/services/mailu/data/filter:/var/lib/rspamd",
"/services/mailu/data/overrides/rspamd:/etc/rspamd/override.d:ro"
]
},
{
"id": "antivirus",
"type": "container",
"name": "mailu-antivirus",
"image": "ghcr.io/mailu/clamav@sha256:01d30483e4a8a20a54566addb1f9b00ebb51e8a103f9226602379c412cf5fb62",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"volumes": [
"/services/mailu/data/filter:/data"
]
},
{
"id": "webmail",
"type": "container",
"name": "mailu-webmail",
"image": "ghcr.io/mailu/webmail@sha256:076b720fc766e58a97321cdb700e887c2008d6d323685fe59f323088333059dc",
"image": "ghcr.io/mailu/roundcube@sha256:076b720fc766e58a97321cdb700e887c2008d6d323685fe59f323088333059dc",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"volumes": [
"/services/mailu/data/webmail:/data"
"/services/mailu/data/webmail:/data",
"/services/mailu/data/overrides/roundcube:/overrides:ro"
]
},
{
"id": "webdav",
"type": "container",
"name": "mailu-webdav",
"image": "ghcr.io/mailu/radicale@sha256:e13cbad3791c0a6841b5d387e57e49a117808dcef87b8c9969f671ae9c3b67c0",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"volumes": [
"/services/mailu/data/dav:/data"
]
},
{
"id": "fetchmail",
"type": "container",
"name": "mailu-fetchmail",
"image": "ghcr.io/mailu/fetchmail@sha256:7dcd1392882925d612ab2d0230d437f0c660989d572283c48b0d0f2d491adce7",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"volumes": [
"/services/mailu/data/data/fetchmail:/data"
]
},
{
@@ -269,6 +349,7 @@
"image": "ghcr.io/mailu/nginx@sha256:09f28ab6d36367fcacc7994f7021f132ac845bdc05f04bf80906102d11aaa057",
"network": "mailu",
"env-file": [
"/var/lib/mailu/mailu.env",
"/var/lib/mailu/secret.env"
],
"ports": [
@@ -296,15 +377,18 @@
"type": "container",
"name": "mesh-mailu",
"image": "mesh-runtime-mailu@sha256:0000000000000000000000000000000000000000000000000000000000000000",
"network": "host",
"network": "mailu",
"volumes": [
"/var/lib/mesh/mailu/broker:/run/secrets/broker:ro",
"/var/lib/mailu/api-token.secret:/run/secrets/api-token:ro",
"/var/lib/mesh/mailu/config.json:/run/config/config.json:ro",
"/var/run/docker.sock:/var/run/docker.sock"
],
"env": {
"MESH_BROKER_FILE": "/run/secrets/broker",
"MESH_MAILU_URL": "http://127.0.0.1:80",
"MESH_MAILU_URL": "http://mailu-admin/api/v1",
"MESH_MAILU_API_KEY_FILE": "/run/secrets/api-token",
"MESH_MAILU_IMAP_CONTAINER": "mailu-imap",
"MESH_MAILU_CONFIG_FILE": "/run/config/config.json"
},
"restart-on": [