honeypot.md: - §3 新增小节:LimitKey 折叠规则(IPv4 原样 / IPv6 折 /64)、邮件里两个地址 各自含义、封禁只在内存里、建封 WARN 的字段与 grep 方法; - §4 注明取证存的是精确地址,不是 /64 聚合键; - §8 排障首行补「邮件说封了但还能访问」的三种原因与判定路径, 并提醒「邮件里的来源 IP 不是封禁键,拿它对 ban_key 永远对不上」。 cli.md:stats 说明改为「来源排行(IPv6 按 /64 聚合)」,补 ip 语义。
18 KiB
TamaBox 命令行操作手册(站方运维)
本项目没有管理后台:站方的全部运营动作只落在三类操作上 —— CLI 命令 / 邮件告警 / 改配置后重启。 这份文档只讲 CLI,覆盖处置、举报取证、批量隐藏与账号管理。
0. 前置
二进制从哪里来
# 仓库根目录构建(与 Dockerfile 是两条不同入口,命令列表必须双向同步)
go build -o tamabox .
# Docker 用的是这一条
go build -o NekoBox ./cmd/
两处都注册在同一份 app.Commands 列表中。新增命令时必须同时改 tamabox.go 和 cmd/tamabox.go,
否则会出现「本机二进制有、线上没有」(或反之),典型报错是 No help topic for '<cmd>'。
⚠️ 替换二进制不等于生效:go build -o tamabox . 只改磁盘文件,正在运行的进程仍是旧映像。
换完之后必须重启(systemd / supervisor / 手动拉起),否则改了代码也看不到变化。
另外 ./tamabox web 在前台立即返回,通常是端口被旧进程占着:
ps -eo pid,lstart,cmd | grep '[t]amabox' # 看进程启动时间,与二进制 mtime 比对
确认手头二进制包含全部命令:
./tamabox --help | sed -n '/COMMANDS/,/GLOBAL/p'
# web / censor / uid / ban / restore / reports / restrict / unrestrict / hide-public / honeypot
想确认某个字符串有没有编进二进制,不要用
strings—— 它默认只输出可打印 ASCII, UTF-8 中文整段被丢弃,strings x | grep 中文恒为 0。 用grep -ac "中文字符串" x。
配置文件
CLI 与 web 读同一份配置,路径由环境变量指定:
NEKOBOX_CONFIG_PATH=/path/to/app.ini ./tamabox reports --domain xxx
必要条件(缺失会直接启动失败):
app.external_urlserver.salt、server.xsrf_key[database]段的 type / host / port / user / password / name
⚠️ CLI 启动时会跑 AutoMigrate(与 web 相同)。这是特性也是风险:迁移有问题时连只读的
reports 都跑不起来。做重要操作前先 pg_dump。
操作前先备份
pg_dump -Fc <dbname> > before-$(date +%F-%H%M).dump
1. 命令总览
| 命令 | 作用 | 是否改数据 | 典型用途 |
|---|---|---|---|
reports |
举报取证 / 待处理列表 / 全站统计 | 否 | 处置前看证据 |
restrict |
施加处置(L1–L4) | 是 | 停止分发、冻结发布、停用页面 |
unrestrict |
撤销全部生效中处置并发解除通知 | 是 | 解封、误操作回滚 |
hide-public |
批量把公开回答转为私密 | 是 | 清理存量挂人内容 |
ban |
永久封禁(删用户+内容) | 是(不可逆) | 严重违规账号 |
restore |
恢复已停用账号 | 是 | 用户申诉后恢复 |
censor |
重新审核历史内容 | 是 | 审核规则变更后复检 |
uid |
为用户生成/补齐 uid | 是 | 数据修复 |
honeypot |
扫描器诱捕取证(假登录页) | 部分是(prune / purge-creds) |
看谁在扫、拿到了什么字典 |
web |
启动 Web 服务 | — | 生产入口 |
除 reports、honeypot hits/stats/paths/size 外,所有写操作都要求交互式输入大写 YES 确认;
脚本化时加 --yes。注意 honeypot prune / purge-creds 的确认词是 yes(小写)。
2. reports —— 处置前的取证
三种互斥用法(--stats / --pending 优先于 --email/--domain)。
2.1 单个箱主的证据包
./tamabox reports --domain 52mxtx1314
./tamabox reports --email [email protected] --days 90 --limit 50
输出包含:
- 当前处置:级别、到期时间、原因
- 近 N 天:被举报 X 条 / 公开回答 Y 条(累计,Z%);举报 W 次,其中待处理 V 次
- 被举报条目:按举报数降序,带题号、内容前 48 字与状态(
待处理 N/已处理) - 历史处置:每条的时间、级别、原因、状态(生效中 / 已撤销 / 已到期)
口径(容易误读,务必看清):
- 分子 = 窗口内被举报的条目数(不是举报次数)。同一条被 5 个人举报只算 1 条。
- 分母 = 该箱主当前累计的公开回答数,不是窗口内产出。 所以 L2 收窄公开之后,分母会变小,比例可能超过 100% —— 这不是 bug,是分母口径问题。 判断「组织化挂人」时优先看绝对条数与指涉对象分布,比例只作参考。
- 已删除的提问不计入(
deleted_at IS NULL)。
2.2 全站待处理举报
./tamabox reports --pending --limit 50
按箱主分组列出所有 status = pending 的举报(不受 30 天窗口限制,方便清理积压)。
2.3 全站维度统计(识别「举报被武器化」)
./tamabox reports --stats --days 7
输出两个 Top 10:
- 被举报最多的箱主(含 domain / 昵称 / 待处理举报数)
- 举报来源 IP 最多(同一 IP 反复举报同一批内容)
怎么读:
| 现象 | 更可能是 |
|---|---|
| 多个 IP 打同一个箱主,且内容指向多个不同对象 | 组织化挂人 |
| 少量 IP 打多个不同箱主 | 举报被武器化(报复性举报) |
| 同一 IP 单点爆发 | 脚本刷量(已由验证码 + 唯一索引限制) |
⚠️ 统计只覆盖待处理举报,且最长回溯 30 天(超期未处理的举报会自动退出统计池)。
2.4 举报计数的前提:客户端 IP
举报去重键是 (question_id, reporter_ip),计数与统计全部依赖真实客户端 IP。
- 站点在反向代理 / CDN 之后时,必须在
[security]配置trust_proxy = true, 并按需配置trusted_proxies。 - 若
trust_proxy = false而站点实际在代理后,所有访客会被识别成同一个代理 IP → 每条内容最多只有 1 条举报,阈值告警永远不会触发。 - 验证方法:从两个不同网络各举报一次,然后
reports --pending看是否出现两个不同 IP。
3. restrict —— 分级处置
./tamabox restrict --domain <domain> --level L1 --days 7 --reason "组织挂人投稿"
./tamabox restrict --email <email> --level L3 --days 14 --reason "复发" --yes
参数:
| 参数 | 说明 |
|---|---|
--domain / --email |
二选一;都填时按 email 查 |
--level |
L1 / L2 / L3 / L4,默认 L1,大小写敏感 |
--days |
生效天数,从当前时间起算,必须 > 0 |
--reason |
必填,写入审计记录(不会展示给用户,只用于内部留痕) |
--yes |
跳过 YES 确认 |
--force |
--days 超过 90 天时必须加,否则直接拒绝执行 |
--no-notify |
不给箱主发处置通知邮件(默认发) |
异常时长与二次确认
--days 不设硬上限(长期封禁是真实需求),但超过 90 天时:
- 不加
--force→ 直接报错退出(多打一个 0 不会被执行) - 交互模式下还要再输入一次级别名(如
L3)才继续 - 脚本里
--force --yes两个显式开关即构成两次确认
处置通知
处置生效后会自动给箱主发一封邮件(按箱主自己的界面语言,四语言齐全),内容只有:
- 级别与级别名称(如 L3 冻结发布)
- 到期时间
- 社区规则链接
- 申诉渠道(页脚「找站长」)
邮件里不包含 --reason 原文,也不引用被举报内容 —— 沿用公告模板的口径:只写级别、期限、依据。
需要「静默止血」(不惊动对方、避免其转移阵地)时加 --no-notify。
邮件发送失败不会回滚处置:只打一条 warn 并在终端提示,处置仍然生效。
级别语义
| 级别 | 名称 | 实际效果 | 生效点 |
|---|---|---|---|
| L1 | 停止分发 | 单题/整箱分享卡不可生成;从首页、探索、标签检索、随机位摘除;热门标签不再出现 | share.go、users.go |
| L2 | 收窄公开 | L1 全部 + 新回答强制私密(单条 UPDATE 同时写回答与 is_private)+ 禁止「设为公开」+ 禁止回答追问 |
question.go、follow_up.go |
| L3 | 冻结发布 | L2 全部 + 禁止回答(仍可收信) | question.go |
| L4 | 页面停用 | 整个 /_/{domain} 全部 302 到 /restricted(单题、私聊、分享卡、JSON 列表接口一并挡住) |
page.go Pager + ListAPI |
设计要点:
- L3 不禁收信。伤害来自「发布」,剥夺收信能力没有治理收益,还会给对方「平台封口」的说法。
- 到期自动解除:判定是
expires_at > now(),没有定时任务,也不需要手工解封。 - 替换语义:同一用户再次
restrict会先撤销旧的生效记录再写新的,任何时刻只有一条生效。 升级(L1→L3)与降级(L3→L1)都走这条路径。 - 处置期间审核严格度锁定为最高档(
strict),箱主无法自行改档;解禁后自动恢复他原本的设置 (users.moderation_strictness全程未被改写)。
副作用(需要提前知道)
- 处置期间箱主在自己后台能看到橙色条幅(级别 + 到期日 + 规则页链接)。
- L1 就强制 strict:L1 只停分发、内容仍能发布,但审核已按最严处理,
正常的吐槽式提问也可能被拦。若线上发现大面积误拒,把
moderation.EffectiveStrictness改成「L1 沿用原设置」即可(一行)。 - 处置会给箱主发通知邮件(见上)。若用
--no-notify跳过,箱主只剩登录后的橙色条幅。 - 邮件发送依赖
[mail]段的 SMTP 配置;未配置时处置仍会生效,但终端会提示发送失败。
4. unrestrict —— 撤销
./tamabox unrestrict --domain <domain>
./tamabox unrestrict --email <email> --no-notify
- 把该用户所有生效中的处置写
revoked_at(保留记录,不删除,审计可追溯)。 - 幂等:没有生效中处置时输出「没有生效中的处置,无需操作」。
- 撤销后:页面/发布/分享卡立即恢复;
hide-public已经转为私密的内容不会自动变回公开, 需要箱主自己逐条「设为公开」。 - 会给箱主发一封「限制已解除」通知(四语言,与处置通知同一模板,无申诉段),
正文里明确写了「私密内容不会自动恢复」。
--no-notify可跳过。
⚠️ 到期自动解除不会发信:那个判定是读表时比较 expires_at,没有定时任务。
如果希望「到期也通知」,只能靠 unrestrict 手工解封,或将来加个调度器。
5. hide-public —— 清理存量内容
# 最推荐:只隐藏「有未处理举报」的公开回答
./tamabox hide-public --domain <domain> --only-reported
# 全部公开回答(激进的兜底)
./tamabox hide-public --domain <domain>
# 只处理最近 30 天
./tamabox hide-public --domain <domain> --only-reported --since 30
| 参数 | 说明 |
|---|---|
--only-reported |
只处理有 pending 举报的题目 |
--since N |
只处理最近 N 天创建的题目;0 = 不限 |
--yes |
跳过确认 |
行为:
- 只动「公开且已回答」的题(
answer != '' AND is_private = false),不碰私密与未回答。 - 执行前打印总数 + 最新 10 条预览。
- 可逆:只改可见性,箱主可逐条改回公开(但该箱主处于 L2 及以上时改不回去)。
- 隐藏成功后自动把相关
pending举报标记为accepted,让它们退出告警统计池。 - 单独用它不足以「停止分发」,建议配合
restrict --level L1。
6. 账号管理(ban / restore)
# 永久封禁:物理删除用户与其内容,邮箱释放。不可恢复。
./tamabox ban --email [email protected] --yes
./tamabox ban --domain xxx --yes
# 恢复「自助停用」的账号(软删除行),可同时重置密码
./tamabox restore --email [email protected]
./tamabox restore --email [email protected] --password 'NewPassw0rd' --yes
两者的区别(涉及唯一索引,容易踩):
- 停用(可恢复):内容物理删除 + 用户行软删除。邮箱/域名继续被占用是有意为之,
恢复靠
restore。 - 封禁(不可恢复):内容物理删除 + 用户行物理删除,邮箱立即可重新注册。
restore --password必须在恢复之后执行,否则改不到(普通作用域查不到软删除行)。
7. honeypot —— 扫描器取证
设计取舍、凭据存储格式、部署要点见 honeypot.md。这里只列命令。
| 子命令 | 作用 | 改数据 |
|---|---|---|
hits |
列出捕获到的凭据(时间倒序,密码默认脱敏) | 否 |
stats |
来源排行(IPv6 按 /64 聚合)+ 路径排行(库)+ 凭据排行(文件) | 否 |
paths |
列出当前生效的诱饵路径 | 否 |
size |
表行数、磁盘占用、凭据文件体积 | 否 |
prune |
按保留期清元数据 + 凭据文件 | 是 |
purge-creds |
删除旧版遗留的明文凭据列(不可逆) | 是 |
./tamabox honeypot hits --since 7d --limit 50
./tamabox honeypot hits --ip 203.0.113.9 --show-password
./tamabox honeypot stats --days 7
./tamabox honeypot paths
./tamabox honeypot size
./tamabox honeypot prune --days 30
./tamabox honeypot purge-creds
要点:
- 凭据不入库。用户名/密码只写在
data/honeypot-creds/creds-YYYY-MM-DD.jsonl(目录 0700、文件 0600),数据库里只有 IP / 路径 / UA / 是否封禁这些元数据。 所以hits/paths不连数据库,DB 挂了也能查。 - 密码默认脱敏(保留首尾各 1 位)。终端输出常被贴进工单或截图,
--show-password只在确有取证需要时开。 hits/stats里的ip是精确客户端地址;封禁用的是shared.LimitKey折叠后的键 (IPv6 为/64前缀)。两者在 IPv6 下不一致是正常的,别拿邮件里的精确地址去对封禁键。purge-creds用于清掉早期版本存在honeypot_hits上的明文列(AutoMigrate 不删既有列)。 执行前先pg_dump;它只处理当前库,历史备份与从库里的明文需要自行清理。- 封禁语义:GET 探测走常规阈值(默认 10 次 / 60 秒),提交凭据才一次即封。 所以「扫了几十次怎么没封」不是 bug —— 见 honeypot.md 的「两条封禁路径」。
8. 典型处置流程
- 发现:收到告警邮件(同一箱主 24h ≥ 8 条 或 7d ≥ 20 条)→ 或箱主后台出现「被举报 N 次」。
- 取证:
reports --domain <domain>看条目、占比、历史处置。 - 冷静期:举报后 24 小时内不作判定(避免被集中举报推着走,也避免情绪化处置)。
- 止血:
restrict --level L1 --days 7 --reason "..."。默认会通知箱主; 若判断通知会导致其转移阵地、需要先静默收口,加--no-notify。 - 清存量:
hide-public --domain <domain> --only-reported。 - 观察:7 天内复发 → 升 L2/L3;双方互挂 → 两边同级别处理,不选边。
- 收尾:到期自动解除(不发信);需要提前解封用
unrestrict(会给箱主发「已解除」通知)。 - 留痕:
reports --domain <domain>的历史处置段落就是处置记录(谁、何时、级别、原因)。
判定「挂人」的三要件(同时成立才处理):
- 可识别性:能让圈内人定位到具体账号/作品/主页(昵称、代称、@、外链、作品标签)
- 贬损或号召规避:带「避雷/挂/别信/辱骂」这类定性,而不是中性描述
- 无公共利益:纯私人纠纷、粉籍立场之争
只成立一两条 → 不处理或仅提示。
9. 排错
| 现象 | 原因 | 处理 |
|---|---|---|
No help topic for 'xxx' |
构建入口不同步(根 tamabox.go vs cmd/tamabox.go) |
两处都注册后重新编译 |
find user: record not found |
--domain 填了昵称/登录名 |
domain 是 URL 里的那一段,/_/{domain} |
| 处置后页面无变化 | 二进制是旧的 / 服务没重启 | --help 看命令在不在;抓线上 HTML 比对标记 |
| 待处理列表是空的 | 举报已被箱主处理(自动 accepted)或超过 30 天 | 正常;必要时直接查库 |
| 告警邮件一直没收到 | [security] security_alert_email 为空(静默跳过,无日志) |
配好并重启 |
| 举报数始终是 1 | trust_proxy 未按部署环境配置,全部识别为代理 IP |
见 §2.4 |
| 箱主说「我改了但没生效」 | 处置期内严格度被锁定 / L2 禁止设为公开 | 看后台条幅;reports 查当前处置 |
--days 超过 90 天 被拒绝 |
防手误设计,需要 --force |
确认时长后重试,或改用常规天数 |
| 处置成功但箱主说没收到邮件 | SMTP 未配 / 发送失败 / 用了 --no-notify |
看终端 warn;[mail] 配好后可 restrict 重发(同级别重设会替换记录) |
蜜罐记录的 ip 是 127.0.0.1 |
trust_proxy 未配,源站把所有人看成同一个代理 IP |
配 trust_proxy;此时一次封禁会封掉整站(见 honeypot.md 的「部署」一节) |
| 整站都返回 403 | 蜜罐把反代 IP 封了(上一条的后果) | 重启进程(封禁是内存态)或等 honeypot_ban_minutes 到期 |
| 换了二进制但行为没变 | 进程没重启,跑的还是旧映像 | ps -eo lstart,cmd | grep tamabox 比对启动时间与文件 mtime |
strings 二进制 | grep 中文 恒为 0 |
strings 只输出可打印 ASCII,中文 UTF-8 被丢弃 |
用 grep -ac "中文字符串" 二进制 |
honeypot 命令不存在 |
二进制旧 / 两个构建入口未同步 | 重新编译后重启进程 |
改完配置必须重启才生效(配置只在启动时读取一次)。
10. 明确不做的事
- 不做自动处置:举报数达标只发邮件,不自动封禁。这个场景里举报本身就是攻击武器。
- 不做申诉后台:申诉走邮件,处置结果由 CLI 写入。
- 不用配置文件当黑名单:无法表达「N 天后自动解除」,最终会变成手工维护灾难。
- 不要在 CLI 之外直接改库:
user_restrictions的写入必须带revoked_at语义, 手工 UPDATE 容易留下多条同时生效的记录(读取端会取最高级别,但不便于审计)。