# TamaBox 命令行操作手册(站方运维) 本项目**没有管理后台**:站方的全部运营动作只落在三类操作上 —— **CLI 命令 / 邮件告警 / 改配置后重启**。 这份文档只讲 CLI,覆盖处置、举报取证、批量隐藏与账号管理。 --- ## 0. 前置 ### 二进制从哪里来 ```bash # 仓库根目录构建(与 Dockerfile 是两条不同入口,命令列表必须双向同步) go build -o tamabox . # Docker 用的是这一条 go build -o NekoBox ./cmd/ ``` 两处都注册在同一份 `app.Commands` 列表中。**新增命令时必须同时改 `tamabox.go` 和 `cmd/tamabox.go`**, 否则会出现「本机二进制有、线上没有」(或反之),典型报错是 `No help topic for ''`。 确认手头二进制包含全部命令: ```bash ./tamabox --help | sed -n '/COMMANDS/,/GLOBAL/p' # web / censor / uid / ban / restore / reports / restrict / unrestrict / hide-public ``` ### 配置文件 CLI 与 `web` 读同一份配置,路径由环境变量指定: ```bash NEKOBOX_CONFIG_PATH=/path/to/app.ini ./tamabox reports --domain xxx ``` 必要条件(缺失会直接启动失败): - `app.external_url` - `server.salt`、`server.xsrf_key` - `[database]` 段的 type / host / port / user / password / name ⚠️ CLI 启动时会跑 **AutoMigrate**(与 web 相同)。这是特性也是风险:迁移有问题时连只读的 `reports` 都跑不起来。做重要操作前先 `pg_dump`。 ### 操作前先备份 ```bash pg_dump -Fc > before-$(date +%F-%H%M).dump ``` --- ## 1. 命令总览 | 命令 | 作用 | 是否改数据 | 典型用途 | |---|---|---|---| | `reports` | 举报取证 / 待处理列表 / 全站统计 | 否 | 处置前看证据 | | `restrict` | 施加处置(L1–L4) | 是 | 停止分发、冻结发布、停用页面 | | `unrestrict` | 撤销全部生效中处置并发解除通知 | 是 | 解封、误操作回滚 | | `hide-public` | 批量把公开回答转为私密 | 是 | 清理存量挂人内容 | | `ban` | 永久封禁(删用户+内容) | 是(不可逆) | 严重违规账号 | | `restore` | 恢复已停用账号 | 是 | 用户申诉后恢复 | | `censor` | 重新审核历史内容 | 是 | 审核规则变更后复检 | | `uid` | 为用户生成/补齐 uid | 是 | 数据修复 | | `web` | 启动 Web 服务 | — | 生产入口 | 除 `reports` 外,所有写操作都要求交互式输入大写 `YES` 确认;脚本化时加 `--yes`。 --- ## 2. `reports` —— 处置前的取证 三种互斥用法(`--stats` / `--pending` 优先于 `--email/--domain`)。 ### 2.1 单个箱主的证据包 ```bash ./tamabox reports --domain 52mxtx1314 ./tamabox reports --email user@example.com --days 90 --limit 50 ``` 输出包含: - **当前处置**:级别、到期时间、原因 - **近 N 天:被举报 X 条 / 公开回答 Y 条(累计,Z%);举报 W 次,其中待处理 V 次** - **被举报条目**:按举报数降序,带题号、内容前 48 字与状态(`待处理 N` / `已处理`) - **历史处置**:每条的时间、级别、原因、状态(生效中 / 已撤销 / 已到期) 口径(容易误读,务必看清): - 分子 = 窗口内**被举报的条目数**(不是举报次数)。同一条被 5 个人举报只算 1 条。 - 分母 = 该箱主**当前累计**的公开回答数,**不是**窗口内产出。 所以 L2 收窄公开之后,分母会变小,比例可能超过 100% —— 这不是 bug,是分母口径问题。 判断「组织化挂人」时优先看**绝对条数**与**指涉对象分布**,比例只作参考。 - 已删除的提问不计入(`deleted_at IS NULL`)。 ### 2.2 全站待处理举报 ```bash ./tamabox reports --pending --limit 50 ``` 按箱主分组列出所有 `status = pending` 的举报(不受 30 天窗口限制,方便清理积压)。 ### 2.3 全站维度统计(识别「举报被武器化」) ```bash ./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` —— 分级处置 ```bash ./tamabox restrict --domain --level L1 --days 7 --reason "组织挂人投稿" ./tamabox restrict --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` —— 撤销 ```bash ./tamabox unrestrict --domain ./tamabox unrestrict --email --no-notify ``` - 把该用户**所有生效中**的处置写 `revoked_at`(保留记录,不删除,审计可追溯)。 - 幂等:没有生效中处置时输出「没有生效中的处置,无需操作」。 - 撤销后:页面/发布/分享卡立即恢复;`hide-public` 已经转为私密的内容**不会**自动变回公开, 需要箱主自己逐条「设为公开」。 - **会给箱主发一封「限制已解除」通知**(四语言,与处置通知同一模板,无申诉段), 正文里明确写了「私密内容不会自动恢复」。`--no-notify` 可跳过。 ⚠️ **到期自动解除不会发信**:那个判定是读表时比较 `expires_at`,没有定时任务。 如果希望「到期也通知」,只能靠 `unrestrict` 手工解封,或将来加个调度器。 --- ## 5. `hide-public` —— 清理存量内容 ```bash # 最推荐:只隐藏「有未处理举报」的公开回答 ./tamabox hide-public --domain --only-reported # 全部公开回答(激进的兜底) ./tamabox hide-public --domain # 只处理最近 30 天 ./tamabox hide-public --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`) ```bash # 永久封禁:物理删除用户与其内容,邮箱释放。不可恢复。 ./tamabox ban --email user@example.com --yes ./tamabox ban --domain xxx --yes # 恢复「自助停用」的账号(软删除行),可同时重置密码 ./tamabox restore --email user@example.com ./tamabox restore --email user@example.com --password 'NewPassw0rd' --yes ``` 两者的区别(涉及唯一索引,容易踩): - **停用(可恢复)**:内容物理删除 + **用户行软删除**。邮箱/域名继续被占用是**有意为之**, 恢复靠 `restore`。 - **封禁(不可恢复)**:内容物理删除 + **用户行物理删除**,邮箱立即可重新注册。 - `restore --password` 必须在恢复之后执行,否则改不到(普通作用域查不到软删除行)。 --- ## 7. 典型处置流程 1. **发现**:收到告警邮件(同一箱主 24h ≥ 8 条 或 7d ≥ 20 条)→ 或箱主后台出现「被举报 N 次」。 2. **取证**:`reports --domain ` 看条目、占比、历史处置。 3. **冷静期**:举报后 24 小时内不作判定(避免被集中举报推着走,也避免情绪化处置)。 4. **止血**:`restrict --level L1 --days 7 --reason "..."`。默认会通知箱主; 若判断通知会导致其转移阵地、需要先静默收口,加 `--no-notify`。 5. **清存量**:`hide-public --domain --only-reported`。 6. **观察**:7 天内复发 → 升 L2/L3;双方互挂 → **两边同级别处理,不选边**。 7. **收尾**:到期自动解除(不发信);需要提前解封用 `unrestrict`(会给箱主发「已解除」通知)。 8. **留痕**:`reports --domain ` 的历史处置段落就是处置记录(谁、何时、级别、原因)。 判定「挂人」的三要件(同时成立才处理): - **可识别性**:能让圈内人定位到具体账号/作品/主页(昵称、代称、@、外链、作品标签) - **贬损或号召规避**:带「避雷/挂/别信/辱骂」这类定性,而不是中性描述 - **无公共利益**:纯私人纠纷、粉籍立场之争 只成立一两条 → 不处理或仅提示。 --- ## 8. 排错 | 现象 | 原因 | 处理 | |---|---|---| | `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` 重发(同级别重设会替换记录) | 改完配置**必须重启**才生效(配置只在启动时读取一次)。 --- ## 9. 明确不做的事 - **不做自动处置**:举报数达标只发邮件,不自动封禁。这个场景里举报本身就是攻击武器。 - **不做申诉后台**:申诉走邮件,处置结果由 CLI 写入。 - **不用配置文件当黑名单**:无法表达「N 天后自动解除」,最终会变成手工维护灾难。 - **不要在 CLI 之外直接改库**:`user_restrictions` 的写入必须带 `revoked_at` 语义, 手工 UPDATE 容易留下多条同时生效的记录(读取端会取最高级别,但不便于审计)。