mlmmj 2.2.2 Ubuntu 22.04 安装与 Postfix 对接指南
Ubuntu 22.04 实测 mlmmj 2.2.2 源码编译安装与 Postfix 3.6.4 的 transport 管道对接:apt/离线 deb/源码三方案对比,按官方 README.postfix.md 推荐用 mlmmj pipe 传输+伪别名域 localhost.mlmmj 路由,实测建列表、订阅、端到端收发与 +subscribe/+confsub 命令邮件闭环,含 maintd systemd 托管、apt cron 共存坑与卸载清理。
mlmmj 2.2.2 Ubuntu 22.04 安装与 Postfix 对接指南
目标:在一台 Ubuntu 22.04 (jammy) 服务器上从源码编译安装 mlmmj 2.2.2,并按官方 README.postfix.md 推荐的 transport 管道 + 伪别名域(localhost.mlmmj)路由方式完成与 Postfix 3.6.4 的对接,实测建列表、订阅、经 mlmmj-receive 管道收信、向订阅者分发、+subscribe/+confsub 命令邮件订阅闭环。全文命令与回显均在 Ubuntu 22.04.5 LTS x86_64 + Postfix 3.6.4-1ubuntu1.4 + GCC 11.4.0 环境实测通过。
一、环境信息
本篇实测环境与关键产物路径如下,读者按同版本操作可直接复现。
- 操作系统:Ubuntu 22.04.5 LTS (jammy),x86_64
- mlmmj 版本:2.2.2(源码编译,2026-10-09 发布,本篇主推方案);Ubuntu 22.04 universe 仓库打包版为 1.3.0-4(对比用)
- MTA:Postfix 3.6.4-1ubuntu1.4(Ubuntu 22.04 官方源版本)
- 编译工具链:GCC 11.4.0(Ubuntu 11.4.0-1ubuntu1~22.04.3,build-essential 12.9ubuntu3)
- 源码包:
RELEASE_2_2_2.tar.gz,291781 字节,sha2567c38047e681738cefc6e4eb2c13355e6d418cdacec20d39ad773167bb40fd047 - 关键路径:二进制
/usr/local/bin/mlmmj-*(源码方案)或/usr/bin/mlmmj-*(apt 方案);列表数据/var/spool/mlmmj/<列表名>/;路由映射表/var/spool/mlmmj/virtual与/var/spool/mlmmj/transport;列表文本模板/usr/local/share/mlmmj/text.skel/ - 邮件域名约定:列表服务器主机名
lists.example.com,列表地址mlmmj-test@lists.example.com;本地信箱域example.com,测试订阅者alice@example.com/bob@example.com/carol@example.com(与本机 Postfix 同域,便于本地投递验证) - 伪别名域:
localhost.mlmmj——仅用于 Postfix 内部把列表地址桥接到 mlmmj 传输,不是真实域名、不对外解析(机制见 8.4 节) - 运行身份:列表管道以专用系统用户
mlmmj(uid 999)执行,区别于 Debian 篇 aliases 方案的nobody方案(见 《Debian 12 安装 mlmmj 2.2.2 指南》 8.5 节) - 源码下载源说明:官方仓库在 Codeberg(codeberg.org/mlmmj/mlmmj),本篇直连
https://codeberg.org/mlmmj/mlmmj/archive/RELEASE_2_2_2.tar.gz实测可用(官方源直连即达,435 KB/s,未发生渠道降级) - 日志取证提示:Ubuntu 22.04 精简服务器默认不带 syslog 守护进程,收信管道日志需要
apt-get install -y rsyslog后才有/var/log/mail.log(实测踩坑与修复见第十一章 Q2)
二、前置条件
安装 mlmmj 前,确认服务器具备以下条件,缺一项都会导致后续集成步骤卡住。
- 一台可用的 MTA:mlmmj 自身不收发邮件,收信靠 MTA 管道触发、发信交给本机 SMTP 服务(默认
127.0.0.1:25)。本篇以 Postfix 为例:
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y postfix
apt-get update 实测回显(节选,实测尾部):
Get:10 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [4467 kB]
Fetched 28.9 MB in 5s (5338 kB/s)
Reading package lists...
apt-get install -y postfix 实测回显(节选,实测尾部):
Postfix configuration values, see postconf(1).
After modifying main.cf, be sure to run 'systemctl reload postfix'.
Running newaliases
invoke-rc.d: policy-rc.d denied execution of restart.
invoke-rc.d: policy-rc.d denied execution of start.
Created symlink /etc/systemd/system/multi-user.target.wants/postfix.service → /lib/systemd/system/postfix.service.
/usr/sbin/policy-rc.d returned 101, not running 'start postfix.service'
Processing triggers for libc-bin (2.35-0ubuntu3.15) ...
⚠️ 注意:安装器已把 postfix 设为开机自启(
Created symlink ...),但包内脚本被policy-rc.d拦截、服务并未立即启动,必须手动systemctl start postfix(该现象的排障记录另见第十一章 Q3)。
启动并核验 MTA:
systemctl start postfix
systemctl is-active postfix
postconf mail_version
实测回显:
active
mail_version = 3.6.4
- 确认 25 端口已监听(MTA 投递出口依赖本机 SMTP;
ss命令属 iproute2,精简系统缺失时先apt-get install -y iproute2):
ss -ltn | grep ':25 '
实测回显:
LISTEN 0 100 0.0.0.0:25 0.0.0.0:*
LISTEN 0 100 [::]:25 [::]:*
- 编译依赖(仅源码方案需要):
apt-get install -y build-essential wget pkg-config
- 主机名与邮件域规划:列表服务器的 FQDN(本例
lists.example.com)将出现在所有列表地址中,安装前确认 DNS/hosts 已就绪:
hostname -f
实测回显:
lists.example.com
三、版本选型说明
mlmmj 的「仓库版」与「上游版」差距非常大,安装前必须先选型,这直接决定后续功能完整性。
| 渠道 | 版本 | 发布时间 | 说明 |
|---|---|---|---|
| Ubuntu 22.04 universe 源 | 1.3.0-4 | 上游 2016 年前后,包维护打包 | 与最新版相差两个大版本(1.x → 2.x) |
| 上游源码(Codeberg RELEASE_2_2_2) | 2.2.2 | 2026-10-09 | 默认注入 RFC 2919/2369 列表头、归档 YYYY/MM 分区、subrelease 自助释放、DKIM/From 改写变量等均为 2.x 能力 |
| Ubuntu 22.04 仓库内 Postfix | 3.6.4-1ubuntu1.4 | 2024-05 安全更新 | 与 mlmmj 无版本耦合,直接用仓库版即可 |
先看仓库候选版本的实测取证:
apt-cache policy mlmmj postfix
实测回显:
mlmmj:
Installed: (none)
Candidate: 1.3.0-4
Version table:
1.3.0-4 500
500 http://archive.ubuntu.com/ubuntu jammy/universe amd64 Packages
postfix:
Installed: (none)
Candidate: 3.6.4-1ubuntu1.4
Version table:
3.6.4-1ubuntu1.4 500
500 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages
500 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages
3.6.4-1ubuntu1 500
500 http://archive.ubuntu.com/ubuntu jammy/main amd64 Packages
表格解读:Ubuntu 22.04 仓库里的 mlmmj 与 Debian 12 同为 1.3.0-4(Ubuntu 直接同步 Debian 打包),1.3.0 没有 2.x 的全部新特性(默认 List-* 标准头、归档分区、自助释放、$disarmedfrom$ DKIM 配方等,特性清单见 《mlmmj 2.2.2 介绍与选型指南》)。因此本篇推荐源码编译 2.2.2 为生产方案(方案 C),apt/离线包方案(A/B)适合仅作功能评估或内网受限场景。
⚠️ 版本自检陷阱:2.2.2 源码编译出的二进制执行
mlmmj-list -V会显示mlmmj-list version 2.2.1。原因是上游打 RELEASE_2_2_2 标签(2026-10-09 15:37)时未同步更新源码内版本常量(源码mk/defs.mk第 2 行仍是VERSION= 2.2.1,本篇 6.5 节实测取证)。判断真实版本请以所下载源码标签为准,勿以-V输出为准。
四、方案 A:在线 apt 安装(Ubuntu 22.04 仓库版)
apt 方案是最省事的安装方式,一条命令完成;代价是拿到的是老版本 1.3.0-4。本节先给出完整安装过程与核验,末尾说明版本局限。
4.1 安装命令与实测输出
apt-get install -y mlmmj
实测回显(节选,保留 mlmmj 相关行):
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 mlmmj amd64 1.3.0-4 [188 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 188 kB in 2s (115 kB/s)
Selecting previously unselected package mlmmj.
(Reading database ... 7507 files and directories currently installed.)
Preparing to unpack .../mlmmj_1.3.0-4_amd64.deb ...
Unpacking mlmmj (1.3.0-4) ...
Setting up mlmmj (1.3.0-4) ...
安装完成后核对包版本:
dpkg -l mlmmj postfix | tail -2
实测回显:
ii mlmmj 1.3.0-4 amd64 mail server independent mailing list manager
ii postfix 3.6.4-1ubuntu1.4 amd64 High-performance mail transport agent
4.2 安装产物与 cron 联动核验
apt 包把命令装到 /usr/bin/,同样附带历史拼写兼容命令 mlmmj-recieve(多一个 c,指向与 mlmmj-receive 相同的实现,老脚本兼容用):
ls /usr/bin/mlmmj-* | tr '\n' ' '; echo
实测回显:
/usr/bin/mlmmj-bounce /usr/bin/mlmmj-list /usr/bin/mlmmj-maintd /usr/bin/mlmmj-make-ml /usr/bin/mlmmj-process /usr/bin/mlmmj-receive /usr/bin/mlmmj-recieve /usr/bin/mlmmj-send /usr/bin/mlmmj-sub /usr/bin/mlmmj-unsub
Ubuntu 的 mlmmj 包自带维护 cron 任务(这是与源码方案共存时的隐患,详见 9.3 节):
cat /etc/cron.d/mlmmj
实测回显:
0 */2 * * * root /usr/bin/test -x /usr/bin/mlmmj-maintd && /usr/bin/mlmmj-maintd -F -d /var/spool/mlmmj
包安装时还预建了 spool 根目录(8.1 节会用到):
ls -ld /var/spool/mlmmj
实测回显:
drwxr-xr-x 2 root root 4096 Nov 2 18:17 /var/spool/mlmmj
4.3 apt 方案的版本局限
1.3.0-4 缺少 2.x 的全部新特性(默认 List-* 标准头、归档分区、自助释放、DKIM 配方等)。如果坚持用 apt 版本,建列表脚本与维护任务命令都要用 /usr/bin/ 路径;不要把 apt 版与源码版混装在同一台机器上不加区分地使用(PATH 中 /usr/local/bin 恒先于 /usr/bin,混装时实际执行的是源码版,见第十章)。
五、方案 B:deb 离线包安装(断网内网)
内网服务器无法访问 apt 源时,可在任意外网同架构机器上把 deb 包下载下来,用移动介质带入内网后 dpkg -i 安装。deb 包与方案 A 安装的是同一版本(1.3.0-4)。
第一步,在能联网的 Ubuntu 22.04 机器上下载(只下载不安装):
mkdir -p /root/offline
apt-get download mlmmj postfix
mv -f mlmmj_*.deb postfix_*.deb /root/offline/
实测回显(节选,保留关键行):
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 mlmmj amd64 1.3.0-4 [188 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 postfix amd64 3.6.4-1ubuntu1.4 [1248 kB]
Fetched 1436 kB in 2s (808 kB/s)
说明:
apt-get download把 deb 落到当前工作目录(本例为/root),故用mv归位到/root/offline。root 直接执行时 apt 可能输出一条_apt用户权限相关警告,不影响下载结果。
第二步,核对包完整性与大小:
cd /root/offline && ls -l *.deb && sha256sum *.deb
实测回显:
-rw-r--r-- 1 root root 188040 Aug 27 2020 mlmmj_1.3.0-4_amd64.deb
-rw-r--r-- 1 root root 1247868 May 7 14:29 postfix_3.6.4-1ubuntu1.4_amd64.deb
6b983def6fbefb4154bce57ae58b608388af013bfa165e59f617a71b7caecfb3 mlmmj_1.3.0-4_amd64.deb
f34c63430dc4bd855b41a673f6a19d051ad3b827a93594b3671cf12ea863388c postfix_3.6.4-1ubuntu1.4_amd64.deb
注意:Ubuntu 的
mlmmj_1.3.0-4_amd64.deb(sha256 以6b983def开头)与 Debian 12 同名包(sha256 以98d6d18b开头)内容并不相同,跨发行版搬运前先核对校验和,见 《Debian 12 安装 mlmmj 2.2.2 指南》 第五章。
第三步,把两个 deb 带入内网目标机后离线安装:
cd /root/offline
dpkg -i mlmmj_1.3.0-4_amd64.deb postfix_3.6.4-1ubuntu1.4_amd64.deb
实测回显(dpkg -i 重装演示,节选,已滤去 debconf 前端告警行):
(Reading database ... 7737 files and directories currently installed.)
Preparing to unpack mlmmj_1.3.0-4_amd64.deb ...
Unpacking mlmmj (1.3.0-4) over (1.3.0-4) ...
Setting up mlmmj (1.3.0-4) ...
⚠️ 离线安装的连带依赖:
dpkg -i不解析依赖,若目标机缺 postfix 依赖库(如 libsasl2、libdb 等),先在联网机上用apt-get download把apt-get install -s postfix模拟输出里列出的依赖包一并下载带入。安装后用dpkg -l mlmmj postfix核对为ii状态。
验证命令:
dpkg -l mlmmj | tail -1
实测回显:
ii mlmmj 1.3.0-4 amd64 mail server independent mailing list manager
六、方案 C:源码编译安装 2.2.2(生产推荐)
从官方 Codeberg 仓库直接编译最新稳定版,是拿到 2.x 完整能力的唯一途径(截至 2026-11-02,Ubuntu 22.04 仓库尚无 2.x 打包)。全程约 3 分钟。
6.1 安装编译依赖
apt-get install -y build-essential wget pkg-config
实测回显(节选,实测尾部):
Setting up build-essential (12.9ubuntu3) ...
Setting up libalgorithm-diff-xs-perl (0.04-6build3) ...
Setting up libalgorithm-merge-perl (0.08-3) ...
Processing triggers for libc-bin (2.35-0ubuntu3.15) ...
核对工具链版本:
gcc --version | head -1
pkg-config --version
实测回显:
gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0
0.29.2
6.2 下载源码包
源码统一放 /usr/local/src(生产服务器惯例路径),下载后立即校验 sha256:
mkdir -p /usr/local/src
cd /usr/local/src
wget https://codeberg.org/mlmmj/mlmmj/archive/RELEASE_2_2_2.tar.gz -O mlmmj-2.2.2.tar.gz
sha256sum mlmmj-2.2.2.tar.gz
实测回显(官方源直连成功,未走任何降级渠道):
--2026-11-02 18:25:52-- https://codeberg.org/mlmmj/mlmmj/archive/RELEASE_2_2_2.tar.gz
Resolving codeberg.org (codeberg.org)... 217.197.84.140, 2a0a:4580:103f:c0de::1
Connecting to codeberg.org (codeberg.org)|217.197.84.140|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 291781 (285K) [application/octet-stream]
Saving to: 'mlmmj-2.2.2.tar.gz'
0K .......... .......... .......... .......... .......... 17% 225K 1s
50K .......... .......... .......... .......... .......... 35% 233K 1s
100K .......... .......... .......... .......... .......... 52% 11.8M 0s
150K .......... .......... .......... .......... .......... 70% 12.4M 0s
200K .......... .......... .......... .......... .......... 87% 238K 0s
250K .......... .......... .......... .... 100% 42.2M=0.7s
2026-11-02 18:25:53 (435 KB/s) - 'mlmmj-2.2.2.tar.gz' saved [291781/291781]
7c38047e681738cefc6e4eb2c13355e6d418cdacec20d39ad773167bb40fd047 mlmmj-2.2.2.tar.gz
解压并进入源码目录:
tar xf mlmmj-2.2.2.tar.gz && cd mlmmj
⚠️ 不要把源码解压到挂载了
noexec的分区再编译:/tmp常被按安全基线配置为 noexec,./configure会以退出码 126 直接无法执行。/usr/local/src是无此限制的惯例位置。
6.3 configure 配置(含报错实录)
mlmmj 的 configure 是自研脚本(非 autoconf),先看可用开关:
./configure --help | grep -A8 'Project options'
实测回显:
Project options:
--with-asan build with libasan support
--with-lsan build with liblsan support
--with-ubsan build with libubsan support
--with-tsan build with libtsan support
--with-coverage build with coverage flags
--enable-receive-strip build mlmmj-receive-strip (default: disabled)
--disable-tests build and run tests (default: enabled)
直接执行 ./configure 会报错(pkg-config 已在 6.1 装好,Ubuntu 上只剩测试框架一项缺口),实录如下:
./configure
实测回显(完整):
Found C compiler: cc
Host: x86_64-unknown-linux7.0.0
Configuring from /usr/local/src/mlmmj
Found tool: ar
Found tool: ranlib
Found tool: strip
Checking for function arc4random_uniform... no
Checking for function copy_file_range... yes
Checking for iconv... yes
Checking for package atf-c... no
Error: Unable to find atf-c via pkg-config (required for tests)
结论:测试套件需要 pkg-config + atf-c(Automated Testing Framework,机制与 Debian 篇一致,见 《Debian 12 安装 mlmmj 2.2.2 指南》 6.3 节)。两种处理:apt-get install -y libatf-dev 补齐并保留测试(实测 jammy 仓库有该包),或加 --disable-tests 跳过测试(生产部署的常规选择)。先取证仓库可用性:
apt-cache policy libatf-dev | head -3
实测回显:
libatf-dev:
Installed: (none)
Candidate: 0.21-6
本篇采用 --disable-tests:
./configure --disable-tests --prefix=/usr/local
实测回显(完整):
Found C compiler: cc
Host: x86_64-unknown-linux7.0.0
Configuring from /usr/local/src/mlmmj
Found tool: ar
Found tool: ranlib
Found tool: strip
Checking for function arc4random_uniform... no
Checking for function copy_file_range... yes
Checking for iconv... yes
Creating config.h
Creating mk/defs.mk from mk/defs.mk.in
Creating Makefile from Makefile.in
Creating src/Makefile from src/Makefile.in
Creating listtexts/Makefile from listtexts/Makefile.in
Creating contrib/Makefile from contrib/Makefile.in
Creating contrib/receivestrip/Makefile from contrib/receivestrip/Makefile.in
Creating src/mlmmj-make-ml from src/mlmmj-make-ml.in
Creating kyua.conf from kyua.conf.in
Configuration complete. You can now run 'make'.
💡 说明:
Checking for function arc4random_uniform... no是 Ubuntu 22.04 与 Debian 12 实测输出的差异点——jammy 的 glibc 2.35 尚无该函数(glibc 2.36 才引入),configure 探测失败后 mlmmj 自动回退到内置实现,编译不受影响(6.4 节实测make退出码 0)。Host: x86_64-unknown-linux7.0.0中的7.0.0是 configure 拼接的内核版本号,属正常输出。
6.4 编译与安装
make -j2 > /tmp/make.log 2>&1; echo "make rc=$?"; tail -4 /tmp/make.log
make install > /tmp/mki.log 2>&1; echo "install rc=$?"; tail -3 /tmp/mki.log
实测回显:
make rc=0
make[2]: Entering directory '/usr/local/src/mlmmj/contrib/receivestrip'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/usr/local/src/mlmmj/contrib/receivestrip'
make[1]: Leaving directory '/usr/local/src/mlmmj/contrib'
install rc=0
make[2]: Nothing to be done for 'install'.
make[2]: Leaving directory '/usr/local/src/mlmmj/contrib/receivestrip'
make[1]: Leaving directory '/usr/local/src/mlmmj/contrib'
make install 同时安装二进制、man 手册页与列表文本模板。
6.5 安装产物核验与版本自检
ls /usr/local/bin/ | tr '\n' ' '; echo
ls /usr/local/man/man1/ | tr '\n' ' '; echo
实测回显:
mlmmj-bounce mlmmj-list mlmmj-maintd mlmmj-make-ml mlmmj-make-ml.sh mlmmj-process mlmmj-receive mlmmj-recieve mlmmj-send mlmmj-sub mlmmj-unsub
mlmmj-bounce.1 mlmmj-list.1 mlmmj-maintd.1 mlmmj-make-ml.1 mlmmj-process.1 mlmmj-receive.1 mlmmj-send.1 mlmmj-sub.1 mlmmj-unsub.1
版本自检(注意第三章说明的 2.2.2 自报 2.2.1 陷阱):
which mlmmj-list
mlmmj-list -V
grep -n VERSION /usr/local/src/mlmmj/mk/defs.mk | head -1
实测回显:
/usr/local/bin/mlmmj-list
mlmmj-list version 2.2.1
2:VERSION= 2.2.1
which 显示实际执行的是 /usr/local/bin/mlmmj-list——此时机器上 apt 版(/usr/bin,1.3.0-4)与源码版(/usr/local/bin,2.2.2)并存,PATH 中 /usr/local/bin 恒先,命令落到源码版(共存治理见第十章)。
再核一处目录:多语言列表文本模板已就位(含 zh-cn,供建列表时选择):
ls /usr/local/share/mlmmj/text.skel
实测回显:
ast
cs
de
en
fi
fr
gr
it
pt
sk
zh-cn
七、方案对比与选择建议
三种方案在同一台 Ubuntu 22.04 上的实测对比如下:
| 维度 | A:在线 apt | B:deb 离线包 | C:源码编译 |
|---|---|---|---|
| 安装版本 | 1.3.0-4 | 1.3.0-4 | 2.2.2(上游最新) |
| 需联网 | 是(apt 源) | 否(离线带入) | 是(拉源码)或离线带入源码包 |
| 安装路径 | /usr/bin | /usr/bin | /usr/local/bin |
| systemd/cron 联动 | 包自带 /etc/cron.d/mlmmj | 同 apt | 需自建(见第九章) |
| man 手册/文本模板 | /usr/share 下齐全 | 同 apt | /usr/local/man、/usr/local/share/mlmmj |
| 2.x 特性(List-* 头/归档分区/DKIM 配方) | 无 | 无 | 全部具备 |
| 适用场景 | 快速评估、特性兼容性验证 | 断网内网、合规审计环境 | 生产部署(本篇推荐) |
表格解读:能联网且要上生产,直接选 C;断网内网先评估 B(但拿不到 2.x 能力);A 最快但也最老,且它自带的 cron 会与源码版的维护任务形成双版本并行隐患(9.3 节实录)。A/B 装在 /usr/bin、C 装在 /usr/local/bin,共存时 PATH 优先执行 C 的版本,务必按第十章/第十二章清理其一避免混乱。
八、Postfix 对接:transport 管道 + 伪别名域路由(本篇主线)
mlmmj 装完只是拿到一堆命令,必须打通「MTA 收信 → mlmmj-receive 管道 → 列表分发」才算可用。官方 README.postfix.md(源码包内,2012-01-28 版)开宗明义指出核心矛盾:mlmmj 必须以 root 或列表目录属主身份执行,而 Postfix 默认以 nobody 执行外部命令,并给出六种绕法,其中官方推荐的是「用 Postfix transport 以专用 mlmmj 用户执行 mlmmj」——这也是本篇与 《Debian 12 安装 mlmmj 2.2.2 指南》(aliases 管道 + nobody 属主方案)定位上的最大差异。
💡 技术拓展:mlmmj 与 Postfix 的对接方式不止本文所用的一种。官方 README.postfix.md 列举的六种方案对比——
方案 原理 官方评价 列表目录归 nobody 迁就管道默认身份 不安全(nobody 被 NFS 等广泛用作拒绝访问的载体) 改 default_privs 为 mlmmj 全局改管道默认执行身份 不安全/不切实际(影响其他外部命令) .forward 文件 每列表建系统用户收信转发 不实际(列表数=用户数) :include: 文件 别名包含 mlmmj 用户可读文件 不安全且凌乱(需对 :include: 开放命令投递) mlmmj 用户属主的 alias 表 独立别名表指向管道 次优(多域场景凌乱) Postfix transport 管道(本文) master.cf 自定义 pipe 传输, user=mlmmj降权执行官方推荐 本文按官方推荐采用 transport 方案:列表目录属主是专用用户 mlmmj,管道以 mlmmj 身份执行,既不动 nobody 也不动 default_privs,多列表只需在映射表加行、无需逐个改 /etc/aliases。
本节以列表 mlmmj-test@lists.example.com 为例,全部命令实测。
8.1 专用运行用户 mlmmj 与 spool 目录
创建系统用户 mlmmj(无密码、nologin、家目录即 spool 根),并把 spool 根目录属主交给它:
useradd -r -m -d /var/spool/mlmmj -s /usr/sbin/nologin mlmmj
id mlmmj
chown mlmmj:mlmmj /var/spool/mlmmj
实测回显:
useradd: warning: the home directory /var/spool/mlmmj already exists.
useradd: Not copying any file from skel directory into it.
uid=999(mlmmj) gid=999(mlmmj) groups=999(mlmmj)
说明:
home directory already exists警告是因为方案 A 的 apt 包已预建了/var/spool/mlmmj(4.2 节核验过,属主 root:root),chown一步即纠正;纯源码方案(未装 apt 包)则无此警告,目录由-m新建。
8.2 Postfix 基础域与收件人分隔符
先让本机 Postfix 认下「列表域 + 本地信箱域」,并统一 mlmmj 与 Postfix 的地址扩展分隔符(以下值按你的实际域名替换):
postconf -e 'myhostname = lists.example.com'
postconf -e 'mydomain = example.com'
postconf -e 'myorigin = $mydomain'
postconf -e 'mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain'
postconf -e 'recipient_delimiter = +'
postconf -e 'inet_interfaces = all'
postconf -e 'mynetworks = 127.0.0.0/8'
逐项解读:mydestination 同时含 $myhostname(lists.example.com,列表域按本地域受理)与 $mydomain(example.com,alice/bob/carol 的本地信箱域);recipient_delimiter = + 让 Postfix 把 列表名+扩展词@域名 中的扩展段正确剥离——mlmmj 的全部邮件指令(订阅/退订/取信/帮助)都挂在这个扩展上。
核验关键参数(含两个决定「+扩展能否传到 mlmmj」的项):
postconf myhostname mydestination recipient_delimiter propagate_unmatched_extensions
实测回显:
myhostname = lists.example.com
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
recipient_delimiter = +
propagate_unmatched_extensions = canonical, virtual
💡 说明:
propagate_unmatched_extensions默认值已含virtual,意味着虚拟别名改写时+subscribe这类未参与匹配的扩展会被原样保留到改写后的地址上——8.4 节的伪别名域桥接正是靠它才能把命令扩展送到 mlmmj-receive,无需额外改动(官方 README 也注明「通常不必改」)。
⚠️ 不要把分隔符改成
-:官方 README 明确注明「使用-作分隔符大概率不工作」,因为 mlmmj 自身把-用作内部子定界(如 8.7 节实测回显里的+bounces-1-bob=example.com),列表名里也不能带-。
8.3 master.cf 注册 mlmmj 传输
按官方 README.postfix.md 的配方,在 /etc/postfix/master.cf 追加 pipe 传输服务(注意第二行必须缩进,flags 与 argv 是 pipe 代理的命令属性):
cat >> /etc/postfix/master.cf <<'EOF'
# mlmmj mailing lists (official README.postfix.md transport recipe)
mlmmj unix - n n - - pipe
flags=ORhu user=mlmmj argv=/usr/local/bin/mlmmj-receive -F -L /var/spool/mlmmj/$nexthop
EOF
核验 master.cf 注册生效:
postconf -M | grep '^mlmmj '
实测回显:
mlmmj unix - n n - - pipe flags=ORhu user=mlmmj argv=/usr/local/bin/mlmmj-receive -F -L /var/spool/mlmmj/$nexthop
这一行的每个要素都决定成败,逐项解读(pipe(8) flags 语义取自 Postfix 官方手册):
user=mlmmj:管道以 8.1 节创建的 mlmmj 用户执行 mlmmj-receive,列表目录属主即该用户,权限自洽——这就是官方推荐 transport 而非 aliases 的根本原因(aliases 管道以default_privs的 nobody 执行,逼你把列表目录交给 nobody)。$nexthop:pipe 代理的命令行宏,取值为 transport 映射结果里冒号后的部分(8.4 节),即「列表目录名」,从而拼出-L /var/spool/mlmmj/<列表目录>。flags=ORhu:O给消息前置X-Original-To:头(写入 Postfix 收到的原始收件地址,mlmmj 靠它识别+subscribe等命令扩展,要求单收件人投递,见下条);R前置Return-Path:头(信封发件人);h把$nexthop/域名部分折叠为小写(防大小写混拼出两个列表目录);u对$sender/$recipient中的空白与特殊字符做引用(防参数注入)。-F:mlmmj-receive 的「不 fork 到后台」选项——pipe 代理要求命令同步完成投递并返回退出码,否则 Postfix 误判投递结果。实测选项语义:
/usr/local/bin/mlmmj-receive -h
实测回显(节选):
Usage: /usr/local/bin/mlmmj-receive -L /path/to/listdir
[-s sender@example.org] [-e extension] [-h] [-V] [-P] [-F]
-h: This help
-F: Don't fork in the background
-L: Full path to list directory
-s: Specify sender address
-e: The foo part of user+foo@example.org
-P: Don't execute mlmmj-process
-V: Print version
配套 main.cf 参数:pipe 传输必须限制单收件人投递(否则 O 前置头会错配多人):
postconf -e 'mlmmj_destination_recipient_limit = 1'
8.4 virtual 别名表 + transport 路由表(伪别名域桥接)
两张映射表是 transport 方案的路由核心:virtual_alias_maps 把「列表地址」改写成「伪别名域地址」,transport_maps 再把伪别名域地址指回 mlmmj 传输并携带列表目录名。表都放在 spool 根下(属主 mlmmj,与列表数据同生命周期):
printf "mlmmj-test@lists.example.com\tlists.example.com--mlmmj-test@localhost.mlmmj\n" > /var/spool/mlmmj/virtual
printf "lists.example.com--mlmmj-test@localhost.mlmmj\tmlmmj:mlmmj-test\n" > /var/spool/mlmmj/transport
postmap /var/spool/mlmmj/virtual
postmap /var/spool/mlmmj/transport
main.cf 挂接两张表:
postconf -e 'virtual_alias_maps = hash:/var/spool/mlmmj/virtual'
postconf -e 'transport_maps = hash:/var/spool/mlmmj/transport'
逐行解读:第一张表把 mlmmj-test@lists.example.com 改写为 lists.example.com--mlmmj-test@localhost.mlmmj(键里把域名编进用户名,多域部署时不同域的同名列表互不冲突);第二张表把伪别名域地址路由到 mlmmj: 传输,冒号后的 mlmmj-test 即 $nexthop,拼成列表目录 /var/spool/mlmmj/mlmmj-test。localhost.mlmmj 是纯内部伪域名,不要求任何 DNS 解析,Postfix 因 transport 映射命中而直接投递,不会尝试对外 SMTP。
用 postmap 做查询级验证(模拟 Postfix 运行时的两次查表):
postmap -q mlmmj-test@lists.example.com hash:/var/spool/mlmmj/virtual
postmap -q lists.example.com--mlmmj-test@localhost.mlmmj hash:/var/spool/mlmmj/transport
实测回显:
lists.example.com--mlmmj-test@localhost.mlmmj
mlmmj:mlmmj-test
重载 Postfix 使全部配置生效:
systemctl reload postfix
systemctl is-active postfix
实测回显:
active
💡 说明:transport 方案不需要在
/etc/aliases里为每个列表写管道行,也不需要跑newaliases(那是 aliases 方案的动作,见 《Debian 12 安装 mlmmj 2.2.2 指南》 8.4 节)。新增列表只需:建列表目录 + virtual/transport 各加一行 + 两次 postmap + reload。
8.5 建列表(mlmmj-make-ml)
先看脚手架脚本的全部选项:
/usr/local/bin/mlmmj-make-ml -h
实测回显:
mlmmj-make-ml 0.2
/usr/local/bin/mlmmj-make-ml <options>
options:
-L listname: the name of the mailing list
-a: create the needed entries in your /etc/aliases file
-b: add needed entry to /etc/crontab
-c user: user to chown the spool directory to
-d fqdn: fully qualified domain name of the mailing list
-f answerfile: use answerfile to create mailing list non-interactively
-h: display this help text
-n answerfile: Do nothing. Instead, create answer file for later non-interactive use with -f
-s spooldir: mlmmj spool directory
本篇用 transport 路由,不传 -a(别名行用不上),-c mlmmj 让列表目录直接归 8.1 节的运行用户:
/usr/local/bin/mlmmj-make-ml -L mlmmj-test -d lists.example.com -c mlmmj -s /var/spool/mlmmj
实测回显(交互提示:属主邮箱与语言取默认回车、别名与 cron 均答 n):
The emailaddress of the list owner? [postmaster@lists.example.com] :
You can choose between the following languages for the list texts.
Available languages:
ast
cs
de
en
fi
fr
gr
it
pt
sk
zh-cn
mailing list texts language? [en] :
Don't forget to add this line to /etc/aliases:
mlmmj-test: "|/usr/local/bin/mlmmj-receive -L /var/spool/mlmmj/mlmmj-test/"
Should I add it for you? (y/n) [n]
If you're not starting mlmmj-maintd in daemon mode,
don't forget to add this line to your crontab:
0 */2 * * * /usr/local/bin/mlmmj-maintd -F -L /var/spool/mlmmj/mlmmj-test/
Should I add it to the system-wide crontab at /etc/crontab for you? (y/n) [n]
Mailing list /var/spool/mlmmj/mlmmj-test was created successfully.
** FINAL NOTES **
1) The mailinglist directory has to be owned by the user running the
mailserver (i.e. starting the binaries to work the list)
2) Run newaliases
说明:FINAL NOTES 的两条提醒(写别名、跑 newaliases)是面向 aliases 方案的通用文案;本篇走 8.3/8.4 的 transport 路由,两条都不适用,列表目录属主已由
-c mlmmj与管道执行身份对齐(这正是 transport 方案免踩「nobody 属主坑」的体现)。
核对生成的目录树与关键控制文件:
ls -ld /var/spool/mlmmj/mlmmj-test
ls /var/spool/mlmmj/mlmmj-test/ | tr '\n' ' '; echo
cat /var/spool/mlmmj/mlmmj-test/control/listaddress /var/spool/mlmmj/mlmmj-test/control/owner
实测回显:
drwxr-xr-x 15 mlmmj root 4096 Nov 2 18:20 /var/spool/mlmmj/mlmmj-test
archive bounce control digesters.d incoming index moderation nomailsubs.d queue requeue subconf subscribers.d text unsubconf
mlmmj-test@lists.example.com
postmaster@lists.example.com
8.6 订阅测试用户
先创建两个本地测试用户作为订阅者(真实场景是远端邮箱,原理相同),再用管理员模式 -a 静默订阅(跳过邮件确认,适合测试与导入名单):
useradd -m -s /bin/bash alice
useradd -m -s /bin/bash bob
useradd -m -s /bin/bash carol
/usr/local/bin/mlmmj-sub -L /var/spool/mlmmj/mlmmj-test/ -a alice@example.com
/usr/local/bin/mlmmj-sub -L /var/spool/mlmmj/mlmmj-test/ -a bob@example.com
实测回显(两条 sub 输出一致):
Changing to uid 999 gid 0, owner of /var/spool/mlmmj/mlmmj-test/.
Changing to uid 999 gid 0, owner of /var/spool/mlmmj/mlmmj-test/.
说明:root 执行 mlmmj 命令时自动降权到列表目录属主(uid 999 = mlmmj),与管道执行身份一致,文件属主不会写歪——这是 8.1 节把目录归 mlmmj 而非 nobody 后的自然行为。
列出订阅者核对:
/usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/
实测回显:
bob@example.com
alice@example.com
8.7 端到端投递实测(transport 管道取证)
向列表投一封信(发件人 alice,走本机 Postfix 的 sendmail 入口):
printf 'From: alice@example.com\nTo: mlmmj-test@lists.example.com\nSubject: transport pipe e2e test\n\nHello everyone, this post goes through the mlmmj transport pipe.\n' \
| sendmail -f alice@example.com mlmmj-test@lists.example.com
sleep 5
mailq
实测回显:
Mail queue is empty
队列已排空说明投递闭环走通。看 Postfix 日志里 transport 管道的投递记录(需已装 rsyslog,见第一章提示):
grep 'relay=mlmmj' /var/log/mail.log | head -2
实测回显:
Nov 2 18:21:30 lists postfix/pipe[6956]: 24649E0ED8B: to=<lists.example.com--mlmmj-test@localhost.mlmmj>, orig_to=<mlmmj-test@lists.example.com>, relay=mlmmj, delay=0.09, delays=0/0/0/0.09, dsn=2.0.0, status=sent (delivered via mlmmj service)
Nov 2 18:21:42 lists postfix/pipe[6956]: B2A6DE0ED8B: to=<lists.example.com--mlmmj-test+subscribe@localhost.mlmmj>, orig_to=<mlmmj-test+subscribe@lists.example.com>, relay=mlmmj, delay=0.05, delays=0/0/0/0.05, dsn=2.0.0, status=sent (delivered via mlmmj service)
第一行就是 8.4 节路由链的运行时证据:orig_to 是 Postfix 实际收到的列表地址,to 是经 virtual 表改写后的伪别名域地址,relay=mlmmj 命中 8.3 节的 pipe 传输,status=sent 表示 mlmmj-receive 同步返回成功;第二行是 8.8 节命令邮件的同类记录,可见 +subscribe 扩展在改写后原样保留。
再验证订阅者 bob 的信箱里收到了列表分发版:
head -24 /var/mail/bob
实测回显(关键头与正文完整保留):
From mlmmj-test+bounces-1-bob=example.com@lists.example.com Mon Nov 2 18:21:05 2026
Return-Path: <mlmmj-test+bounces-1-bob=example.com@lists.example.com>
X-Original-To: bob@example.com
Delivered-To: bob@example.com
Received: from lists.example.com (localhost [127.0.0.1])
by lists.example.com (Postfix) with ESMTP id 9A18EE0ED84
for <bob@example.com>; Mon, 2 Nov 2026 18:21:05 +0000 (UTC)
X-Original-To: mlmmj-test@lists.example.com
Received: by lists.example.com (Postfix, from userid 0)
id 9316FE0EC10; Mon, 2 Nov 2026 18:21:05 +0000 (UTC)
From: alice@example.com
To: mlmmj-test@lists.example.com
Subject: transport pipe e2e test
Message-Id: <20261102182105.9316FE0EC10@lists.example.com>
Date: Mon, 2 Nov 2026 18:21:05 +0000 (UTC)
List-Id: <mlmmj-test.lists.example.com>
List-Post: <mailto:mlmmj-test@lists.example.com>
List-Help: <mailto:mlmmj-test+help@lists.example.com>
List-Subscribe: <mailto:mlmmj-test+subscribe@lists.example.com>
List-Unsubscribe: <mailto:mlmmj-test+unsubscribe@lists.example.com>
List-Owner: <mailto:mlmmj-test+owner@lists.example.com>
Precedence: list
Hello everyone, this post goes through the mlmmj transport pipe.
逐项解读这封信证明的事实:信封退信地址是 VERP 形式(mlmmj-test+bounces-1-bob=example.com@,第 1 封给 bob 的退信自动定位到订阅记录,退信回来时同样经 8.4 路由链进 mlmmj-bounce);List-Id 等 6 个标准列表头由 mlmmj 2.x 默认注入;Precedence: list 告知自动回复器勿回信;正文与头之间由空行分隔,格式符合 RFC 5322。到此「收 → 审 → 发」主链路全部打通。
8.8 +subscribe/+confsub 命令邮件订阅闭环
transport 方案的 O 标志(X-Original-To 头)让 mlmmj 能识别地址扩展。实测让 carol 用邮件自助订阅:第一步,carol 向 mlmmj-test+subscribe@lists.example.com 发一封信:
printf 'From: carol@example.com\nTo: mlmmj-test+subscribe@lists.example.com\n\n' \
| sendmail -f carol@example.com mlmmj-test+subscribe@lists.example.com
sleep 5
ls /var/spool/mlmmj/mlmmj-test/subconf/
实测回显:
6afe7ff343d81398
subconf/ 下出现 16 位令牌文件,说明带扩展的地址经 virtual 表改写时扩展被保留(8.2 节 propagate_unmatched_extensions 的作用)、mlmmj-receive 从 X-Original-To 头解析出了 +subscribe 命令。第二步,看 carol 收到的确认信:
head -28 /var/mail/carol
实测回显:
From mlmmj-test+bounces-confsub-6afe7ff343d81398@lists.example.com Mon Nov 2 18:21:42 2026
Return-Path: <mlmmj-test+bounces-confsub-6afe7ff343d81398@lists.example.com>
X-Original-To: carol@example.com
Delivered-To: carol@example.com
Received: from lists.example.com (localhost [127.0.0.1])
by lists.example.com (Postfix) with ESMTP id B4094E0ED86
for <carol@example.com>; Mon, 2 Nov 2026 18:21:42 +0000 (UTC)
Subject: =?utf-8?q?Confirm_subscription_to_mlmmj-test=40lists.example.com?=
From: mlmmj-test+help@lists.example.com
To: carol@example.com
Message-ID: <1793643702-7353-mlmmj-6632b915@lists.example.com>
Date: Mon, 02 Nov 2026 18:21:42 +0000
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
Reply-To: mlmmj-test+confsub-6afe7ff343d81398@lists.example.com
Hi, this is the Mlmmj program managing the <mlmmj-test@lists.example.com>
mailing list.
Somebody (and we hope it was you) has requested that your email address
<carol@example.com> be added to the list. This means every time a post is
sent to the list, you will receive a copy of it.
To confirm you want to do this, please send a message to
<mlmmj-test+confsub-6afe7ff343d81398@lists.example.com> which can usually
be done simply by replying to this message. The subject and the body of the
message can be anything.
第三步,carol 向确认地址(+confsub-令牌)回信完成订阅:
printf 'From: carol@example.com\nTo: mlmmj-test+confsub-6afe7ff343d81398@lists.example.com\n\n' \
| sendmail -f carol@example.com mlmmj-test+confsub-6afe7ff343d81398@lists.example.com
sleep 5
/usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/
实测回显:
bob@example.com
alice@example.com
carol@example.com
carol 已进入订阅名单,subconf/ 下令牌文件同时被消费清空(ls 无输出)。确认命令邮件同样经 transport 管道送达:
grep 'relay=mlmmj' /var/log/mail.log | tail -1
实测回显:
Nov 2 18:21:55 lists postfix/pipe[6956]: B02D7E0ED8B: to=<lists.example.com--mlmmj-test+confsub-6afe7ff343d81398@localhost.mlmmj>, orig_to=<mlmmj-test+confsub-6afe7ff343d81398@lists.example.com>, relay=mlmmj, delay=0.05, delays=0/0/0/0.05, dsn=2.0.0, status=sent (delivered via mlmmj service)
carol 信箱里的成功通知:
tail -20 /var/mail/carol
实测回显:
Content-Transfer-Encoding: 8bit
Hi, this is the Mlmmj program managing the <mlmmj-test@lists.example.com>
mailing list.
Thank you for confirming your subscription. You have now been added to the
normal version of the list.
The email address you are subscribed with is <carol@example.com>.
If you ever wish to unsubscribe, send a message to
<mlmmj-test+unsubscribe@lists.example.com> using this email address. The
subject and the body of the message can be anything. You will then receive
confirmation or further instructions.
For other information and help about this list, send a message to
<mlmmj-test+help@lists.example.com>.
💡 说明:
+subscribe/+confsub/+unsubscribe/+help等邮件命令地址族的完整语义与更多实测见 《mlmmj 2.2.2 CLI 命令详解》;订阅确认相关控制文件(nosubconfirm等)见 《mlmmj 2.2.2 配置参数详解》。
九、维护任务 mlmmj-maintd 托管
mlmmj 的退信处理、队列重放、摘要发送、过期清理全部由 mlmmj-maintd 完成,必须为每个列表(或每个 spool 根目录)配置周期维护。
9.1 systemd 服务方式(推荐常驻托管)
创建服务单元(-d 参数让 maintd 扫描整个 spool 根目录下所有列表,守护模式自 daemonize,故 Type=forking):
cat > /etc/systemd/system/mlmmj-maintd.service <<'EOF'
[Unit]
Description=mlmmj mailing list maintenance daemon
After=network.target postfix.service
[Service]
Type=forking
ExecStart=/usr/local/bin/mlmmj-maintd -d /var/spool/mlmmj
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now mlmmj-maintd
实测回显:
Created symlink /etc/systemd/system/multi-user.target.wants/mlmmj-maintd.service → /etc/systemd/system/mlmmj-maintd.service.
状态核验(注意 240KB 的内存占用,印证 mlmmj 的轻量定位):
systemctl status mlmmj-maintd --no-pager -l | head -11
实测回显:
● mlmmj-maintd.service - mlmmj mailing list maintenance daemon
Loaded: loaded (/etc/systemd/system/mlmmj-maintd.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2026-11-02 18:22:12 UTC; 2s ago
Process: 7431 ExecStart=/usr/local/bin/mlmmj-maintd -d /var/spool/mlmmj (code=exited, status=0/SUCCESS)
Main PID: 7432 (mlmmj-maintd)
Tasks: 1 (limit: 2266)
Memory: 240.0K
CPU: 2ms
CGroup: /system.slice/mlmmj-maintd.service
└─7432 /usr/local/bin/mlmmj-maintd -d /var/spool/mlmmj
9.2 维护运行取证
维护动作的证据落在每个列表目录的 mlmmj-maintd.lastrun.log:
cat /var/spool/mlmmj/mlmmj-test/mlmmj-maintd.lastrun.log
实测回显(一次维护运行的十个阶段):
Starting maintenance run at Mon Nov 2 18:22:12 2026
clean_moderation
clean_discarded
clean_subconf
clean_unsubconf
resend_queue
resend_requeue
clean_nolongerbouncing
unsub_bouncers
probe_bouncers
run_digests
9.3 与 apt 包 cron 任务的共存坑(Ubuntu 特有场景)
如果这台机器上方案 A 的 apt 包还没卸载,/etc/cron.d/mlmmj 会带着系统级维护任务常驻:
cat /etc/cron.d/mlmmj
实测回显:
0 */2 * * * root /usr/bin/test -x /usr/bin/mlmmj-maintd && /usr/bin/mlmmj-maintd -F -d /var/spool/mlmmj
⚠️ 注意:这条 cron 每 2 小时跑的是
/usr/bin/mlmmj-maintd(1.3.0 老版),与 systemd 里的/usr/local/bin/mlmmj-maintd(2.2.2)形成双版本双实例并行维护同一 spool,老版可能按 1.x 语义处理 2.x 写的队列文件。选 systemd 托管时二选一清理:apt-get purge -y mlmmj(连 cron 文件一起删,本篇第十二章卸载实测即此路径),或手动rm /etc/cron.d/mlmmj。
十、升级、降级与多版本共存
- 源码版升级:下载新版本标签的 tar 包,解压到
/usr/local/src,重复第六章 configure/make/make install——同一--prefix直接覆盖旧二进制,列表数据在/var/spool/mlmmj与安装目录无关,升级不影响列表。升级后重启systemctl restart mlmmj-maintd并检查 lastrun 日志确认新版本工作正常。 - 降级:源码安装无版本管理,降级 = 下载旧标签包重新 make install 覆盖;或
apt-get install mlmmj装回 1.3.0-4(注意此时/usr/bin与/usr/local/bin并存)。 - 多版本共存判定:
which mlmmj-list显示实际执行的是/usr/local/bin(PATH 中 /usr/local/bin 恒在前,6.5 节实测)。生产建议二选一:用源码版就apt-get purge -y mlmmj移除仓库版,同时消掉/etc/cron.d/mlmmj双维护隐患(9.3 节)。
十一、常见问题(FAQ)
Q1:./configure 报 Unable to find atf-c via pkg-config (required for tests)?
测试套件依赖 atf-c。Ubuntu 22.04 仓库有该包(apt-cache policy libatf-dev 实测 Candidate: 0.21-6),apt-get install -y libatf-dev 补齐可保留测试;生产部署常规做法是 ./configure --disable-tests(6.3 节实录)。若连 pkg-config 都缺,会先报 Unable to find pkg-config,apt-get install -y pkg-config 后重跑。
Q2:投递后想查日志,tail /var/log/mail.log 报 No such file or directory?
Ubuntu 22.04 精简服务器默认不带 syslog 守护进程,Postfix 的日志无处落盘。实测报错原文:
tail: cannot open '/var/log/mail.log' for reading: No such file or directory
修复:apt-get install -y rsyslog && systemctl enable --now rsyslog,之后投递记录即落 /var/log/mail.log(8.7 节的 relay=mlmmj 取证就是这么来的)。
Q3:apt-get install postfix 装完服务没起来?
安装器输出里有 invoke-rc.d: policy-rc.d denied execution of start 与 /usr/sbin/policy-rc.d returned 101, not running 'start postfix.service'(第二章实测回显)——服务只被设为开机自启、并未当场启动,手动 systemctl start postfix 即可,systemctl is-active postfix 应回 active。
Q4:./configure 里 Checking for function arc4random_uniform... no 要紧吗?
不要紧。jammy 的 glibc 2.35 没有该函数(glibc 2.36 才引入),configure 探测失败后 mlmmj 自动回退内置实现,实测编译与运行全部正常(6.3/6.4 节)。Debian 12(glibc 2.36+)上同项为 yes,属发行版差异而非错误。
Q5:投信后 mailq 有滞留、日志报 mlmmj 管道失败?
按链路排查:① postmap -q <列表地址> hash:/var/spool/mlmmj/virtual 与 postmap -q <改写后地址> hash:/var/spool/mlmmj/transport 两次查表都应有输出(8.4 节),没输出是表没 postmap 或地址拼错;② ls -ld /var/spool/mlmmj/<列表> 属主必须是 user=mlmmj 指定的用户(8.1/8.5 节),transport 方案不需要 chown nobody;③ postfix check 看配置语法;④ tail /var/log/mail.log 找 pipe 报错原文。
Q6:mlmmj-list -V 显示的版本和下载的源码版本对不上?
上游 2.2.2 标签未更新源码内版本常量,二进制自报 2.2.1 属正常现象(见第三章与 6.5 节实测),以你实际下载的 Release 标签为准。
Q7:建列表后发信,订阅者收不到,但列表目录 incoming/ 有落件?
说明收信管道已通、卡在 process 阶段,九成是执行身份与目录属主不符(回看 8.1/8.5 节);transport 方案下确认 master.cf 的 user=mlmmj 与 -c mlmmj 两边一致即可。
十二、卸载与清理
源码方案卸载(make install 无对应 uninstall 目标,手动删除三类产物)+ systemd 单元清理:
systemctl disable --now mlmmj-maintd
rm -f /etc/systemd/system/mlmmj-maintd.service
systemctl daemon-reload
rm -f /usr/local/bin/mlmmj-*
rm -f /usr/local/man/man1/mlmmj-*.1
rm -rf /usr/local/share/mlmmj
实测回显:
Removed /etc/systemd/system/multi-user.target.wants/mlmmj-maintd.service.
apt 方案卸载(连同 9.3 节的 cron 文件一并清除):
apt-get purge -y mlmmj
apt-get autoremove -y
实测回显(节选,保留关键行):
(Reading database ... 17126 files and directories currently installed.)
Purging configuration files for mlmmj (1.3.0-4) ...
Processing triggers for man-db (2.10.2-1) ...
Reading state information...
0 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.
说明:mlmmj 无自动安装的依赖包,
autoremove输出0 to remove属正常;Purging configuration files一行即/etc/cron.d/mlmmj等 conffile 被删除(9.3 节隐患随之消除)。
Postfix 侧配置清理(本篇 transport 方案的三处改动逐项撤销):
postconf -X virtual_alias_maps transport_maps mlmmj_destination_recipient_limit
删除 master.cf 末尾追加的 mlmmj 块(本例该块位于文件尾部,可用 sed 按标记行删到末尾;若手工编辑请只删那三行):
sed -i '/# mlmmj mailing lists/,$d' /etc/postfix/master.cf
postfix check
systemctl reload postfix
数据与运行用户清理(卸载软件不会动列表数据与映射表,确认不再保留后手动删):
rm -rf /var/spool/mlmmj
userdel mlmmj
清理后核验(三条查询均无输出、队列空即干净):
ls /usr/local/bin | grep mlmmj; echo "usrlocal rc=$?"
dpkg -l | grep mlmmj; echo "dpkg rc=$?"
ls /var/spool/ | grep mlmmj; echo "spool rc=$?"
mailq | tail -1
实测回显:
usrlocal rc=1
dpkg rc=1
spool rc=1
Mail queue is empty
postconf virtual_alias_maps transport_maps 复核参数已回退默认值:
virtual_alias_maps = $virtual_maps
transport_maps =
十三、参考链接
- 官方安装文档(README):https://mlmmj.org/README.html
- Postfix 对接专篇(源码包 README.postfix.md,本篇第八章配方出处):https://mlmmj.org/README.postfix.html
- Postfix pipe(8) 传输代理手册(flags 与 $nexthop 宏语义):https://www.postfix.org/pipe.8.html
- 版本发布页:https://codeberg.org/mlmmj/mlmmj/releases
- 2.2.2 源码包直链:https://codeberg.org/mlmmj/mlmmj/archive/RELEASE_2_2_2.tar.gz
- Exim4/Qmail/Sendmail 对接说明(源码包内 README.exim4.md / README.qmail.md / README.sendmail.md)
- 本专栏认知篇:《mlmmj 2.2.2 介绍与选型指南》(2.x 功能演进与同类对比)
- 同题姊妹篇:《Debian 12 安装 mlmmj 2.2.2 指南》(aliases 管道 + nobody 属主对接方案)
- 容器形态部署:《mlmmj 2.2.2 Docker Compose 部署指南》
- 参数与命令专题:《mlmmj 2.2.2 配置参数详解》、《mlmmj 2.2.2 CLI 命令详解》