SimpleLogin 4.83.1 常用 CLI 命令详解

把 SimpleLogin 4.83.1 自托管的命令面整理成可查、可抄、可验的运维地图:alembic 迁移、init_app 与角色进程、commands/ 维护命令矩阵、Compose 运维与 MailPit+swaks 收发调试,每条命令均给出实测回显。

SimpleLoginSimpleLogin邮件服务2026/10/26

SimpleLogin 4.83.1 常用 CLI 命令详解

目标:把 SimpleLogin 4.83.1 自托管的命令面整理成一张可查、可抄、可验的运维地图——数据库迁移(alembic)、应用初始化与角色进程(init_app / cron.py)、维护命令矩阵(commands/ 17 个脚本)、Compose 运维组合与 MailPit+swaks 收发调试组合,每条命令均给出实测回显。已在 Ubuntu 22.04 + SimpleLogin 4.83.1 环境实测。

一、环境信息

  • SimpleLogin:4.83.1(官方发布构建 simplelogin/app-ci:v4.83.1,Python 3.12 虚拟环境位于 /code/.venv)
  • 部署形态:PostgreSQL 12.1 + Postfix 中继 + SimpleLogin 三角色(api/email/jobrunner)+ CoreDNS + MailPit(见站内部署指南)
  • 实测工作目录:/opt/simplelogin/sl(所有 docker compose 命令在此目录执行)
  • 演示账号:admin@mailtest.dev / q1w2e3r4;数据库 sluser / q1w2e3r4
  • 实测域名:sltest.dev(别名域)、mailtest.dev(真实收件域)、outside.dev(外部观察域)

二、工具概览

SimpleLogin 4.83.1 没有单一 sl 总命令(旧版的 sl CLI 已在 4.x 移除),命令面由四类组成:

类别入口用途
数据库迁移alembic(应用环境内预装)升级/查看/回退数据库结构
应用初始化与角色python init_app.py、python email_handler.py、python job_runner.py、python /code/cron.py -j <job>初始化数据、承担角色进程、跑定时任务
维护命令矩阵python -m commands.<脚本名>(/code/commands/ 共 17 个)账号/别名/订阅等专项维护
运维与调试组合docker compose 子命令 + MailPit API + swaks状态、日志、收发链路验证

调用模式统一为「先进入 Compose 运行环境,再 exec 到目标服务执行」,例如:

cd /opt/simplelogin/sl
docker compose exec api alembic current
b7e2d4a9c1f3 (head)

三、前置条件

  1. 已完成部署并迁移(alembic current 必须输出 b7e2d4a9c1f3 (head),缺表类报错先回部署指南补迁移);
  2. 服务健康:docker compose ps 中 api/email/jobrunner/mail 均为 Up(回显见 5.1 节);
  3. 收发链路可用:已按部署指南完成「外部来信 → 别名 → 真实信箱」验证。

四、各工具详解

4.1 数据库迁移:alembic

查看当前版本(alembic current):

docker compose exec api alembic current
b7e2d4a9c1f3 (head)

查看迁移轨迹(alembic history,实测前 5 条):

docker compose exec api alembic history 2>/dev/null | head -5
4a9f8c2e1b3d -> b7e2d4a9c1f3 (head), email canonical indices
3ee37864eb67 -> 4a9f8c2e1b3d, Add credential metadata columns to fido table
f3d65fe0b5b4 -> 3ee37864eb67, Add flags field to mailbox
3ffdea52697d -> f3d65fe0b5b4, Add display name to auto create rule
9e80159405af -> 3ffdea52697d, deleted alias id

升级到最新(alembic upgrade head):首次部署与版本升级都执行它,迁移文件由 migrations/env.py 自动从 /code/.env 读取 DB_URI:

docker compose run --rm api alembic upgrade head

单步回退(alembic downgrade -1):仅用于升级后故障回滚,会删除新版本引入的列/表,生产先备份数据库再执行。

4.2 应用初始化与角色进程

init_app.py(应用初始化,幂等):加载内置 PGP 公钥、把 EMAIL_DOMAIN 注册为官方别名域。重复执行不产生副作用(实测输出第二行即证明):

docker compose run --rm api python init_app.py

实测输出(输出节选(实测首尾)):

2026-10-26 11:30:14,187 - webapp - DEBUG - 1 - "/code/init_app.py:39" - load_pgp_public_keys() -  - Finish load_pgp_public_keys
2026-10-26 11:30:14,189 - webapp - DEBUG - 1 - "/code/init_app.py:45" - add_sl_domains() -  - sltest.dev is already a SL domain

角色进程启动命令(与 Compose 中的 command: 一一对应,手工排障时可用 docker compose run --rm api <命令> 临时拉起一份):

角色启动命令说明
Web/APIgunicorn(应用默认启动命令,wsgi:app)监听 7777
收信处理python email_handler.py监听 20381,Postfix 的收信路由目标
异步任务python job_runner.py处理 Job 队列
健康检查python monitoring.py需 NewRelic 配置,生产可选

cron.py(定时任务执行器):4.83.1 的定时任务由外部调度器按 crontab.yml 调度,cron.py -j <任务名> 执行单个任务。命令帮助(实测):

docker compose exec api python /code/cron.py -h
usage: cron.py [-h] [-j JOB]

options:
  -h, --help         show this help message and exit
  -j JOB, --job JOB  Choose a cron job to run

crontab.yml(应用环境内 /code/crontab.yml)定义了 18 个任务,常用的如下表(完整清单以文件为准):

任务名调度作用
send_undelivered_mails每 5 分钟重投未送达邮件
check_custom_domain每 4 小时巡检自定义域 MX/验证状态
check_hibp / notify_hibp每 4 小时 / 每日数据泄露库比对与提醒
delete_old_monitoring / delete_logs / delete_old_data每日清理监控与日志数据
notify_trial_end / notify_premium_end每日订阅到期提醒
clear_alias_audit_log / clear_user_audit_log每小时审计日志滚动清理

自托管用系统 crontab 调度即可,示例(每 5 分钟重投未送达邮件):

( crontab -l 2>/dev/null; echo '*/5 * * * * cd /opt/simplelogin/sl && docker compose run --rm api python /code/cron.py -j send_undelivered_mails >> /var/log/sl-cron.log 2>&1' ) | crontab -
crontab -l | tail -1
*/5 * * * * cd /opt/simplelogin/sl && docker compose run --rm api python /code/cron.py -j send_undelivered_mails >> /var/log/sl-cron.log 2>&1

4.3 维护命令矩阵(commands/)

17 个维护脚本统一用 python -m commands.<脚本名> 调用(必须用 -m 模块方式,直接给文件路径会因相对导入失败)。清单(ls /code/commands/ 实测):

alias_partner_set_flag_and_clear_note.py
backfill_abuser_data.py
backfill_email_log.py
check_oidc_identity_drift.py
check_paddle_passthrough_abuse.py
check_user_for_forbidden_mx.py
check_user_leaks.py
disable_users_with_forbidden_mx.py
emulate_dummy_load.py
find_abuser_by_domain.py
handle_leaks.py
mark_aliases_as_partner_created.py
recalculate_user_flag_alias_create_from_partner.py
replace_noreply_in_contacts.py
schedule_sync_subscription_job.py
send_lifetime_user_events.py
send_plan_change_events.py

按用途归类:

  • 反滥用:find_abuser_by_domain.py、backfill_abuser_data.py、check_paddle_passthrough_abuse.py、check_user_for_forbidden_mx.py、disable_users_with_forbidden_mx.py;
  • 数据泄露(HIBP):check_user_leaks.py、handle_leaks.py;
  • 数据修复/回填:backfill_email_log.py、replace_noreply_in_contacts.py、recalculate_user_flag_alias_create_from_partner.py、mark_aliases_as_partner_created.py、alias_partner_set_flag_and_clear_note.py;
  • 事件与订阅:schedule_sync_subscription_job.py、send_lifetime_user_events.py、send_plan_change_events.py;
  • 身份漂移巡检:check_oidc_identity_drift.py(默认只读报告,--fix 才修复);
  • 压测模拟:emulate_dummy_load.py(勿在生产执行)。

实测一个只读巡检(无 OIDC 登录数据时快速返回):

docker compose exec api python -m commands.check_oidc_identity_drift

实测输出(输出节选(实测首尾),中间为运行告警,可忽略):

>>> init logging <<<
2026-10-26 11:43:08,619 - webapp - DEBUG - 33 - "/code/app/utils.py:16" - <module>() -  - load words file: /code/local_data/words.txt
/code/.venv/lib/python3.12/site-packages/spf.py:1724: SyntaxWarning: invalid escape sequence '\d'
  """Quote the value for a key-value pair in Received-SPF header field
no client_user row found

4.4 Compose 运维命令

状态总览(docker compose ps,实测):

docker compose ps --format 'table {{.Name}}\t{{.Service}}\t{{.Status}}'
NAME                            SERVICE     STATUS
simplelogin-v4.83.1-api         api         Up 27 minutes
simplelogin-v4.83.1-db          db          Up 32 minutes (healthy)
simplelogin-v4.83.1-dns         dns         Up 8 minutes
simplelogin-v4.83.1-email       email       Up 26 minutes
simplelogin-v4.83.1-jobrunner   jobrunner   Up 29 minutes
simplelogin-v4.83.1-mail        mail        Up 21 minutes
simplelogin-v4.83.1-mailpit     mailpit     Up 29 minutes (healthy)

日志追踪(邮件链路第一入口是 mail 服务的 Postfix 日志):

docker compose logs -f api email jobrunner mail
docker compose logs mail 2>&1 | grep -E 'status=sent|reject' | tail -3

mail 服务的投递成功日志实测样例(激活邮件投递):

postfix/local[3611]: 4YvZqp0kz8z2xVt: to=<admin@mailtest.dev>, relay=local, delay=0.09, delays=0.05/0.01/0/0.03, dsn=2.0.0, status=sent (delivered to maildir)

单服务重启(改 .env 后三个应用角色都要重启):

docker compose restart api email jobrunner
Container simplelogin-v4.83.1-email Started

重建自构建服务(改了 Postfix 配置后):

docker compose up -d --build mail

停止与清理:

docker compose down              # 停栈,宿主目录数据保留
docker compose down --remove-orphans   # 连同残留实例一起清理

4.5 MailPit 收件观察 + swaks 投递测试组合

该组合用于闭环验证「外部来信 → 别名 → 转发」与「反向别名回信」两条链路,不依赖真实公网邮箱。

第一步:swaks 模拟外部发件人投递(mail 服务内置 swaks,目标为别名域,实测):

docker compose exec mail swaks --to github.news@sltest.dev --from someone@outside.dev \
  --server 127.0.0.1 --h-Subject "Your GitHub receipt 2026" \
  --body "Hello from external mailbox someone@outside.dev" 2>&1 | grep -E '<-|250' | head -6
 <-  220 mail.sltest.dev ESMTP Postfix (Ubuntu)
 ->  MAIL FROM:<someone@outside.dev>
 <-  250 2.1.0 Ok
 ->  RCPT TO:<github.news@sltest.dev>
 <-  250 2.1.5 Ok
 ->  DATA
 <-  354 End data with <CR><LF>.<CR><LF>
 <-  250 2.0.0 Ok: queued as 4YvZqp1tN4z2xW0

第二步:读真实信箱验证转发(mailtest.dev 为中继的本地投递域,转发的邮件落在 admin 用户的 Maildir,可直接核对改写后的 From 与 DKIM 头):

docker compose exec mail sh -c 'F=$(ls -t /home/admin/Maildir/new/ | head -1); echo "file: $F"; grep -E "^(Return-Path|From:|To:|DKIM-Signature|X-SimpleLogin-Type)" /home/admin/Maildir/new/$F | cut -c1-120'
file: 1793013245.V52Ia41760M643163.a3f54c93383d
Return-Path: <sl.lmycyibsfqqdenjtgm3dgnc5.jvslckkxqmjzs@sltest.dev>
From: "someone at outside.dev" <someone_at_outside_dev_necwoch@sltest.dev>
To: github.news@sltest.dev
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=sltest.dev;
X-SimpleLogin-Type: Forward

第三步:MailPit 观察外部域收件(outside.dev 的来信全部落在 MailPit,Web 控制台 8137 端口,API 实测):

curl -s http://127.0.0.1:8137/api/v1/messages | python3 -c "import json,sys; d=json.load(sys.stdin); print('total:', d['total']); [print(m['ID'], '|', m['From']['Address'], '->', [t['Address'] for t in m['To']], '|', m['Subject']) for m in d['messages'][:5]]"
total: 2
1liDGHH1YnxP | github.news@sltest.dev -> ['someone@outside.dev'] | Re: Your GitHub receipt 2026
544sszuQdzOjJBld7pFS96 | noreply@sltest.dev -> ['real@outside.dev'] | Please confirm your mailbox real@outside.dev

第一行即「反向别名回信」的实证:从真实信箱回复转发信后,外部联系人收到以别名为发件人的回信。读取单封正文并提取其中的确认链接:

curl -s 'http://127.0.0.1:8137/api/v1/message/544sszuQdzOjJBld7pFS96' | python3 -c "import json,sys,re; d=json.load(sys.stdin); m=re.search(r'http://sltest\.dev:8135/dashboard/mailbox_verify\?[^\"\\s<)]*', d['Text']); print(m.group(0))"
http://sltest.dev:8135/dashboard/mailbox_verify?mailbox_id=2&code=L9TxMevwLX3UVNTUuUUKWQ

第四步:DNS 与 Postfix 路由诊断(收信异常时按此顺序查):

docker compose exec mail postmap -q sltest.dev pgsql:/etc/postfix/pgsql-transport-maps.cf
docker compose exec mail dig @10.89.63.53 sltest.dev MX +short
docker compose exec mail nc -z 127.0.0.1 25 && echo smtp-listen-ok
smtp:[email]:20381
10 mail.sltest.dev.
smtp-listen-ok

4.6 REST API 快速自检

命令面之外,管理 API 是批量操作的主力(API Key 在仪表盘「API Key」页创建)。实测创建随机别名:

AK=<你的 API Key>
curl -s -X POST http://127.0.0.1:8135/api/alias/random/new \
  -H "Authentication: $AK" -H 'Content-Type: application/json' \
  -d '{"mode":"word"}'
{"alias":"undesired_ageless166@sltest.dev","creation_date":"2026-10-26 11:48:54+00:00","creation_timestamp":1793015334,"disable_pgp":false,"email":"undesired_ageless166@sltest.dev","enabled":true,"id":6,"latest_activity":null,"mailbox":{"email":"admin@mailtest.dev","id":1},"mailboxes":[{"email":"admin@mailtest.dev","id":1}],"name":null,"nb_block":0,"nb_forward":0,"nb_reply":0,"note":null,"pinned":false,"support_pgp":false}

带错误 Key 的鉴权失败回显(实测):

{"error": "Wrong api key"}

五、注意事项

  1. 迁移先行:所有「relation does not exist」类报错都源于跳过 alembic upgrade head;jobrunner 缺 job 表时会陷入重启循环。
  2. python -m 而非路径直跑:commands/ 下脚本直接 python /code/commands/check_user_leaks.py 会因包相对导入失败,必须 python -m commands.check_user_leaks。
  3. 命令影响面先看源码:commands/ 中 disable_users_with_forbidden_mx.py、handle_leaks.py 等会改写用户状态,执行前先读脚本头部 Usage,确认默认只读再运行。
  4. cron 任务勿重复调度:crontab.yml 中带 concurrencyPolicy: Forbid 的任务(如 check_custom_domain)禁止并发,自托管用系统 crontab 时要保证上次未跑完不重入。
  5. swaks 目标必须是收信域:实验栈对未知域一律拒绝(default_transport = error),用 postmap -q 先确认路由再发信,避免 5xx 噪音。
  6. MailPit 仅限实验:8137/8138 端口无认证,生产环境不要把它暴露到公网。

六、常见问题(FAQ)

Q1:docker compose exec api alembic current 报 KeyError 或连不上数据库。 alembic 必须以 docker compose 方式在栈内执行(依赖服务名 db 解析与 /code/.env 挂载),脱离运行环境直跑必然失败。

Q2:jobrunner 一直重启,日志 sqlalchemy.exc.OperationalError: relation "job" does not exist。 迁移未执行完。docker compose exec api alembic current 若为空,执行 docker compose run --rm api alembic upgrade head 后重启 jobrunner。

Q3:python /code/cron.py -j stats 报 unknown job。 任务名以 -j 严格匹配 app/monitoring.py 或 cron 注册表中的名字,先 grep -n '"stats"' /code/app/monitoring.py 2>/dev/null 或直接查 crontab.yml 中的 command: 行确认任务名拼写(如 delete_old_monitoring、check_custom_domain)。

Q4:用 API Key 调接口返回 {"error": "Wrong api key"}。 鉴权头为 Authentication: <key>(无 Bearer 前缀);Key 在仪表盘「API Key」页创建,泄露后可在页面吊销重建。

Q5:转发邮件没有出现在真实信箱。 按 4.5 节第四步顺序自查:postmap -q 确认路由 → dig 确认 MX → mail 服务日志找 reject 行。闭环栈内最常见的是路由查询失败(Postfix chroot 问题,修复见部署指南 FAQ)与自定义域未通过 DKIM 校验。

七、参考链接