Files
mesh-catalog/modules/mailu/module.json
jschoubben 06954b5a70 Merge main: the trunk's seat names, this branch's event names
Two lines of work renamed the same seats differently. The trunk named them for their
scope — node-scoped ones `node-*`, leaving `the-artifact-store`, `npm-package-registry`
and `git` as they were — and this branch had renamed ten of them to `mesh-*`. The trunk's
set is what the live controller loads and what the live seats were actually renamed to, so
a manifest claiming this branch's name is one the running mesh refuses. Three of them
needed reverting by hand: git had auto-merged this branch's names where the trunk had not
touched those lines, which is the quiet kind of merge result.

Event names are this branch's, because the trunk has not converted them and they are what
issue 127 was about.

Verdaccio goes with the trunk's removal of it. The template work on dnsmasq's roster fact
is the trunk's, sitting beside this branch's local event names in the same file — the one
hunk where both changes landed together.

75 manifests, all parsing, no claim outside the trunk's set and no event name left in the
old bus's form.
2026-09-27 18:25:57 +02:00

561 lines
16 KiB
JSON

{
"module": "mailu",
"version": "1",
"capabilities": [
"container-runtime"
],
"requires": [
"postgres-database",
"route",
"secret"
],
"contributes": {
"postgres-database": {
"name": "mailu"
},
"route": {
"web": {
"label": "mail",
"port": 7443,
"scheme": "https",
"insecure": true
},
"acme": {
"label": "mail",
"path": "/.well-known/acme-challenge",
"port": 7080,
"priority": 100
},
"autoconfig": {
"label": "autoconfig",
"port": 4243
},
"autodiscover": {
"label": "autodiscover",
"port": 4243
},
"automx": {
"label": "automx",
"port": 4243
}
}
},
"binds": {
"postgres-database": "${dir:state}/database.json",
"route": "${dir:state}/route.json"
},
"secrets": {
"postgres-database": "${dir:state}/database.secret",
"secret": {
"secret-key": "${dir:state}/secret-key.secret",
"admin": "${dir:state}/admin.secret",
"api-token": "${dir:state}/api-token.secret"
}
},
"emits": [
"user.created",
"user.deleted",
"alias.created",
"alias.deleted"
],
"listens": [
{
"port": 25,
"protocol": "tcp",
"from": "anywhere",
"why": "mail from other mail servers",
"fixed": true
},
{
"port": 110,
"protocol": "tcp",
"from": "anywhere",
"why": "POP3, kept at parity with the predecessor; pruning legacy protocols is its own deliberate change",
"fixed": true
},
{
"port": 143,
"protocol": "tcp",
"from": "anywhere",
"why": "IMAP with STARTTLS, kept at parity",
"fixed": true
},
{
"port": 465,
"protocol": "tcp",
"from": "anywhere",
"why": "submission over TLS",
"fixed": true
},
{
"port": 587,
"protocol": "tcp",
"from": "anywhere",
"why": "submission; also what the smtp provision serves consumers",
"fixed": true
},
{
"port": 993,
"protocol": "tcp",
"from": "anywhere",
"why": "IMAP over TLS",
"fixed": true
},
{
"port": 995,
"protocol": "tcp",
"from": "anywhere",
"why": "POP3 over TLS, kept at parity",
"fixed": true
},
{
"port": 7080,
"protocol": "tcp",
"from": "mesh",
"why": "the web front over http; only the ACME HTTP-01 passthrough is routed here \u2014 everything else 301s to https and would loop a proxy"
},
{
"port": 7443,
"protocol": "tcp",
"from": "mesh",
"why": "the web front over its own TLS (admin, webmail, API); the public name mail.novox.be is a route grant reaching it here"
},
{
"port": 4243,
"protocol": "tcp",
"from": "mesh",
"why": "automx: mail client autoconfiguration; autoconfig/autodiscover/automx.novox.be are route grants reaching it here"
}
],
"own-secrets": {
"broker": "/var/lib/mesh/mailu/broker"
},
"resources": [
{
"id": "mesh-state",
"type": "directory",
"path": "/var/lib/mesh/mailu",
"mode": "0700"
},
{
"id": "state",
"type": "directory",
"mode": "0700",
"place": "."
},
{
"id": "grants",
"type": "directory",
"mode": "0700"
},
{
"id": "data-automx",
"type": "directory",
"mode": "0700"
},
{
"id": "config-env",
"type": "file",
"path": "${dir:state}/mailu.env",
"mode": "0644",
"content": "ADMIN_ADDRESS=mailu-admin\nANTISPAM_ADDRESS=mailu-antispam\nANTIVIRUS_ADDRESS=mailu-antivirus\nIMAP_ADDRESS=mailu-imap\nSMTP_ADDRESS=mailu-smtp\nFRONT_ADDRESS=mailu-front\nWEBMAIL_ADDRESS=mailu-webmail\nWEBDAV_ADDRESS=mailu-webdav\nREDIS_ADDRESS=mailu-redis\nPORTS=25,80,443,465,993,995,4190,110,143,587\nDOMAIN=novox.be\nHOSTNAMES=mail.novox.be\nPOSTMASTER=admin\nSITENAME=Novox\nWEBSITE=https://novox.be\nTLS_FLAVOR=letsencrypt\nSUBNET=192.168.203.0/24\nCOMPOSE_PROJECT_NAME=mailu\nANTIVIRUS=clamav\nWEBMAIL=roundcube\nWEBDAV=radicale\nFETCHMAIL_ENABLED=True\nFETCHMAIL_DELAY=600\nADMIN=true\nWEB_ADMIN=/admin\nWEB_WEBMAIL=/webmail\nWEBROOT_REDIRECT=/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=142.132.152.141\nCOMPRESSION=\nCOMPRESS_LEVEL=\nCOMPRESSION_LEVEL=\nBIND_ADDRESS4=127.0.0.1\nBIND_ADDRESS6=::1\nMAILU_VERSION=1.9\nDOCKER_ORG=mailu\nDOCKER_PREFIX=\nWELCOME_SUBJECT=Welcome to your new email account\nWELCOME_BODY=Welcome to your new email account, if you can read this, then it is configured properly!\n"
},
{
"id": "secret-env",
"type": "file",
"path": "${dir:state}/secret.env",
"mode": "0600",
"content": "SECRET_KEY=${secret:secret-key}\n"
},
{
"id": "database-env",
"type": "file",
"path": "${dir:state}/database.env",
"mode": "0600",
"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": "${dir:state}/admin.env",
"mode": "0600",
"content": "INITIAL_ADMIN_PW=${secret:admin}\nAPI_TOKEN=${secret:api-token}\n"
},
{
"id": "data-certs",
"type": "directory",
"mode": "0700"
},
{
"id": "data-data",
"type": "directory",
"mode": "0700"
},
{
"id": "data-dkim",
"type": "directory",
"mode": "0700"
},
{
"id": "data-mail",
"type": "directory",
"mode": "0700"
},
{
"id": "data-mailqueue",
"type": "directory",
"mode": "0755"
},
{
"id": "data-filter",
"type": "directory",
"mode": "0700"
},
{
"id": "data-clamav",
"type": "directory",
"mode": "0700"
},
{
"id": "data-redis",
"type": "directory",
"mode": "0700"
},
{
"id": "data-webmail",
"type": "directory",
"mode": "0700"
},
{
"id": "data-dav",
"type": "directory",
"mode": "0700"
},
{
"id": "data-fetchmail",
"type": "directory",
"mode": "0700"
},
{
"id": "data-overrides-nginx",
"type": "directory",
"mode": "0700"
},
{
"id": "data-overrides-dovecot",
"type": "directory",
"mode": "0700"
},
{
"id": "data-overrides-postfix",
"type": "directory",
"mode": "0700"
},
{
"id": "data-overrides-rspamd",
"type": "directory",
"mode": "0700"
},
{
"id": "data-overrides-roundcube",
"type": "directory",
"mode": "0700"
},
{
"id": "net",
"type": "network",
"name": "mailu"
},
{
"id": "resolver",
"type": "container",
"name": "mailu-resolver",
"image": "ghcr.io/mailu/unbound@sha256:3a0fdfb364a63f4f9259526e013c1ef40f5f14de3621ce1560804b3a5909584a",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env",
"${dir:state}/secret.env"
],
"secrets-in-environment": "mailu-admin honours SECRET_KEY_FILE, DB_PW_FILE and API_TOKEN_FILE (configuration.py) but INITIAL_ADMIN_PW is env-only (start.py); the remaining containers' need for SECRET_KEY is unverified",
"ip": "192.168.203.254"
},
{
"id": "redis",
"type": "container",
"name": "mailu-redis",
"image": "redis@sha256:4bed291aa5efb9f0d77b76ff7d4ab71eee410962965d052552db1fb80576431d",
"network": "mailu",
"volumes": [
"${dir:data-redis}:/data"
]
},
{
"id": "admin",
"type": "container",
"name": "mailu-admin",
"image": "ghcr.io/mailu/admin@sha256:6dbfdadc4a9590dcb7652357b505200115b689b74008653bbf369e4599a3be5a",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env",
"${dir:state}/secret.env",
"${dir:state}/database.env",
"${dir:state}/admin.env"
],
"volumes": [
"${dir:data-data}:/data",
"${dir:data-dkim}:/dkim"
],
"secrets-in-environment": "mailu-admin honours SECRET_KEY_FILE, DB_PW_FILE and API_TOKEN_FILE (configuration.py) but INITIAL_ADMIN_PW is env-only (start.py); the remaining containers' need for SECRET_KEY is unverified",
"dns": [
"192.168.203.254"
]
},
{
"id": "imap",
"type": "container",
"name": "mailu-imap",
"image": "ghcr.io/mailu/dovecot@sha256:7f0ed5db996fbdc00adc5c5e38a08492e04f7eb4a9fbd66a03aa9a28ddf23993",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env"
],
"volumes": [
"${dir:data-mail}:/mail",
"${dir:data-overrides-dovecot}:/overrides:ro"
],
"dns": [
"192.168.203.254"
]
},
{
"id": "smtp",
"type": "container",
"name": "mailu-smtp",
"image": "ghcr.io/mailu/postfix@sha256:e2e49f39e53b80eac9e7a2f18d9df11edeb4914fd62dbba89b3155e8e034f62e",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env"
],
"volumes": [
"${dir:data-mailqueue}:/queue",
"${dir:data-overrides-postfix}:/overrides:ro"
],
"dns": [
"192.168.203.254"
]
},
{
"id": "antispam",
"type": "container",
"name": "mailu-antispam",
"image": "ghcr.io/mailu/rspamd@sha256:ff3666d8a61f17d309c5c6f6bcf4d40470b82299ca706ac650301175bb1a079d",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env"
],
"volumes": [
"${dir:data-filter}:/var/lib/rspamd",
"${dir:data-overrides-rspamd}:/etc/rspamd/override.d:ro"
],
"dns": [
"192.168.203.254"
]
},
{
"id": "antivirus",
"type": "container",
"name": "mailu-antivirus",
"image": "clamav/clamav-debian@sha256:b12ef8fefddbba7d88de59bea8a32622f365339154adf02d38fd089112e6745a",
"network": "mailu",
"volumes": [
"${dir:data-clamav}:/var/lib/clamav"
],
"dns": [
"192.168.203.254"
]
},
{
"id": "webmail",
"type": "container",
"name": "mailu-webmail",
"image": "ghcr.io/mailu/webmail@sha256:bdbee44cdb05a4658f0e3b62cc448de55ca8f8aea172279fda594826144c04f6",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env",
"${dir:state}/secret.env"
],
"volumes": [
"${dir:data-webmail}:/data",
"${dir:data-overrides-roundcube}:/overrides:ro"
],
"secrets-in-environment": "mailu-admin honours SECRET_KEY_FILE, DB_PW_FILE and API_TOKEN_FILE (configuration.py) but INITIAL_ADMIN_PW is env-only (start.py); the remaining containers' need for SECRET_KEY is unverified",
"dns": [
"192.168.203.254"
]
},
{
"id": "webdav",
"type": "container",
"name": "mailu-webdav",
"image": "ghcr.io/mailu/radicale@sha256:690ed6edf189dfef100a5a8b37c195ebf5d9241ac5f23f2f44b8b7b75726e3de",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env",
"${dir:state}/secret.env"
],
"volumes": [
"${dir:data-dav}:/data"
],
"secrets-in-environment": "mailu-admin honours SECRET_KEY_FILE, DB_PW_FILE and API_TOKEN_FILE (configuration.py) but INITIAL_ADMIN_PW is env-only (start.py); the remaining containers' need for SECRET_KEY is unverified",
"dns": [
"192.168.203.254"
]
},
{
"id": "fetchmail",
"type": "container",
"name": "mailu-fetchmail",
"image": "ghcr.io/mailu/fetchmail@sha256:f881c8412d3bbe73d638469b48321558d6403a9d45bfa043c1e52c752103d42d",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env",
"${dir:state}/secret.env"
],
"volumes": [
"${dir:data-fetchmail}:/data"
],
"secrets-in-environment": "mailu-admin honours SECRET_KEY_FILE, DB_PW_FILE and API_TOKEN_FILE (configuration.py) but INITIAL_ADMIN_PW is env-only (start.py); the remaining containers' need for SECRET_KEY is unverified",
"dns": [
"192.168.203.254"
]
},
{
"id": "front",
"type": "container",
"name": "mailu-front",
"image": "ghcr.io/mailu/nginx@sha256:36f98897cd1bc9d27628bbb4e04bdf60147af2ec7507d6da77f002c4f256896d",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env"
],
"ports": [
"25",
"110",
"143",
"465",
"587",
"993",
"995",
"7080:80",
"7443:443"
],
"volumes": [
"${dir:data-certs}:/certs",
"${dir:data-overrides-nginx}:/overrides:ro"
],
"dns": [
"192.168.203.254"
]
},
{
"id": "runtime-config",
"type": "file",
"path": "/var/lib/mesh/mailu/config.json",
"mode": "0600",
"content": "{}\n",
"merge": "json"
},
{
"id": "runtime",
"type": "container",
"name": "mesh-mailu",
"network": "mailu",
"volumes": [
"/var/lib/mesh/mailu/broker:/run/secrets/broker:ro",
"${dir:state}/api-token.secret:/run/secrets/api-token:ro",
"${dir:grants}:${dir:grants}: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://mailu-admin:8080/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",
"MESH_MAILU_DOMAIN": "novox.be",
"MESH_RECEIVES": "${dir:grants}/mesh.json"
},
"restart-on": [
"runtime-config"
],
"artifact": "runtime"
},
{
"id": "automx",
"type": "container",
"name": "mailu-automx",
"artifact": "automx",
"network": "mailu",
"env-file": [
"${dir:state}/mailu.env"
],
"ports": [
"4243"
],
"volumes": [
"${dir:data-automx}:/data"
]
}
],
"build": {
"on": [
{
"arg": "BUILD_BASE",
"module": "mesh-tools",
"artifact": "build"
},
{
"arg": "RUNTIME_BASE",
"module": "mesh-tools",
"artifact": "runtime"
},
{
"arg": "PYTHON_BASE",
"image": "python@sha256:25f3cfeaceca14921366af4d1240b56457ef46273bdb508c7b0e8f469f6fd228"
}
],
"artifacts": [
{
"name": "runtime",
"kind": "image",
"from": "Dockerfile"
},
{
"name": "automx",
"kind": "image",
"from": "automx/Dockerfile"
}
]
},
"provides": [
{
"name": "smtp",
"scope": "mesh"
}
],
"serves": {
"smtp": {
"port": 587,
"domain": "novox.be",
"name": "mail.novox.be"
}
},
"receives": {
"smtp": "${dir:grants}/mesh.json"
},
"grants": {
"smtp": "${dir:grants}"
}
}