diff --git a/deploy/webmail.sh b/deploy/webmail.sh index a45ca96..6267cdb 100755 --- a/deploy/webmail.sh +++ b/deploy/webmail.sh @@ -328,6 +328,26 @@ cmd_doctor() { else grn " ${avail} MB free on ${DATA_ROOT}"; fi } + # A plugin shipped in the image but absent from ENABLED_PLUGINS never gets its + # tables created, and then fails at runtime rather than at install. The + # entrypoint's default is the list of what ships, so compare against that + # instead of duplicating it here. + local shipped missing="" + shipped="$(sed -n 's/^: "${ENABLED_PLUGINS:=\(.*\)}"/\1/p' "${ROOT_DIR}/docker/entrypoint.sh")" + if [ -n "${ENABLED_PLUGINS:-}" ] && [ -n "$shipped" ]; then + local p + for p in $shipped; do + case " ${ENABLED_PLUGINS} " in *" $p "*) ;; *) missing="${missing} $p" ;; esac + done + if [ -n "$missing" ]; then + ylw " ENABLED_PLUGINS is missing:${missing}" + echo " these ship in the image but will have no tables created." + echo " fix: add them to ENABLED_PLUGINS in ${ENV_FILE}, then: webmail.sh update" + else + grn " ENABLED_PLUGINS covers every plugin in the image" + fi + fi + if command -v ss >/dev/null 2>&1; then local p for p in "WEB_PORT:${WEB_PORT:-8088}" "ADMIN_PORT:${ADMIN_PORT:-8089}"; do @@ -479,6 +499,11 @@ cmd_update() { # then the API, then the web front end. IMAGE_TAG="$target" compose up -d worker-sync worker-jobs IMAGE_TAG="$target" compose up -d api web + # Converge anything the ordered roll does not name — a release that adds a + # service (the antispam engine did) would otherwise never start it, and the + # stack would look updated while missing a container. --no-recreate so this + # only creates what is absent and leaves the rolled services alone. + IMAGE_TAG="$target" compose up -d --no-recreate echo "== 5/5 health gate (${HEALTH_TIMEOUT}s)" if health_gate; then diff --git a/docker/.env.prod.example b/docker/.env.prod.example index 06ab4dd..0fc22e2 100644 --- a/docker/.env.prod.example +++ b/docker/.env.prod.example @@ -97,7 +97,7 @@ OAUTH_MS_CLIENT_SECRET= # Plugins whose schema is migrated on every start. Remove one to stop shipping # its tables; `hello` is the SDK demo and is intentionally not listed. -ENABLED_PLUGINS="calendar contacts labels filters" +ENABLED_PLUGINS="calendar contacts labels filters antispam" # --- Backups ---------------------------------------------------------------- BACKUP_AT=03:30 diff --git a/docs/nas-install.md b/docs/nas-install.md index 79d66c2..99fcee2 100644 --- a/docs/nas-install.md +++ b/docs/nas-install.md @@ -556,12 +556,30 @@ Steady state for one or two mail accounts, `SEARCH_BACKEND=pg`: | worker-sync | 120-250 MB | `WORKER_SYNC_MEM_LIMIT=512m` | | worker-jobs | 150-350 MB | `WORKER_JOBS_MEM_LIMIT=640m` | | redis / web / backup | < 100 MB total | — | -| **total** | **~0.7-1.2 GB** | ~2.2 GB of limits | +| rspamd + its redis | 150-300 MB | `RSPAMD_MEM_LIMIT=256m`, `RSPAMD_REDIS_MEM_LIMIT=160m` | +| **total** | **~0.9-1.5 GB** | ~2.6 GB of limits | | meilisearch (optional) | 300-800 MB | `MEILI_MEM_LIMIT=1g` | On a 2 GB NAS: stay on `SEARCH_BACKEND=pg` and drop `PG_SHARED_BUFFERS` to `128MB`. Tight but workable. On 4 GB+ you have room for Meilisearch. +### The anti-spam engine + +`rspamd` and `redis-rspamd` start with everything else — spam filtering is part +of the stack, not an add-on. Two things follow from that: + +- **Nothing waits for them.** No service declares `depends_on: rspamd`, so an + engine that is down, slow or restarting cannot delay mail. Classification is + best-effort; delivery is not. The health gate ignores it for the same reason. +- **`webmail.sh init` generates `RSPAMD_CONTROLLER_PASSWORD`.** It is internal — + nobody types it — but the engine will not start without it. Upgrading an + install that predates the engine means re-running `init`, which only fills in + values that are empty and never touches `MASTER_KEY`. + +Its Bayes database lives in `$DATA_ROOT/rspamd-redis`. Like the search index, +that is derived data: it rides along in backups but never needs restoring, and +retraining rebuilds it. + Two NAS-specific realities worth knowing: - **Disk hibernation won't happen.** Active accounts hold IMAP IDLE connections