Deploy: a release that adds a service must actually start it

Three things stood between v1.1.0 and a working anti-spam engine on an existing
install, each silent:

`update` rolled only worker-sync, worker-jobs, api and web. A release that adds
a service — this one adds two — would report success while never creating it.
It now converges the rest with `up -d --no-recreate`, which creates what is
absent and leaves the rolled services alone.

.env.prod.example pinned ENABLED_PLUGINS to the four plugins that existed when it
was written. The entrypoint's default includes antispam, but an explicit value in
.env wins, so migrate would skip the plugin's tables and every antispam request
would fail at runtime rather than at install. The template is fixed for new
installs; `doctor` now warns existing ones, comparing .env against the
entrypoint's default so there is still one list, not two.

And the runbook now documents what the engine costs (~150-300 MB), that nothing
depends on it by design, that `init` must be re-run for the controller password,
and that its Bayes database is derived data like the search index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SqJYt7jD7oUEkoY68sBZo
This commit is contained in:
2026-09-17 20:44:25 +02:00
co-authored by Claude Opus 5
parent 5aa133e18c
commit b54ccbe653
3 changed files with 45 additions and 2 deletions
+25
View File
@@ -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