GGA Designs Logo GGA DESIGNS

Porting Mail-in-a-Box v76 to Ubuntu 24.04

How we got from upstream Mail-in-a-Box v76 to a working custom fork on Ubuntu 24.04 LTS (noble) called MeetrMail.

Live and Operational
Upstream base Mail-in-a-Box v76 (git tag v76-base in the fork)
Fork branch noble-php83-py312-rspamd
Target Ubuntu 24.04 LTS, single VPS
Result Installs with one command; 88/88 sandbox self-test checks passing; live box sending and receiving with Gmail and Outlook

1. Why v76 does not install on 24.04

Upstream v76 supports Ubuntu 22.04 only. Running it on 24.04 fails in several independent places, any one of which stops the install:

Blocker Where What happens on 24.04
OS version gate setup/preflight.sh Refuses anything but 22.04
PHP 8.0 from ppa:ondrej/php setup/system.sh, setup/functions.sh Third-party PPA for an EOL PHP; 24.04 ships 8.3
PEP 668 setup/questions.sh, setup/management.sh noble's pip 24.0 refuses pip3 install into the system Python: error: externally-managed-environment
cryptography==37.0.2 setup/management.sh No wheel for Python 3.12; pip falls back to a source build needing an old Rust toolchain
SpamAssassin 4.0 setup/spamassassin.sh Major version jump; /etc/default/spamassassin no longer exists; spampd's embedding of SpamAssassin untested against 4.0
Hard-coded php8.0 paths nginx config, backup code, tools Every PHP page returns an error; backups abort
systemctl link setup/management.sh, setup/munin.sh systemd 255 refuses to enable the unit — management daemon never starts at boot

2. The approach

The goal was the smallest diff from v76 that runs correctly on 24.04 — not a rewrite. The plan was worked in phases:

Phase Work
1 Preflight gate, trivial package fixes, remove PPAs
2 Python daemon on 3.12 via uv
3 PHP 8.3, remove Z-Push
4 Fresh Nextcloud 33 install
5a Rspamd for spam + greylisting (OpenDKIM/OpenDMARC kept)
Sandbox mode, test harness, and fixes found by testing

Two decisions shaped everything else:

OpenDKIM and OpenDMARC stayed exactly as v76 had them. Rspamd can do DKIM signing, but moving it means rewriting key generation in management/dns_update.py and validation in management/status_checks.py. DKIM decides whether Gmail accepts your mail, so that change was deferred to a later, unhurried "Phase 5b". The successful Gmail/Outlook tests confirm this held up.

Every change was tested by actually installing it. A static read of the code found the obvious problems. A real install in an Ubuntu 24.04 container found eight more that would have stopped or silently broken a live box.

3. Operating system and packages

Preflight

setup/preflight.sh and setup/bootstrap.sh now accept exactly Ubuntu 24.04 — an exact match, not "24.04 or newer". A box should refuse to install on an untested release rather than half-work.

Third-party apt repositories

Repository v76 Fork Why
ppa:ondrej/php Yes (PHP 8.0) Removed noble ships PHP 8.3 with every module needed
ppa:duplicity-team/duplicity-release-git Yes Removed noble ships duplicity 2.1.4 in main
Rspamd project repo No Added Needed to replace SpamAssassin smoothly

The Rspamd repository is the only third-party source left. Its signing key is pinned by fingerprint and apt-pinned so it can supply only the rspamd package. Setting RSPAMD_PACKAGE_SOURCE=ubuntu uses Ubuntu's package instead and leaves no third-party repositories at all. The choice is saved in /etc/mailinabox.conf so a later re-run of setup can't silently switch.

Individual package changes

Package Change Reason
ntp Removed On noble it's a transitional package pulling in ntpsec, which fights systemd-timesyncd. MIAB never configured it.
libmagic1 → libmagic1t64 Renamed in noble's 64-bit time_t transition. The old name still resolves, but naming the real package is safer.
bind9-host, bind9-dnsutils Added to base packages v76 only installed host from the network checks, so a box that skipped them had no DNS tools.
python3 Installed at start tools/editconf.py and setup/migrate.py use python3 and run before anything else installs it.

Versions verified against the noble archive

Every package name was checked against the real noble package indexes before any code was changed. Highlights include Postfix jumping to 3.8.6, Dovecot to 2.3.21, and nsd to 4.8.0.

nsd 4.8: nsd jumped five minor versions. setup/dns.sh now runs nsd-checkconf on the generated config straight after install, so an unsupported directive fails immediately with a precise message rather than as a mysterious service failure later.

4. PHP: what broke and what we changed

The version change

PHP_VER=8.0PHP_VER=8.3 in setup/functions.sh. All required modules exist in noble's stock archive. Roundcube 1.6.15 supports PHP 8.3 without changes.

Hard-coded PHP 8.0 paths — the part that would have hurt

Changing the variable was not enough. v76 had 8.0 written directly into files that never read the variable, causing every PHP page to fail and backups to abort. Setup now records PHP_VERSION=8.3 in /etc/mailinabox.conf. A new helper reads it, and the nginx template uses a placeholder that is filled in when the web config is generated.

5. Python: what broke and what we changed

Problem 1 — PEP 668 & Problem 2 — the cryptography pin

Ubuntu 24.04's pip enforces PEP 668, preventing installation into the system Python. Furthermore, v76 pinned cryptography==37.0.2 which has no wheel for Python 3.12, causing pip to attempt a source build.

The fix: uv

uv installs its own CPython, so the management daemon never touches the system Python at all. The installer changed from pip install --upgrade to uv sync --frozen using a lockfile (management/uv.lock) to guarantee reproducibility.

Unpinning resolved to cryptography 50.0.1. All functions were tested directly (Loading certificate chains, validity checks, CSRs, TOTP two-factor codes, QR code generation) and passed.

6. Nextcloud

Nextcloud was updated from 26.0.13 to 33.0.9 to support PHP 8.3 and the updated user_external app. The upgrade ladder was removed since a fresh install doesn't need it.

Finding: Nextcloud login no longer needs PHP's IMAP extension, it uses cURL's built-in imap support, preparing the ground well for Ubuntu 26.04 which drops ext/imap from PHP core.

7. The spam system: SpamAssassin to Rspamd

Keeping SpamAssassin looked safer but wasn't. noble ships SpamAssassin 4.0.0, and the v76 config no longer exists. Rspamd with Postfix and Dovecot is a very widely used, well-documented setup.

v76 component Job Replaced by
spampd + spamassassin Spam scoring Rspamd, connected to Postfix as a milter
postgrey Greylisting Rspamd's greylist module
razor, pyzor Shared spam signatures Rspamd's fuzzy hashing and reputation
dovecot-antispam Learn from Spam-folder moves Dovecot IMAPSieve + rspamc
libmail-dkim-perl DKIM checks inside SpamAssassin Rspamd's own DKIM checks
redis-server (new data store)

Rspamd checks mail while the sender is still connected, enabling immediate greylisting rejections without needing a separate daemon.

8. Z-Push / ActiveSync removed

Z-Push was removed completely rather than carried forward to PHP 8.3, including its config, nginx locations, and autodiscover subdomains. The general CalDAV/CardDAV proxy in nginx stays.

9. Bugs found only by actually installing

Each of these was found by installing the fork in a real Ubuntu 24.04 container, not by reading code.

# Bug What would have happened
1 php8.0-fpm.sock hard-coded in nginx Every PHP page fails
2 systemctl link on a unit already in /lib/systemd/system systemd 255 refuses to enable it; management daemon never starts
3 Both classifier-bayes.conf and statistic.conf in Rspamd's local.d Rspamd refuses to start
4 postgrey still installed by mail-postfix.sh Two greylisting systems running; postgrey doing the work
5 Redis data-folder permissions set in the wrong order Redis fails at next reboot; spam filtering silently degrades
6 Redis blocked from /home by its systemd lockdown Redis won't start at all
7 fail2ban won't start if /var/log/auth.log doesn't exist yet No brute-force protection on a fresh box
8 tr < /dev/urandom | head with set -o pipefail Setup exits with code 141 (broken pipe)
9 boto3 missing from the daemon's environment Admin panel returns HTTP 500
10 sievec doesn't read plugin settings on its own Spam-learning scripts can't be compiled
11 resolved.conf missing on minimal images Setup crashes

10. Security hardening found along the way

The self-test checks that internal-only services aren't reachable from the internet. Dovecot quota-status (port 12340) and munin-node (port 4949) were changed to listen on localhost only, resolving issues that existed in v76.

11. Sandbox mode and the test harness

A sandbox mode was created allowing installation of the full box on a local VM/container with no public DNS, Let's Encrypt, or outbound port 25. The test harness runs 88 distinct checks covering services, ports, config validity, DNS, TLS, and mail flow (spam headers, greylisting, training).

Final result: 88 passed, 0 failed.

What is still open

Backups via duplicity need to be tested end-to-end to confirm they fully backup and restore properly with the new b2sdk/boto3 UV environment. Also deferred is moving DKIM signing and DMARC into Rspamd (Phase 5b), and testing for Ubuntu 26.04.