SimpleLogin 4.83.1 常用 CLI 命令详解
把 SimpleLogin 4.83.1 自托管的命令面整理成可查、可抄、可验的运维地图:alembic 迁移、init_app 与角色进程、commands/ 维护命令矩阵、Compose 运维与 MailPit+swaks 收发调试,每条命令均给出实测回显。
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)
三、前置条件
- 已完成部署并迁移(
alembic current必须输出b7e2d4a9c1f3 (head),缺表类报错先回部署指南补迁移); - 服务健康:
docker compose ps中 api/email/jobrunner/mail 均为 Up(回显见 5.1 节); - 收发链路可用:已按部署指南完成「外部来信 → 别名 → 真实信箱」验证。
四、各工具详解
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/API | gunicorn(应用默认启动命令,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"}
五、注意事项
- 迁移先行:所有「relation does not exist」类报错都源于跳过
alembic upgrade head;jobrunner 缺job表时会陷入重启循环。 python -m而非路径直跑:commands/下脚本直接python /code/commands/check_user_leaks.py会因包相对导入失败,必须python -m commands.check_user_leaks。- 命令影响面先看源码:
commands/中disable_users_with_forbidden_mx.py、handle_leaks.py等会改写用户状态,执行前先读脚本头部 Usage,确认默认只读再运行。 - cron 任务勿重复调度:
crontab.yml中带concurrencyPolicy: Forbid的任务(如 check_custom_domain)禁止并发,自托管用系统 crontab 时要保证上次未跑完不重入。 - swaks 目标必须是收信域:实验栈对未知域一律拒绝(
default_transport = error),用postmap -q先确认路由再发信,避免 5xx 噪音。 - 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 校验。
七、参考链接
- 官方仓库(CLI 与迁移源码):https://github.com/simple-login/app
- crontab 任务定义:https://github.com/simple-login/app/blob/master/crontab.yml
- swaks 官方站点:https://www.jetmore.org/john/code/swaks/
- MailPit 官方站点:https://github.com/axllent/mailpit
- 站内部署指南(栈的搭建与排障):https://www.quzuo.net/document/tORMRsffUIa3
- 站内配置参数详解(.env 全项):https://www.quzuo.net/document/GtmpLL8YcKL3