; ── 关于配置键名 ────────────────────────────────────────────────────────── ; ⚠️ 键名写错时,程序启动会 WARN 提示「存在无法识别的键」并给出最接近的合法键名。 ; 对于少量已知的常见误拼(如 honeypot_retention_days → honeypot_retain_days), ; 程序会自动纠正并在日志里说明纠正了什么。 ; 但请优先写对键名 —— 自动纠正只覆盖已知的误拼,不认识的键一律不猜。 [app] production = true ; 【必填】站点对外访问地址。用于生成 og:url 与邮件中的全部链接 external_url = "https://box.shiroko.one" ; (找回密码、私信通知等),必须以 http(s):// 开头、结尾不带斜杠 ; (程序会自动 TrimRight "/")。域名迁移时只改这一处即可全局生效。 icp = "" ; 站点默认语言:zh-CN / zh-TW / en / ja,留空默认简体中文(访客未设置语言时的兜底) default_lang = "" uptrace_dsn = "" qiniu_access_key = "" qiniu_access_secret = "" aliyun_access_key = "" aliyun_access_key_secret = "" ; 默认分享图背景图链接(1280x720) share_background = "https://your-default-background.jpg" ; 分享卡绘图用的中文字体(相对项目根目录)。字体文件不在仓库里,必须自备一份, ; 否则分享卡接口会返回 500「字体文件未配置或无法加载」。可用 Noto Sans SC / 思源黑体, ; 支持 .otf/.ttf/.ttc;放好后无需重启生效。 font_path = "static/fonts/NotoSansSC-Regular.otf" [security] enable_text_censor = true ; 文本审核服务通道:qiniu(七牛)/ aliyun(阿里云)/ auto(七牛优先、阿里云兜底)。 text_censor_source = auto ; 留空等同于 auto,即引入该配置前的历史行为。 ; 显式选 qiniu / aliyun 时必须在 [app] 配好对应凭据,否则启动即报错 ; —— 避免运行期审核必然失败(审核失败是 fail-closed,拒绝提交, ; 会让发布/私信整站不可用,且现象隐蔽、只在日志可见)。 ; 凭据位置:[app] 的 qiniu_access_key / qiniu_access_secret、 ; aliyun_access_key / aliyun_access_key_secret。 ; ⚠️ 配好凭据 ≠ 可用:阿里云通道走的是「内容安全 2.0 / 文本审核增强版」 ; (接口 TextModeration,接入点 green-cip.cn-shanghai.aliyuncs.com), ; 开通前提只要求【实名认证,个人账号即可】,不需要企业认证。 ; 开通入口:内容安全 2.0 控制台 → 开通「内容审核增强版」按量付费 ; (开通本身不收费,按实际调用量出账;文本审核标准 7.5 元/万次)。 ; 用 RAM 子账号调用需授予 AliyunYundunGreenWebFullAccess;欠费停服同理。 ; 审核场景固定为 comment_detection(公聊评论检测,覆盖涉政/涉暴/涉黄等底线维度)。 ; 单次 content 上限 600 字符,超长文本由代码分段审核(段间带重叠,避免跨段漏判)。 ; ⚠️ 不要再去开通「内容安全 1.0」:它要求企业实名认证,个人账号无法开通, ; 调用会永久返回 code=596 "You have not opened Yundun Content Moderation Service"。 ; 审核失败时日志会打印阿里云返回的 code / msg / requestId 便于定位; ; 首次出现账号级失败(未开通 / 未授权 / 密钥错误)还会额外打印一条一次性告警。 ; 逗号分隔的IP黑名单,支持精确IP(如1.2.3.4)和CIDR(如1.2.3.0/24),留空不启用 ip_blacklist = ; 黑名单IP访问时强制跳转到该URL ip_blacklist_redirect_url = ; 是否信任反向代理设置的 CF-Connecting-IP / X-Real-IP / X-Forwarded-For 头。 trust_proxy = false ; 仅当站点部署在可信反向代理(如 nginx/Cloudflare)之后且代理已覆盖这些头时设为 true; ; 否则保持 false,防止客户端伪造 header 绕过速率限制与 IP 黑名单。 ; ⚠️ 若站点在 Cloudflare 后且需使用下面的自动封禁,必须设为 true, ; 否则解析到的将是 Cloudflare 边缘 IP,自动封禁会误伤所有访客。 ; ⚠️ 蜜罐取证同理:trust_proxy = false 时所有请求的来源都被记成反代地址 ; (常见 127.0.0.1),取证数据失去意义;而且一次蜜罐封禁会连带封掉整站。 ; 蜜罐命中回环地址时启动日志会打一次告警提醒检查这里。 ; ⚠️ 开启 trust_proxy 时,必须配合「Cloudflare Tunnel / Authenticated Origin Pull / ; 防火墙仅放行 Cloudflare / EdgeOne 回源网段」锁定源站,否则攻击者可直连源站并伪造 ; CF-Connecting-IP / EO-Client-IP 头。无需维护 CDN 庞大 CIDR 清单。 ; ; —— 双 CDN(境内腾讯 EdgeOne + 境外 Cloudflare)场景 —— ; EdgeOne 回源会用 EO-Client-IP 携带客户端真实 IP(等效 CF-Connecting-IP), ; 本程序在 trust_proxy=true 时已同时信任这两个权威头。 ; 两家 CDN 并存时必须:① 防火墙仅放行两家回源网段;② 在各家 CDN 的规则引擎中 ; 删除回源请求里「对方 CDN」的权威头(经 EdgeOne 回源删 CF-Connecting-IP、 ; 经 Cloudflare 回源删 EO-Client-IP),否则攻击者可借道另一家伪造客户端身份, ; 绕过限流 / 自动封禁 / 单 IP 投票限制。详细步骤见 outputs/edgeone-cloudflare-dual-cdn-guide.md。 trusted_proxies = ; 逗号分隔的「你自己的」反向代理/负载均衡 IP 或 CIDR(如 127.0.0.1、 ; 10.0.0.0/8)。仅当 TCP 对端落在该清单内时才解析 X-Forwarded-For / X-Real-IP。 ; 留空(默认)则这两个头永不被信任,仅采用 CDN 权威头 CF-Connecting-IP / ; EO-Client-IP(需源站已锁定)。 ; 注意:此清单用于你自己的基础设施,不是 CDN 的全量 CIDR 清单。 ; 自动封禁(fail2ban 风格):检测到批量扫描/探测后临时封禁来源 IP enable_auto_ban = true ; 是否把「未注册路径的 404」计入封禁统计(默认 false)。正常业务 ban_track_notfound = false ; (域名/题目不存在等均走 302 业务跳转,不产生 404 计数)一律放行, ; 仅命中后台扫描特征路径(/admin、/.env 等)才参与统计封禁,避免 ; 共享出口(CDN 边缘 / CGNAT)下正常用户被 404 探测连坐误伤。 ; 如需恢复对纯 404 字典扫描的封禁再置 true。 ; 统计窗口内命中敏感路径(/admin、/.env、/wp-admin 等)的次数阈值 ban_threshold_sensitive = 10 ; 统计窗口内返回 404 的次数阈值(仅 ban_track_notfound = true 时生效) ban_threshold_404 = 20 ; 统计窗口时长(秒) ban_window_seconds = 60 ; 临时封禁时长(分钟),到期自动解除,重启即清零 ban_duration_minutes = 15 ; 触发封禁时发送告警邮件的收件人(复用下方 SMTP 发信配置),留空不发送 security_alert_email = ; ── 蜜罐(虚假后台登录界面)──────────────────────────────────────────── ; ⚠️ 本段一律「注释独占一行、值单独一行」。 ; 配置加载器开启了 IgnoreInlineComment,行尾 `; 注释` 会**并入值**: ; honeypot_max_rows = 200000 ; 说明 → 值变成 "200000 ; 说明" → int 解析失败 ; honeypot_paths = ; 说明 → 值变成 " ; 说明" → 被切成一堆假路径 ; 后果是该项静默回退零值,而日志里只会说「配置值不对」,很难联想到是注释造成的。 ; 本文件其余段落已全部改成这个写法,新增配置项时请保持一致。 ; 开启后,扫描器探测后台路径(/wp-admin、/.ssh、/.env 等)时不再返回 302, ; 而是返回一个仿 WordPress 的假登录页,并把其提交的用户名/密码写入取证文件。 ; 只在「没有任何路由匹配」的路径上生效,真实业务路由零交集。 ; 假页面只含 canary 标记与假表单,绝不含任何真实信息。 enable_honeypot = false ; 追加自定义诱饵路径(逗号分隔),与内置清单取并集。留空 = 只用内置清单。 ; 用 `TamaBox honeypot paths` 可查看最终生效的完整清单。 honeypot_paths = ; 蜜罐命中记录的保留天数(取证用途),到期自动清理。 ; ⚠️ 键名是 honeypot_retain_days(没有 tion)。写错会被静默忽略, ; 程序会尝试自动纠正并在日志里提示,但别指望它每次都猜对。 ; 缺省 30 天;写 0 或负数会回退 30(不表示「永不清理」)。 ; 要关闭自动清理请用 honeypot_auto_clean = false。 honeypot_retain_days = 30 ; 是否启用蜜罐记录的后台自动保留期清理(每小时一轮)。 ; honeypot_hits 是只追加的审计表,写入完全由外部扫描流量驱动, ; 不清理的话表体积只增不减。关闭后仍可手动 `TamaBox honeypot prune`。 honeypot_auto_clean = true ; 蜜罐命中记录的行数上限(第二道闸)。显式写 0 = 不限(会告警提醒表只增不减)。 ; 保留期是时间维度上界,但攻击者可用海量源 IP 在保留期内把表撑爆。 ; 实测单行约 281 字节(含索引,常规 UA),20 万行约 53 MB。 honeypot_max_rows = 200000 ; 「提交凭据」多少次即封禁。默认 1(一次即封):往假后台里填账号密码是证据最强 ; 的行为,抓到一次就足够。⚠️ 不作用于纯 GET 探测 —— 那类请求走 ban_threshold_sensitive, ; 否则扫描器看一眼假登录页就被封,永远走不到提交凭据那一步。 honeypot_threshold = 1 ; 蜜罐封禁时长(分钟)。缺省 1440(24 小时),刻意远长于 ban_duration_minutes: ; 命中蜜罐意味着对方已在提交账号密码,证据强度远高于单纯路径探测。 ; 显式写 0 = 回退 ban_duration_minutes。 ; ⚠️ 蜜罐封禁与 enable_auto_ban 相互独立:即使关掉自动封禁, ; 只要 enable_honeypot = true,蜜罐命中仍会封禁并记录。 honeypot_ban_minutes = 1440 ; 凭据取证文件的存放目录。 ; ⚠️ 捕获到的用户名/密码**不入库**,只追加到这个目录下的按天文件 ; (creds-YYYY-MM-DD.jsonl,一行一条 JSON)。原因:明文账号密码一旦入库, ; 就会跟着进备份、主从复制和任何能连库的报表工具,事后无法彻底清除。 ; 目录会被强制为 0700、文件 0600;相对路径以进程工作目录为基准。 ; 目录不可写时只降级为「不保存凭据」,不影响站点启动。 ; 建议单独挂一个卷,并让它不参与备份。 honeypot_cred_dir = data/honeypot-creds ; 站点默认「内容审核严格度」:lax(宽松)/ standard(标准)/ strict(严格)。 default_moderation_strictness = ; 仅对未单独设置档位的账号生效(账号在设置页选择后以账号设置为准)。 ; 留空回退 strict(最严档):保证升级不会放宽任何拦截。 ; 严格档是三个档位中最严的(拦截面不小于升级前),因此部署后默认拦截面可能略有扩大; ; 若希望默认更少误伤,改为 standard 即可,无需改代码。 ; 三档的拦截范围见 internal/security/censor/policy.go, ; 适用于提问、投票、私聊三类内容。 [server] port = 80 ; 绑定监听地址(含端口)。Cloudflare Tunnel 部署时强烈建议设为 127.0.0.1:, ; 使源站端口仅对本地 cloudflared 可见,即使防火墙误配也无法被外部直连伪造 CF-Connecting-IP。 ; 留空(默认)则监听所有网卡 ":port"。 listen = salt = "" # ⚠️ 必填:密码哈希盐,留空会导致启动失败(已在 conf.Init 强制校验) xsrf_key = "" [database] type = "postgres" user = "nekobox" password = "" host = "127.0.0.1" port = 5432 name = "nekobox" schema = "public" # 仅在已确认需要自动清理重复 email/domain 时开启;默认关闭,避免启动时改写账号数据。 dedupe_unique_fields = false [redis] addr = "127.0.0.1:6379" password = "" [recaptcha] ; recaptcha(默认), turnstile, math(本地算数验证码,国内访问友好) captcha_type = recaptcha domain = "https://www.recaptcha.net" site_key = "" server_key = "" turnstile_style = false [upload] default_avatar = "" default_background = "" # User avatar and background. aliyun_endpoint = "" aliyun_access_id = "" aliyun_access_secret = "" aliyun_bucket = "" aliyun_bucket_cdn_host = "" image_endpoint = "" image_access_id = "" image_access_secret ="" image_bucket = "" image_bucket_cdn_host = "" # User uploaded images. image_endpoint = "" image_access_id = "" image_access_secret = "" image_bucket = "" image_bucket_cdn_host = "" [mail] account = "" password = "" port = 465 smtp = "" [limits] ; ── 全站内容 / 数量上限(运行时配置,修改后重启生效)────────────────────── ; 缺失本段或个别键时按内置默认值运行。长度统一按字符(rune)计数。 ; 键留空或填 0/负数时自动回退内置默认值,不会导致启动失败,但也不会按你的意图生效, ; 请按需填写正整数。本文件启用 IgnoreInlineComment,行内分号注释会被并入配置值, ; 因此下面注释一律独占整行、勿写行内注释。 ; ── 标签与屏蔽词(账号设置页)────────────────────────────────────────────── ; 最多标签个数 max_tags = 5 ; 单个标签最大字符数 max_tag_len = 5 ; 最多屏蔽词个数 max_block_words = 10 ; 单个屏蔽词最大字符数 max_block_word_len = 10 ; ── 投票 ──────────────────────────────────────────────────────────────────── ; 参与者填写的「自定义补充选项」(其他/自定义)最大字符数 custom_answer_len = 200 ; 站长创建投票时单个「选项」文本最大字符数(预设选项) vote_option_len = 300 ; ── 提问 / 回答 / 追问 ───────────────────────────────────────────────────── ; 提问正文最大字符数(访客提交的问题内容) question_content_len = 1000 ; 回答正文最大字符数(箱主对提问的回答,及对追问的回复,共用) answer_content_len = 2000 ; 追问正文最大字符数(提问者被回答后继续追问的内容) followup_content_len = 2000 ; ── 私聊 ──────────────────────────────────────────────────────────────────── ; 单条消息(发送/回复)最大字符数 chat_message_len = 2000 ; 每个私聊会话的消息条数上限(系统硬顶)。用户只能在 1..该值 之间设置自己的 ; 会话条数上限;新注册用户的默认值即该硬顶。 chat_max_messages = 200 ; 单用户每日私信条数上限(用户可设范围 0..该值;0 表示不限制每日条数) daily_message_limit_max = 999 [announcement] ; ── 站内公告:全站每页导航栏下方展示,改完重启生效 ────────────────────── ; content :默认文案,留空 = 关闭公告(任一语言文案非空也视为开启)。 ; 用 \n 表示换行;http(s) 地址会自动变成链接; ; 内容按纯文本渲染,不支持 HTML 标签(写 会原样显示)。 ; content_zh_cn / content_zh_tw / content_en / content_ja: ; 分语言文案,留空则回退到 content;content 也留空时该语言不显示公告。 ; timezone:start / end 里不带偏移的「裸时间」按哪个时区解释,默认 Asia/Shanghai。 ; 可填任意 IANA 时区名(UTC / America/New_York …);二进制已内嵌 tzdata, ; 容器不需要另装时区包。带偏移的 RFC3339 以自带偏移为准。 ; start / end:展示时间窗,左闭右开 —— 到 end 那一刻下线。留空表示该侧不限: ; 只写 start = 定时上线;只写 end = 现在起展示到某时刻;都留空 = 一直展示。 ; 支持 2026-10-25 02:00 / 2026-10-25 02:00:59 / 2026-10-25 / RFC3339 ; (2026-10-25T02:00:00+08:00)。 ; 时间或时区写错不会导致启动失败:日志 WARN 后关闭公告。 ; 例:content = "10 月 25 日 02:00–04:00 机房维护,期间可能短暂无法访问。详情见 https://status.shiroko.one" ; content_en = "Maintenance 02:00-04:00 on Oct 25 (UTC+8). Details: https://status.shiroko.one" ; start = "2026-10-24 00:00" ; end = "2026-10-25 04:00" content = "" content_zh_cn = "" content_zh_tw = "" content_en = "" content_ja = "" start = "" end = "" timezone = Asia/Shanghai [theme] ; 主题系统:只改配色 / 图案 / 整页背景,不涉及布局。 ; 作用范围:提问箱页面(主页 / 单题 / 密码 / 分享卡)、箱主本人的投票页与 /user 后台; ; 首页、探索等站方页面保持站点默认外观。 ; default :站点默认主题(箱主未选择时使用)。填 default 或留空 = 现有外观。 ; available :启用哪些主题,逗号分隔,同时是箱主设置页的白名单; ; 列了但 static/css/themes/.css 不存在的会被忽略并告警。 ; 新增主题:把 static/css/themes/_scaffold.css 复制成 .css, ; 改选择器与令牌值,再把 加进 available —— 不需要改模板。 ; 需要图片素材的,放 static/images/themes//(static/fs.go 已嵌 images/themes 目录)。 default = default available = default,sakura,shiroko,new-sakura ; ⚠️ 没登记在 available 里的主题,即使 css 已随二进制发布也不会出现在设置页; ; 启动日志会打出「[theme] available 未登记以下主题」提醒。,shiroko