Debian 12 安装 mlmmj 2.2.2 指南

Debian 12 从零安装 mlmmj 2.2.2 实测指南:apt/离线 deb/源码编译三方案对比、configure 报错排障实录、Postfix 别名管道与 recipient_delimiter 集成、列表目录属主坑位、maintd 托管与端到端投递验证。

mlmmjDebian 12邮件列表Postfix2026/10/26

Debian 12 安装 mlmmj 2.2.2 指南

目标:在一台 Debian 12 服务器上从零安装 mlmmj 2.2.2 邮件列表管理器,并完成与 Postfix 的对接(分隔符、别名管道、列表初始化、订阅与端到端投递验证)。全文命令与回显均在 Debian 12 (bookworm) x86_64 + Postfix 3.7.11 环境实测通过。

一、环境信息

本篇实测环境与关键产物路径如下,读者按同版本操作可直接复现。

  • 操作系统:Debian 12 (bookworm),x86_64
  • mlmmj 版本:2.2.2(源码编译,2026-10-09 发布,本篇主推方案);Debian 12 仓库打包版为 1.3.0-4(对比用)
  • MTA:Postfix 3.7.11-0+deb12u1(Debian 12 官方源版本)
  • 编译工具链:GCC 12.2.0(Debian 12 官方源 build-essential)
  • 源码包:RELEASE_2_2_2.tar.gz,291781 字节,sha256 7c38047e681738cefc6e4eb2c13355e6d418cdacec20d39ad773167bb40fd047
  • 关键路径:二进制 /usr/local/bin/mlmmj-*(源码方案)或 /usr/bin/mlmmj-*(apt 方案);列表数据 /var/spool/mlmmj/<列表名>/;手册页 /usr/local/man/man1/;列表文本模板 /usr/local/share/mlmmj/text.skel/
  • 邮件域名约定:列表服务器主机名 lists.example.com,列表地址 mlmmj-test@lists.example.com,测试订阅者 alice@example.com / bob@example.com(与本机 Postfix 同域,便于本地投递验证)
  • 源码下载源说明:官方仓库在 Codeberg(codeberg.org/mlmmj/mlmmj),本篇下载直连官方 https://codeberg.org/mlmmj/mlmmj/archive/RELEASE_2_2_2.tar.gz 实测可用(官方源直连即达,未发生降级)

二、前置条件

安装 mlmmj 前,确认服务器具备以下条件,缺一项都会导致后续集成步骤卡住。

  1. 一台可用的 MTA:mlmmj 自身不收发邮件,收信靠 MTA 别名管道触发、发信交给本机 SMTP 服务(默认 127.0.0.1:25)。本篇以 Postfix 为例;未安装的在方案 A 中会一并安装:
apt-get update
apt-get install -y postfix    # MTA:收信管道与投递出口
  1. 编译依赖(仅源码方案需要):
apt-get install -y build-essential wget pkg-config    # GCC/make、下载工具、pkg-config
  1. 主机名与邮件域规划:列表服务器的 FQDN(本例 lists.example.com)将出现在所有列表地址中,安装前确认 DNS/hosts 已就绪:
hostname -f

实测回显:

lists.example.com
  1. 确认 25 端口可用(MTA 投递出口依赖本机 SMTP):
ss -ltn | grep ':25 '

实测回显(Postfix 启动后):

LISTEN 0      100        0.0.0.0:25         0.0.0.0:*        users:(("master",pid=5211,fd=13))

三、版本选型说明

mlmmj 的「仓库版」与「上游版」差距非常大,安装前必须先选型,这直接决定后续功能完整性。

渠道版本发布时间说明
Debian 12 官方源1.3.0-4上游 2016 年前后,包维护打包与最新版相差两个大版本(1.x → 2.x)
上游源码(Codeberg RELEASE_2_2_2)2.2.22026-10-09默认注入 RFC 2919/2369 列表头、归档 YYYY/MM 分区、subrelease 自助释放、DKIM/From 改写变量等均为 2.x 能力
Debian 12 仓库内 Postfix3.7.11-0+deb12u12024-03与 mlmmj 无版本耦合,直接用仓库版即可

表格解读:1.3.0 与 2.2.2 的功能差距是实打实的——2.0.0 起默认生成的 List-Id/List-Unsubscribe 等标准头直接影响现代邮箱的投递与体验,1.3.0 全部没有。因此本篇推荐源码编译 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,实查取证)。判断真实版本请以所下载源码标签为准,勿以 -V 输出为准。

四、方案 A:在线仓库安装(Debian 12 apt 源)

apt 方案是最省事的安装方式,两条命令完成;代价是拿到的是老版本 1.3.0。本节先给出完整安装过程与核验,末尾说明版本局限。

4.1 安装命令与实测输出

export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y postfix mlmmj

实测回显(节选,保留 mlmmj 相关行):

Selecting previously unselected package mlmmj.
Unpacking mlmmj (1.3.0-4) ...
Setting up postfix (3.7.11-0+deb12u1) ...
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.7.11-0+deb12u1 amd64        High-performance mail transport agent

4.2 安装产物与版本核验

apt 包把命令装到 /usr/bin/,并附带一个历史拼写兼容命令 mlmmj-recieve(多一个 c,指向与 mlmmj-receive 相同的实现,老脚本兼容用):

ls /usr/bin/mlmmj-*

实测回显:

/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

apt 方案还自带了配置联动:mlmmj 包安装时会登记 cron 维护任务与文档,Postfix 主配置无需额外改动即可进入第八章的集成流程。

4.3 apt 方案的版本局限

1.3.0-4 缺少 2.x 的全部新特性(默认 List-* 标准头、归档分区、自助释放、$disarmedfrom$ DKIM 配方等)。如果坚持用 apt 版本,注意它的建列表脚本提示的路径是 /usr/bin/,而后续维护任务命令同样用 /usr/bin/mlmmj-maintd;不要把 apt 版与源码版混装在同一台机器上不加区分地使用(PATH 中 /usr/local/bin 恒先于 /usr/bin,混装时实际执行的是源码版,见第十章)。

五、方案 B:deb 离线包安装(断网内网)

内网服务器无法访问 apt 源时,可在任意外网同架构机器上把 deb 包下载下来,用移动介质带入内网后 dpkg -i 安装。deb 包与方案 A 安装的是同一版本(1.3.0-4)。

第一步,在能联网的 Debian 12 机器上下载(只下载不安装):

mkdir -p /root/offline && cd /root/offline
apt-get download mlmmj postfix

实测回显:

Fetched 1714 kB in 2s (887 kB/s)
W: Download is performed unsandboxed as root as file '/root/offline/mlmmj_1.3.0-4_amd64.deb' couldn't be accessed by user '_apt'. - pkgAcquire::Run (13: Permission denied)

说明:_apt 用户权限警告不影响下载结果,文件已正常落盘。若要消除该警告,可先 chown _apt /root/offline 或下载到公共可读目录。

第二步,核对包完整性与大小:

cd /root/offline && ls -l *.deb && sha256sum *.deb

实测回显:

-rw-r--r-- 1 root root  203716 Aug 27  2020 mlmmj_1.3.0-4_amd64.deb
-rw-r--r-- 1 root root 1510172 Mar 24  2024 postfix_3.7.11-0+deb12u1_amd64.deb
98d6d18bfe754dae49d34ac0c8936185b631b704f0223ab4f055643ea713d6a2  mlmmj_1.3.0-4_amd64.deb
a097fa379fb22c5dcb2b95446ec355fbea8aa338c185eb37685f8db87d68b305  postfix_3.7.11-0+deb12u1_amd64.deb

第三步,把两个 deb 带入内网目标机后离线安装:

cd /root/offline
dpkg -i mlmmj_1.3.0-4_amd64.deb postfix_3.7.11-0+deb12u1_amd64.deb

实测回显(dpkg -i 重装演示,节选):

(Reading database ... 11874 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-10-26,Debian 12 仓库尚无 2.x 打包)。全程约 3 分钟。

6.1 安装编译依赖

apt-get install -y build-essential wget pkg-config

实测回显(节选):

Setting up build-essential (12.9) ...
Setting up pkg-config (1.8.1-1) ...

用 gcc --version 核对工具链:

gcc --version | head -1

实测回显:

gcc (Debian 12.2.0-14+deb12u1) 12.2.0

6.2 下载源码包

源码统一放 /usr/local/src(生产服务器惯例路径),下载后立即校验 sha256:

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-10-26 08:05:47--  https://codeberg.org/mlmmj/mlmmj/archive/RELEASE_2_2_2.tar.gz
Resolving codeberg.org (codeberg.org)... 217.197.91.145
Connecting to codeberg.org (codeberg.org)|217.197.91.145|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/gzip]
Saving to: 'mlmmj-2.2.2.tar.gz'

mlmmj-2.2.2.tar.gz                                     [ <=>                                                                                ] 291.78K  --.-KB/s    in 0.1s

2026-10-26 08:05:47 (2.68 MB/s) - 'mlmmj-2.2.2.tar.gz' saved [291781]

7c38047e681738cefc6e4eb2c13355e6d418cdacec20d39ad773167bb40fd047  mlmmj-2.2.2.tar.gz

解压并进入源码目录:

tar xf mlmmj-2.2.2.tar.gz && cd mlmmj

⚠️ 不要把源码解压到 /tmp 再在其中编译:部分服务器的 /tmp 挂载了 noexec 属性(或按安全基线配置了 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):

Found C compiler: cc
Host: x86_64-unknown-linux
Configuring from /usr/local/src/mlmmj

Found tool: ar
Found tool: ranlib
Found tool: strip
Checking for function arc4random_uniform... yes
Checking for function copy_file_range... yes
Checking for iconv... yes
Error: Unable to find pkg-config (required for tests)

装上 pkg-config 后第二次报错(缺 atf-c 测试框架):

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)。两种处理:apt-get install -y libatf-dev 补齐并保留测试,或加 --disable-tests 跳过测试(生产部署的常规选择)。本篇采用后者:

./configure --disable-tests --prefix=/usr/local

实测回显(完整):

Found C compiler: cc
Host: x86_64-unknown-linux
Configuring from /usr/local/src/mlmmj

Found tool: ar
Found tool: ranlib
Found tool: strip
Checking for function arc4random_uniform... yes
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'.

6.4 编译与安装

make -j2
make install

make 结尾回显(节选):

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

实测回显:

/usr/local/bin/mlmmj-list
mlmmj-list version 2.2.1

再核一处目录:多语言列表文本模板已就位(含 zh-cn,供建列表时选择):

ls /usr/local/share/mlmmj/text.skel

实测回显:

ast  cs  de  en  fi  fr  gr  it  pt  sk  zh-cn

七、方案对比与选择建议

三种方案在同一台 Debian 12 上的实测对比如下:

维度A:在线 aptB:deb 离线包C:源码编译
安装版本1.3.0-41.3.0-42.2.2(上游最新)
需联网是(apt 源)否(离线带入)是(拉源码)或离线带入源码包
安装路径/usr/bin/usr/bin/usr/local/bin
systemd/cron 联动包自带 cron 任务同 apt需自建(见第九章)
man 手册/文本模板/usr/share 下齐全同 apt/usr/local/man、/usr/local/share/mlmmj
2.x 特性(List-* 头/归档分区/DKIM 配方)无无全部具备
适用场景快速评估、特性兼容性验证断网内网、合规审计环境生产部署(本篇推荐)

表格解读:能联网且要上生产,直接选 C;断网内网先评估 B(但拿不到 2.x 能力);A 最快但也最老,建议只在「先跑通流程理解概念」阶段使用。三个方案互斥性不强——A/B 装在 /usr/bin、C 装在 /usr/local/bin,共存时 PATH 优先执行 C 的版本,务必按第十章清理其一避免混乱。

八、Postfix 集成配置与列表初始化

mlmmj 装完只是拿到一堆命令,必须完成「分隔符 → 建列表 → 别名 → 属主权限」四步才算可用。本节以列表 mlmmj-test@lists.example.com 为例,全部命令实测。

8.1 Postfix 基础邮件域配置

先让本机 Postfix 认下「列表域」,并把投递出口准备好(以下值按你的实际域名替换):

postconf -e 'myhostname = lists.example.com'
postconf -e 'mydomain = example.com'
postconf -e 'myorigin = $mydomain'
postconf -e 'mydestination = $myhostname, $mydomain, localhost'
postconf -e 'mynetworks = 127.0.0.0/8'
postconf -e 'inet_interfaces = all'
systemctl restart postfix
systemctl is-active postfix

实测回显:

active

8.2 收件人扩展分隔符(关键项)

mlmmj 的全部邮件指令(订阅/退订/取信/帮助)都挂在 列表名+扩展词@域名 形式的地址上,分隔符默认 +,必须与 Postfix 一致:

postconf -e 'recipient_delimiter = +'
postconf myhostname mydestination recipient_delimiter

实测回显:

myhostname = lists.example.com
mydestination = $myhostname, $mydomain, localhost
recipient_delimiter = +

⚠️ 不要把分隔符改成 -:官方 README 明确注明「使用 - 作分隔符大概率不工作」,因为 mlmmj 自身把 - 用作内部子定界(如 +bounces-1-),列表名里也不能带 -。

8.3 建列表(mlmmj-make-ml)

用官方脚手架一条命令建列表(非交互参数:-L 列表名、-d 域名、-c 属主用户、-a 自动写别名、-s spool 根目录):

/usr/local/bin/mlmmj-make-ml -L mlmmj-test -d lists.example.com -c postfix -a -s /var/spool/mlmmj

实测回显(交互提示均取默认回车):

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] :
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

核对生成的目录树与关键控制文件:

ls /var/spool/mlmmj/mlmmj-test/ | tr '\n' ' '; echo
cat /var/spool/mlmmj/mlmmj-test/control/listaddress /var/spool/mlmmj/mlmmj-test/control/owner

实测回显:

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.4 别名与 newaliases

-a 参数已把收信管道写入 /etc/aliases,核对内容:

tail -2 /etc/aliases

实测回显:

postmaster:    root
mlmmj-test:  "|/usr/local/bin/mlmmj-receive -L /var/spool/mlmmj/mlmmj-test/"

重建别名库(官方 FINAL NOTES 第 2 条,忘了这步 Postfix 找不到列表地址):

newaliases

8.5 列表目录属主(最容易踩的坑)

Postfix 的别名管道默认以 nobody(default_privs 的默认值)身份执行 mlmmj-receive,列表目录必须让 nobody 可写。上面 -c postfix 把目录属主设成了 postfix,与管道运行用户不符——若此时直接投递,邮件会静默滞留。

先看一个反面实录:如果试图用 default_privs = postfix 让管道直接跑 postfix 身份,Postfix 会拒绝:

newaliases: fatal: file /etc/postfix/main.cf: parameters default_privs and mail_owner specify the same user postfix

正确做法是把列表目录属主改成管道实际运行的用户 nobody:

postconf -X default_privs
chown -R nobody:nogroup /var/spool/mlmmj
newaliases

实测回显(无输出即成功,可用下述命令复核属主):

ls -ld /var/spool/mlmmj/mlmmj-test

实测回显:

drwxr-xr-x 13 nobody nogroup 4096 Oct 26 08:11 /var/spool/mlmmj/mlmmj-test

💡 说明:官方 README 也点名了这个坑——「有的 Postfix 安装以 postfix 用户运行,但写文件时用 nobody:nogroup」。规则一句话:列表目录属主 = 别名管道实际执行者 = default_privs(默认 nobody)。后续建的每个新列表都要执行同样的 chown。

8.6 订阅与端到端投递验证

先创建两个本地测试用户作为订阅者(真实场景是远端邮箱,原理相同):

useradd -m -s /bin/bash alice
useradd -m -s /bin/bash bob

用管理员模式 -a 静默订阅两人(跳过邮件确认,适合测试与导入名单):

/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

实测回显(两条一致):

Changing to uid 65534 gid 65534, owner of /var/spool/mlmmj/mlmmj-test/.

列出订阅者核对:

/usr/local/bin/mlmmj-list -L /var/spool/mlmmj/mlmmj-test/

实测回显:

bob@example.com
alice@example.com

向列表投一封信(发件人 alice,走本机 Postfix 的 sendmail 入口):

printf 'From: alice@example.com\nTo: mlmmj-test@lists.example.com\nSubject: hello list\n\nHello everyone, first post via sendmail pipe test.\n' \
  | sendmail -f alice@example.com mlmmj-test@lists.example.com
sleep 5

验证 alice 与 bob 的邮箱都收到了列表分发版:

head -25 /var/mail/bob

实测回显(关键头完整保留):

From mlmmj-test+bounces-1-bob=example.com@lists.example.com  Mon Oct 26 08:07:21 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 760A5388320
	for <bob@example.com>; Mon, 26 Oct 2026 08:07:21 +0000 (UTC)
X-Original-To: mlmmj-test@lists.example.com
Delivered-To: mlmmj-test@lists.example.com
Received: by lists.example.com (Postfix, from userid 0)
	id 6CCEB38831B; Mon, 26 Oct 2026 08:07:09 +0000 (UTC)
From: alice@example.com
To: mlmmj-test@lists.example.com
Subject: hello list
Message-Id: <20261026080709.6CCEB38831B@lists.example.com>
Date: Mon, 26 Oct 2026 08:07:09 +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

逐项解读这封信证明的事实:信封地址是 VERP 形式(mlmmj-test+bounces-1-bob=example.com@,退信自动定位到 bob);List-Id 等 7 个标准列表头由 mlmmj 2.x 默认注入;Precedence: list 告知自动回复器勿回信。到此 mlmmj 的「收 → 审 → 发 → 退信跟踪」主链路全部打通。

九、维护任务 mlmmj-maintd 托管

mlmmj 的退信处理、队列重放、摘要发送、过期清理全部由 mlmmj-maintd 完成,必须为每个列表(或每个 spool 根目录)配置周期维护。两种托管方式二选一。

9.1 systemd 服务方式(推荐常驻托管)

创建服务单元(-d 参数让 maintd 扫描整个 spool 根目录下所有列表,守护模式自 daemonize):

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.

状态核验(注意 244KB 的内存占用,印证 mlmmj 的轻量定位):

systemctl status mlmmj-maintd --no-pager -l | head -10

实测回显:

● mlmmj-maintd.service - mlmmj mailing list maintenance daemon
     Loaded: loaded (/etc/systemd/system/mlmmj-maintd.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-10-26 08:14:21 UTC; 2s ago
    Process: 7587 ExecStart=/usr/local/bin/mlmmj-maintd -d /var/spool/mlmmj (code=exited, status=0/SUCCESS)
   Main PID: 7588 (mlmmj-maintd)
      Tasks: 1 (limit: 19553)
     Memory: 244.0K
        CPU: 2ms
     CGroup: /system.slice/mlmmj-maintd.service
             └─7588 /usr/local/bin/mlmmj-maintd -d /var/spool/mlmmj

维护动作的证据落在每个列表目录的 mlmmj-maintd.lastrun.log:

cat /var/spool/mlmmj/mlmmj-test/mlmmj-maintd.lastrun.log

实测回显(一次维护运行的十个阶段):

Starting maintenance run at Mon Oct 26 08:14:21 2026

clean_moderation
clean_discarded
clean_subconf
clean_unsubconf
resend_queue
resend_requeue
clean_nolongerbouncing
unsub_bouncers
probe_bouncers
run_digests

9.2 cron 方式(官方推荐写法)

官方 README 目前推荐的仍是 cron 每 2 小时一次(对低流量列表足够,且天然避免常驻进程)。选 cron 方式时先停掉上面的 systemd 服务(systemctl disable --now mlmmj-maintd),再把官方提示的行写入 crontab——单列表逐行写,多列表用 -d 一行覆盖:

# 单列表:/etc/crontab 追加一行
0 */2 * * *  root  /usr/local/bin/mlmmj-maintd -F -L /var/spool/mlmmj/mlmmj-test

# 多列表:整个 spool 根目录一行搞定
0 */2 * * *  root  /usr/local/bin/mlmmj-maintd -F -d /var/spool/mlmmj

⚠️ 注意 -F:cron 模式必须带 -F(前台运行一次即退出),systemd 守护模式则用不带 -F 的 -d。两条路写混会出现双实例并行维护,可能对同一队列重复处理。

十、升级、降级与多版本共存

  • 源码版升级:下载新版本标签的 tar 包,解压到 /usr/local/src,重复第六章 configure/make/make install——同一 --prefix 直接覆盖旧二进制,列表数据在 /var/spool/mlmmj 与安装目录无关,升级不影响列表。升级后跑一次 mlmmj-maintd -F -d /var/spool/mlmmj 并检查 lastrun 日志确认新版本工作正常。
  • 降级:源码安装无版本管理,降级 = 下载旧标签包重新 make install 覆盖;或 apt-get install mlmmj 装回 1.3.0(注意此时 /usr/bin 与 /usr/local/bin 并存)。
  • 多版本共存判定:which mlmmj-list 显示实际执行的是 /usr/local/bin(PATH 中 /usr/local/bin 恒在前)。生产建议二选一:用源码版就 apt-get purge mlmmj 移除仓库版,避免两套版本的 maintd/cron 各跑一份。

十一、常见问题(FAQ)

Q1:./configure 报 Unable to find pkg-config (required for tests)? 缺 pkg-config,apt-get install -y pkg-config 后重跑;若接着报 Unable to find atf-c via pkg-config (required for tests),要么 apt-get install -y libatf-dev 补测试框架,要么 ./configure --disable-tests 跳过测试(见 6.3 节实录)。

Q2:./configure 退出码 126、无法执行? 检查源码所在分区是否 noexec 挂载(mount | grep $(df --output=target /usr/local/src | tail -1) 看挂载项),把源码移到无 noexec 的目录(如 /usr/local/src)再编译。

Q3:newaliases 报 parameters default_privs and mail_owner specify the same user postfix? default_privs 不能与 mail_owner(postfix)相同。不要改 default_privs,保持默认 nobody,把列表目录 chown 给 nobody:nogroup(见 8.5 节)。

Q4:投递后列表没反应、订阅者收不到信? 按链路排查三处:① cat /var/mail/服务账号 或 mailq 看 Postfix 是否把信交给了管道;② ls -l /var/spool/mlmmj/mlmmj-test/incoming/ 有没有落件——有落件说明管道通,问题在 process 阶段(多为目录属主不对,回看 8.5 节);③ tail /var/log/mail.log 找 mlmmj 相关报错。

Q5:sendmail: warning: unable to look up public/pickup: No such file or directory? Postfix 刚启动/重启后队列管理目录尚未就绪时 sendmail 入队会打此警告,多数情况下信件仍会入队。规范做法是等 Postfix 完全启动(systemctl is-active postfix 为 active 且 /var/spool/postfix/public/pickup 存在)再投信;若警告后信确实没进队列,systemctl restart postfix 后重投。

Q6:mlmmj-list -V 显示的版本和下载的源码版本对不上? 上游 2.2.2 标签未更新源码内版本常量,二进制自报 2.2.1 属正常现象(见第三章版本选型说明),以你实际下载的 Release 标签为准。

Q7:订阅者发信后收到一封 "you are not subscribed" 拒绝信? 列表开启了 subonlypost(仅订阅者可发帖)而发件人的 From 地址与订阅地址不一致。让订阅者用订阅地址发信,或按官方 DKIM 配方在 control/customheaders 里规划 From 改写(见配置参数详解篇 4.3 节同款控制文件说明)。

十二、卸载与清理

源码方案卸载(make install 无对应 uninstall 目标,手动删除三类产物):

rm -f /usr/local/bin/mlmmj-*
rm -f /usr/local/man/man1/mlmmj-*.1
rm -rf /usr/local/share/mlmmj
rm -f /etc/systemd/system/mlmmj-maintd.service
systemctl daemon-reload

apt/离线方案卸载:

apt-get purge -y mlmmj
apt-get autoremove -y

数据清理(卸载软件不会动列表数据,确认不再保留后手动删):

rm -rf /var/spool/mlmmj
# 同时移除 /etc/aliases 中的 mlmmj-* 行并执行 newaliases

清理后核验:ls /usr/local/bin/ | grep mlmmj、dpkg -l | grep mlmmj、ls /var/spool/ | grep mlmmj 均应无输出。

十三、参考链接