4 Commits

Author SHA1 Message Date
librelad
4fae22c89e fix(stalwart): use the official logo instead of my placeholder
Replaces the drawn shield-and-envelope stand-in with the real mark from
stalw.art (/favicon.svg), in their #DB2D54.

Padded from the source's 159.95x139.07 to a square 159.95 viewBox with
the art vertically centred, matching every other catalogue icon — all of
which are square, so a non-square box would letterbox in the app tiles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 04:23:33 +01:00
librelad
598f74c26b feat(updater): per-app upgrade verifiers — the safety half of stepping
Stepping 31 -> 32 -> 33 is arithmetic. Knowing 32 FINISHED before
touching 33 is the whole safety story, and it is invisible from outside
the app: Nextcloud runs its migration on boot and sits in maintenance
mode — or fails halfway — while Docker reports the container perfectly
healthy. Advance a rung there and a migration has been skipped on live
data.

Contract:  <app>_upgrade_verify <app> <expected-tag> <deadline>  -> 0

Returns 0 ONLY on positive confirmation that the app serves at the
expected version with nothing outstanding. Unhealthy, indeterminate and
timed-out all return non-zero — uncertainty is a failure, not a maybe,
because the alternative gambles with data.

  nextcloud  `occ status`: installed, NOT in maintenance, no pending DB
             upgrade, and the running major matches the tag. Maintenance
             mid-migration is expected and simply keeps waiting.
  mastodon   /health serving, ZERO "down" rows in db:migrate:status, and
             the version from /api/v1/instance matching. /health alone is
             insufficient — Puma answers before migrations finish.
  stalwart   /healthz/ready (per its documented probes), required to hold
             stable rather than flash once. Weaker by design: the probes
             confirm serving but report no version, and the file says so
             rather than implying more.

updaterVerifyGeneric (running + healthy + no restart during a settle
window) is the fallback for everything else, and is explicitly NOT
sufficient to justify climbing a rung — the engine will refuse to ladder
an app with no declared verifier.

9 tests drive the dangerous states directly: maintenance mode, pending DB
upgrade, and a wrong major all correctly REFUSE to verify; clean states
pass. Those three negatives are the ones that would have corrupted data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 23:58:23 +01:00
librelad
fd8ac29621 feat(stalwart): auto-update patches, VERSION config for release moves
Reverses the manual default from 4ee2529, which was over-cautious once
the tag pin is taken into account.

Two things were conflated. Auto-update does not reinstall anything: it
snapshots, `compose pull`, `up -d` — the container is recreated from the
new image and the data volume is untouched. And because the image is
pinned to v0.16, the updater compares the digest of THAT tag, so auto
can only ever apply rebuilds of 0.16 (security/bug patches). It cannot
jump to 0.17. That is the safe half of updating, and there is no good
reason to withhold it.

Adds CFG_STALWART_VERSION=v0.16, which drives the image tag through the
existing #LIBREPORTAL|STALWART_VERSION_TAG| sentinel (verified: setting
it to v0.17 rewrites the image line). Moving between releases is now a
config change a user can make from the app's config page — the roadmap's
config-first version identity, used for real.

Net behaviour: patches land unattended inside the update window; a
version jump stays a deliberate decision, which is what pre-1.0 software
with a settling storage schema warrants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 23:13:47 +01:00
librelad
4ee25292d5 feat(stalwart): add Stalwart Mail Server as a catalog app
One container providing SMTP/IMAP/POP3/JMAP plus CalDAV/CardDAV, an admin
UI and spam filtering — chosen over mailcow (owns its own installer, which
is what killed the earlier attempt now sitting in scripts/unused/) and
over Mailu (~7 containers) because a single image with a single data dir
is the only shape that fits the existing conventions cleanly: one anchor
service the updater can version, one path the backup engine can snapshot.

Mail-specific departures from the usual app template, each deliberate:

* Ports are FIXED, not random. Other mail servers connect to :25 by
  number and clients expect 465/587/993 — a randomised external port
  would silently make the server unreachable. Only the admin UI takes a
  random port, since that one really is just a browser behind Traefik.
  143/995/4190/443 ship disabled; the port processor comments them out.

* UPDATE_TYPE=manual and the image pinned to v0.16, not :latest.
  Stalwart is pre-1.0 and has said the storage schema is still being
  finalised, so an unattended minor bump could carry a data migration on
  the message store. This is the one app where the auto default is wrong.

* BACKUP_STRATEGY=stop-snapshot-start. The message store is written
  continuously; a live copy can land mid-transaction. Seconds of queued
  delivery (senders retry) buys a consistent snapshot.

* The install hook checks outbound port 25 and reverse DNS, then prints
  the MX/SPF/DMARC records with real values. A mail server whose
  container started is not a working mail server, and every remaining
  requirement lives at the registrar or the VPS provider.

Admin credentials are seeded via STALWART_RECOVERY_ADMIN from the app
config rather than left to Stalwart's first-run random password, which
would otherwise exist only in the container log.

Icon is a drawn placeholder, not the upstream trademark.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 21:02:26 +01:00