Commit Graph
151 Commits
Author SHA1 Message Date
tamakyi e1e840f895 announcement v1 2026-10-22 15:08:24 +08:00
tamakyi c9f22cb81b 修窄屏装饰层压不住作用域块(手机上一直显示桌面版樱花)
@media 不提升特异性:作用域块 html[data-theme-style][data-page-scope="box"] 是 (0,2,1),
媒体查询里写的 html[data-theme-style] 只有 (0,1,1),于是「窄屏装饰层全置 none」这行
从来没生效 —— 手机上照样铺开桌面的花枝与花朵。深色作用域块是 (0,3,1),更压不住。

- 媒体查询里补三条同特异性的通配规则(作用域 0,2,1 / 深色基础 0,2,1 / 深色+作用域 0,3,1),
  媒体块在文件更后,同特异性即胜出。
- 新增 TestNarrowMediaOverridesScopedDeco:外层每处非 none 的 --tb-deco,都要求窄屏里有
  同属性名、属性数不少于它的声明。变异验证过(删掉深层那条立即 FAIL)。
  它同时报出 shiroko 的 900px 块只覆盖浅色 —— 但深色块本就是 none,不算缺陷,故跳过。
- 窄屏改由加强版底纹承担樱花(透明度 ×1.9、格子 180px):桌面那套 0.18~0.34 的透明度
  在 390px 上实测接近纯粉。顶栏与卡片之间那条 35px 带子试过放小花,question/admin 页卡片
  从 y=110 起会切掉一半,最终不放装饰层。
2026-10-22 11:18:29 +08:00
tamakyi 5c0de805c6 new-sakura:花枝朝向与分叉修正,素材包一键导出上传到对象存储
- gen_new_sakura.py:花朵加各自旋转角(原来一律朝上,像贴纸);分叉从垂直下垂改成向右上斜;
  新增 --asset-base / --out —— 填前缀就引用对象存储位图,留空仍是内联 SVG。
- export_assets.py(新增):从 gen_new_sakura 的同一份 SVG 出图(直接 import,不另画一套),
  用无头 Chrome 透明底渲染后转 WebP;--upload 一次传 CSS + 5 张 webp + 生成的 INSTALL.txt,
  公开地址按 data/r2.txt 的 image_bucket_cdn_host(与 app.ini 同名)拼。
- serve.upload_files 支持逐文件 cacheControl。原来硬编码 immutable,而 INSTALL.txt 与镜像
  CSS 的键不带版本 —— 重新生成后浏览器/CDN 一年内都还是旧内容。现在位图 1 天、CSS 5 分钟、
  README 60 秒;selftest 补一条覆盖断言。
- 文档更正两处事实:①自定义域 r2.337845818.best 实测可匿名读(S3 endpoint 仍 403);
  ②服务端有 gzip,内联版 51KB 实际传输 3.9KB,比位图版(2.7KB + 5 图 48.9KB)小得多,
  所以「换位图省 CSS 体积」不成立,位图只在素材要复用到非 CSS 场景时才有意义。
  另记:曾以 immutable 传过的键必须去控制台清缓存,重传不会让边缘失效。
2026-10-22 11:06:13 +08:00
tamakyi 48a1938fb2 新增 new-sakura 主题:纯 CSS 樱花,每个页面位置不同
樱花不依赖任何图片素材:整朵花、带缺口的花瓣、带枝的花朵树都是内联 SVG 的
data URI,写在主题 CSS 里。好处是零请求、任意缩放不糊、换花色只改一个色值;
代价是 CSS 约 50KB(gzip 约 12KB),走 StaticVersion 缓存。

- 六个页面作用域各一套 --tb-deco 位置(global / box / question / admin /
  profile / vote),10 槽三组等长;位置一律「关键字 + 偏移」两值写法。
- 窄屏(≤640px)装饰层全关、底纹缩到 200px:卡片占满宽度后枝条会糊在字上。
- 深色换亮一档的粉、关掉底纹(浅粉压在深底上会变噪点)、背景用渐变避免死黑。
- 亮色 primary 从候选 #c14a72(4.39:1)压深到 #b8436a(4.87:1),
  muted 5.27:1,都踩住 AA;深色对应 8.17:1 / 5.82:1。
- 生成脚本 dev/theme-tools/gen_new_sakura.py(花形、位置表、配色都在里面),
  CSS 是产物,不手改。
- 四份词典加 theme.name.new-sakura;conf/app.sample.ini 的 available 登记。

为什么不用生成器:生成器的装饰层只认图片素材,没有内联 SVG 图层,
见 docs/theme-new-sakura.md「为什么不用主题生成器出图」。
2026-10-22 10:45:39 +08:00
tamakyi 8daa5b0bec 生成器默认监听 0.0.0.0,局域网/手机可直接打开
- serve.py:--host 默认改 0.0.0.0;启动时列出所有网卡地址(0.0.0.0 不能直接点开);
  --host 127.0.0.1 仍是「只给本机」的收紧方式,README 说明公共网络用它。
- 改单线程为 ThreadingTCPServer:上传几 MB 的图会占住连接,单线程时 iframe 预览
  和其它请求都得排队。
- 写接口加同源校验(Origin 与 Host 比对,无 Origin 放行):挡住同网段里「另一个网页
  借访客浏览器发写请求」,curl / 脚本不受影响。
- index.html:提示文案不再写死 127.0.0.1,改用 location.host —— 用局域网地址打开时
  不该反过来提示「请用 127.0.0.1 打开」。
- mock_s3.py 加 --host(默认仍回环),便于在别的设备上联调。
2026-10-21 21:46:34 +08:00
tamakyi 12c4f3e1be 生成器可检测与管理远端主题素材(列表 / 差异 / 覆盖 / 删除)
「传上去」之后还得知道「传得对不对」:同一 slug 传过三次、换过 slug 旧图还在、
重新导出后远端仍是旧版,这些在页面上都看不出来。

- serve.py:s3_request 支持 query;新增 ListObjectsV2(含分页)与 DeleteObject,
  端点 /api/s3/list、/api/s3/delete。
- index.html:远端素材清单按对象键名跟本地比 —— 一致 / 旧图(同名但大小不同)/
  多余(当前主题已不引用)/ 缺失。「补齐差异」只传缺的与旧图,「删除多余」批量删,
  单行可 ↻ 覆盖或 ✕ 删除;删除走 confirm 二次确认。改过 slug 会提示重新检测。
- 修:list 返回完整键而 upload/delete 收相对键,不剥前缀会删到不存在的键
  (S3 回 204,看起来删成功其实没删)。加 relKey() 统一。
- check_sigv4.py 补带 query 的用例(ListObjectsV2 三参数、乱序 token)→ 8/8;
  selftest_upload.py 补列表/分页/幂等删除 → 20 项全过。
2026-10-21 21:29:35 +08:00
tamakyi 9c961b3c05 主题素材可放任意 S3 兼容存储,生成器内测通后一键上传
素材位置改成两选一(本地随二进制 / 自行填 Endpoint 的 S3 兼容存储),
R2、MinIO、B2、OSS-COS 的 S3 端点都只差 Endpoint/Region/路径风格。

- serve.py:手写 SigV4(PUT/GET/DELETE/HEAD),新增 /api/s3/test 与
  /api/s3/upload。测试连接依次验连通、写权限、读回一致、匿名可读,
  失败把 S3 的 <Code> 翻成可执行的建议;上传带 immutable 缓存头。
- index.html:新增「素材托管」面板(模式切换 + 7 个字段 + 测试/上传按钮),
  上传集合与导出共用 usedAssetIds();成功后自动回填「素材前缀」。
  密钥只进 sessionStorage 与内存,不进草稿/工程 json/导出 zip。
- 修:usedAssetIds 返回 Set,误用 length/map 导致「还没有素材」。
- 修:字段填完 700ms 内刷新会丢配置 → 失焦与模式切换时立即落盘。
- 自检:check_sigv4.py 与 aws-sdk-go-v2 signer 逐字节对拍(6/6),
  selftest_upload.py 起假 S3 跑通测试连接→上传→读回(16 项),
  mock_s3.py 供手动点面板用。
2026-10-21 21:08:39 +08:00
tamakyi 6c21184733 主题生成器支持素材前缀(主题素材可放 R2/OSS)
主题素材只有一处引用:主题 css 的 url()(分享卡是 Go 用 imaging 画的,不读素材),
所以只要让 CSS 里的地址指向对象存储即可。

- 导出面板新增「素材前缀」:空 = /static/images/themes/<slug>/(图片随二进制发布,与以前一致);
  填 https://cdn.example.com/themes = CSS 写绝对 URL,zip 里的图片改为「上传用」
- 地址统一由 urlOf() 决定。修了一个重复实现:layerCSS 里复制过一份路径拼接,
  只改 urlOf 会出现「背景走 CDN、装饰还指本地」
- 前缀随意切换不影响预览(预览恒用内联 dataURL);存进工程 json;?assetbase= 可自检
- INSTALL 文案按是否托管分叉,写清图片传到哪、换域名要重新导出
- 新增 docs/theme-assets-cdn.md:三种做法(生成器前缀 / 应用内 301 / 边缘改写)、
  缓存与 ?v=、CORS 不需要、深色无素材、迁移用的 sed 命令
- README 更新;UI 修正:240px 面板里 4 字标签会被 flex 压窄,需 flex:0 0 auto + 84px
2026-10-21 19:32:55 +08:00
tamakyi f37ff28a46 提问管理页面板补卡片底:换主题后内容不再裸铺在场景上
现象:只有 /user/questions 这一页的「卡片」像完全透明的,其它后台页正常。

根因不是主题,是模板本来就没有卡片容器:提问列表是
<div class=uk-margin-medium-bottom> + <hr> 直接铺在页面上,整个模板只有 1 处 .uk-card
(还是投票 tab 里的「我的投票」标题条)。默认主题底色是纯白,所以看不出来;
换成带整页插画的主题后,场景整片透上来,就成了「卡片透明」。

给 .qlist-panel 兜一层卡片底(--tb-surface + 圆角 + 阴影),三个 tab 共用;
面板内已有的一张 .uk-card-default 降成 --tb-surface-2 去阴影,避免卡片套卡片。
≤640px 收紧内边距。与主题无关,默认外观也会多这层卡(和其它后台页一致)。
2026-10-21 19:32:29 +08:00
tamakyi 34a262091b 修 shiroko 单车/步枪素材「白色没抠」:白上白只能全白转透明
根因不是抠图参数:素材是白底+白车架,车架涂装与背景白是同一个值(都是 #f9f9f9),
颜色上无法分离。四角 floodfill 只吃掉外圈,轮圈内、车架三角、车筐、枪与车架之间的白整块留着,
量化就是不透明像素里近白(>240) 占 50%。

- 处置:min(r,g,b)≥238 软过渡 + 饱和度>14 保留彩色 → 白全透明。近白降到 0.0%,
  不透明率 19.4%,29KB;轮辐/车架/车筐成为描边,粉轮圈、蓝枪托、接触阴影保留
- 试过「按近白厚度区分白洞与白涂装」(腐蚀测厚度)→ 失败:轮辐间扇形白楔 9~13px 与车架管
  5~10px 重叠,阈值怎么调都会留白楔,比原图更难看
- 画布保持 397×259 不裁:deco-size 是 auto 259px,宽度按比例算,裁了锚点会漂
- css 图片 ?v= → 20261021a;StaticVersion 20261021c → 20261021d(css 内容变了必须换缓存键)
- docs:assets-spec 补「白上白」取舍与验收项「不透明像素近白占比≈0」,theme-shiroko 素材表同步
2026-10-21 19:18:50 +08:00
tamakyi e9ac72dd7d docs(memory): 复检部署结论(色块已修、四条作用域主题链路、window-size 陷阱) 2026-10-21 19:03:09 +08:00
tamakyi 1f29d3eb2e 修设置页主题色块被模板过滤器替换成 ZgotmplZ
html/template 对 style 属性做 CSS 过滤,linear-gradient(...) 未标记安全会被整体替换成
ZgotmplZ —— 线上设置页四个主题色块一直是灰底(既有问题,不是这次配色改动引入)。

- FuncMap 新增 safeCSS(string → template.CSS);值只来自 internal/theme.swatchOf,
  由两个已校验的色值拼成,不接受用户输入,所以这里放开是安全的
- user/profile.html 的色块改 {{ safeCSS .Swatch }}
- 新增 TestThemeSwatchRenders:渲染 user/profile,断言不含 ZgotmplZ 且含渐变串;
  已做变异验证(去掉 safeCSS 后该用例失败)
2026-10-21 18:53:10 +08:00
tamakyi 27856dd4a2 字号阶梯收尾 + 主色补到 WCAG AA(新增 --tb-on-primary)
一号事项(剩余令牌化):
- templates/user/profile.html 20 处、base.css 9 处 font-size 全部落到 --tb-fs-*
- base.css 导航四档(0.95/0.88/0.82/0.78)合并成「桌面 md → ≤1000 xs」;logo 三档统一 lg
- 有理由保留的:.tb-pin-flag / .md-body code 的 0.9em(相对父级)、验证码 1.4/1.2rem(等宽字形)、
  chat 关闭按钮 40px、password 页 3em、mail/*(邮件客户端不保证 var());行内 12px/0.75rem 等值顺手换掉

二号事项(主色对比度):
- base/default #1e87f0(3.42) → #0e6fd0(4.70);shiroko #4a7fc1(3.69) → #3b6dac(4.74);
  sakura #e5678f(2.80) → #ba3e66(4.70,饱和度压到 .5 保持柔和)。同色相只压亮度,目标 4.7 留余量
- 深色块 muted 之前只对 --tb-bg 算(4.7 以为过了),对压平后的卡片其实 4.31/4.17 → 提到 #a18790 / #7f8ea1
- 新增 --tb-on-primary(亮 #ffffff / 暗 #0b1016):深色模式主色偏亮,白字只有 2.2~3.1:1,
  用同一个令牌兼顾「链接色要深」和「按钮底要深」是矛盾的。.uk-button-primary 与
  .uk-label:not(语义色) 改吃它 → 深色按钮变成浅底深字(6.10 / 8.65 / 7.27)
- 顺手封掉几处主色泄漏:header 验证码 focus、profile .feat-day.is-on、explore 随机投票的 JS outline、
  vote-results 行内色,全部改成令牌(否则换主题时不跟着变)

生成器同步:paletteVars 导出 --tb-on-primary(白字/深字择优);?dump=palette 增加
onPrimaryCr / onPrimaryPickedBest / warnings —— 主色卡中间调时只警告不判失败;
配色面板给橙色提示。_scaffold 令牌 29 → 30。StaticVersion → 20261021c。

验证:TestSwatch 断言同步为 #ba3e66;8 种组合(base 亮暗 + 三主题亮暗)对比度全部 ≥4.5;
go test internal/{theme,templates,conf,i18n} 全绿;生成器 validate/fit/quality/palette/layers/fx/fxpipe 全过。
2026-10-21 18:39:08 +08:00
tamakyi 252b22a342 docs(memory): 记录模板令牌化提交与四处有意的观感变化 2026-10-21 18:28:52 +08:00
tamakyi 001306d940 模板样式令牌化:字号落到 5 档阶梯、圆角改走 --tb-radius
11 个模板里的硬编码字号/圆角换成令牌,为之后的深色与主题一致性打底。

字号(base.css 的 --tb-fs-xs/md/lg/xl/hero = 12/14/16/24/28):
- 0.70~0.88rem、0.72rem 等五六档零碎值 → --tb-fs-xs(标签/meta) / --tb-fs-md(正文、列表、按钮)
- 0.95~1rem → --tb-fs-lg;1.5rem 大数字 → --tb-fs-xl;1.75rem 首页主标题 → --tb-fs-hero
- 以下几处**故意改变了观感**(不是等价替换,先记下来免得以后当 regression 查):
  explore 的 .explore-name 1.2rem → 1rem;.hot-vote-title 0.98 → 0.875;
  首页 .tb-stat-num 手机端 1.25 → 1rem;.hot-vote-action 0.82 → 0.875(变大)。

圆角:
- 8/9/10px → --tb-radius-sm(8) / --tb-radius(12,对话框与气泡);3/4/5/6px → --tb-radius-xs(6)
- 头像 50%、胶囊 999px 保留原样(语义值,不是视觉随意值)

其它:header 验证码输入框边框 #ccc → var(--tb-border);chat 气泡与图片的圆角走内联 var()。

仍未令牌化(留给下一轮):templates/user/profile.html 还有 16 处 font-size、base.css 自身 11 处
(0.78/0.82/0.88/0.95/1.1),邮件模板 mail/* 保持 px 不动(邮件客户端不保证 var())。
2026-10-21 18:28:26 +08:00
tamakyi dc90d96bde 删除生成参考图 shiroko-cutout.png,文档改为「参考图不入库」
- docs/theme-shiroko.md 三处引用不再指向具体文件(它只是 image-to-image 的 image1,从不进 embed)
- 顺手修过时说明:--tb-deco 是 10 个槽位,不是 6 个
2026-10-21 18:24:16 +08:00
tamakyi 8364a2fa3d docs(memory): 记录 test01 删除已提交 2026-10-21 18:20:16 +08:00
tamakyi 2d13c31d62 删除验证用的 test01 主题
test01 是为验证生成器导出的临时主题(css + 2 张 webp),已从仓库移除。
删主题只需删文件:build() 读不到 css 会打印 warning 并从可用列表忽略;
仍选着 test01 的箱主会静默回退到站点默认外观。
2026-10-21 18:20:00 +08:00
tamakyi db4b3fa095 主题体系跟上设计令牌化:派生文字色按对比度下限、主题 muted 补到 AA
生成器(dev/theme-builder):
- paletteVars 的 text-secondary/text-muted 从「往底色混 36%/55%」改成 mixToContrast(5.5/4.5):二分找最淡且达标的位置,文字本身不达标就原样返回
- 半透明卡片先 flatten 压平到底色再算对比度;参考背景取「页面底色 + 压平后的卡片」中更不利的
- 配色面板加读数(正文/次要/淡文字/主色),低于 4.5 变橙;默认 primary #4a7fc1(3.69) → #3d6fac(4.63)
- 新增 ?dump=palette 四种配色回归;README 补配色一节

主题/base(含用户本轮 base.css 改动):
- sakura muted #a08c94(2.8:1) → #78696e(4.61)、shiroko #8794a7(2.76:1) → #656e7a(4.63):主题覆盖令牌,基座的 AA 修复到不了主题页
- 深色顶栏链接补 secondary 覆盖:亮色块已是次要文字色,深色块原来 !important 定成正文色,同一顶栏权重跳一档
- _scaffold.css 令牌清单 23 → 29(补 fs 阶梯与 page-position),muted 说明 AA 下限
- StaticVersion 20261021a → 20261021b
2026-10-21 17:42:43 +08:00
tamakyi 54df166d4e docs(memory): 记录蜜罐文档产出与 path 脏字符的最终定位 2026-10-21 16:26:48 +08:00
tamakyi 3d453749eb docs: 新增蜜罐部署运维文档,cli.md 补 honeypot 命令与排障条目
docs/honeypot.md(新增):
- 生效范围与匹配规则:挂在 NotFound、业务前缀强制剔除、三类匹配方式;
- 配置项表,并单独警告「行尾注释会被并入值」(IgnoreInlineComment 的坑);
- 两条封禁路径:GET 探测走常规阈值、提交凭据才一次即封,附原因
  (GET 就封等于拿不到字典,这是最容易误解的一点);
- 凭据文件格式/权限/保留期,以及为什么不入库(备份、主从、BI);
- trust_proxy 部署必读:回环 IP 会让取证失真且一次封禁封掉整站;
- 升级清理旧版明文列(purge-creds 不可逆);
- 排障表 + 设计取舍(GET 不封、canary 位置、邮件带明文等)。

docs/cli.md:
- §0 补两条实测踩坑:「替换二进制必须重启进程」「strings 不认中文,
  用 grep -ac」;
- 命令总览加 honeypot;
- 新增 §7 honeypot 命令(hits/stats/paths/size/prune/purge-creds),
  原 7/8/9 顺延为 8/9/10;
- 排错表加 5 条:回环 IP、整站 403、进程未重启、strings 误判、命令不存在。
2026-10-21 16:25:58 +08:00
tamakyi 74312fba58 fix(honeypot): cleanPath 处理零宽字符,并在清洗生效时打诊断日志
上一版只用 strings.TrimSpace,实测漏掉「看不见但不算 White_Space」的字符:
  cleanPath("\u200b/wp-login.php") = "/\u200b/wp-login.php"
(零宽没被删,还因为首字符不是 '/' 多补了一个斜杠,拼出更怪的路径)

改为 TrimFunc 同时匹配 unicode.IsSpace 与 U+200B/200C/200D/FEFF,且只处理首尾
—— 中间的零宽字符可能是攻击 payload,取证要保留原样。

另加诊断日志:清洗前后不一致时 WARN,用 %q 打出原始值。
测试环境出现过「记录里 path 带前导空白、本地链路却复现不出来」的情况,
不可见字符必须转义输出才定位得到。

测试:TestCleanPath 补零宽空格 / BOM / 中间零宽字符保留三个用例。
2026-10-21 16:18:25 +08:00
tamakyi 846bb8a3d9 fix(honeypot): 清洗反代链路的脏路径,回环来源 IP 打配置告警
测试环境实测(Cloudflare → nginx → 源站)发现两处:
1. 凭据文件里记录的 path 是 " /wp-login.php"(前导空格);
2. 来源 IP 记成 127.0.0.1。

path:Match 内部会 TrimSpace 所以仍能命中,但落库与假表单 action 用的是原始
req.URL.Path —— 取证数据带脏字符,爆破脚本还可能把表单提交到错误路径。
新增 cleanPath(trim + 补前导斜杠,不改大小写) 在 Serve 入口清洗一次。

IP:记录到回环地址说明站点在反代后面但 [security] trust_proxy 未开启。
此时所有访客在源站眼里是同一个 IP:取证数据失去意义,且一次蜜罐封禁会
连带封掉整站(含普通访客)。新增一次性 WARN(24h 冷却),只提示不改行为 ——
本地直连调试时回环 IP 属正常。sample.ini 的 trust_proxy 段补对应说明。

测试:TestCleanPath / TestIsLoopbackIP 覆盖前导空格、制表符、空串、
无前导斜杠、大小写保持,以及 shared.LimitKey 折叠后的 "::/64" 形式。
2026-10-21 15:54:39 +08:00
tamakyi 5e8decab8f docs(memory): 记录蜜罐审计修复、凭据外置与提交状态 2026-10-21 15:47:13 +08:00
tamakyi c29e1d071f fix(honeypot): 补齐封禁装配与诱捕语义,凭据外置为文件不再入库
一、封禁链路整条没接上(P0,已实测复现)
route.New 的 autoban.New 漏传 HoneypotEnabled/Threshold/Duration,而
RecordHoneypot 首行就是 `if !b.honeypotEnabled { return false }`
→ 蜜罐命中从不封禁、落库 banned 恒 false、IsBanned 永不为真。
同一批改动又把中间件里蜜罐路径的常规计数也跳过了,净效果是蜜罐路径
从「10 次封禁」退化为零计数零封禁 —— 是 regression 不是修复。
处理:抽 newBanner() 作为唯一装配入口并补齐参数;新增 banner_config_test
四个用例(全部经由 newBanner,不再自建 Banner)。变异验证:分别把
HoneypotEnabled 置 false、HoneypotThreshold 置 0,对应用例按预期失败。

二、诱捕语义(审计时漏掉,比上面更影响功能价值)
原实现对**所有方法**计数且阈值 1 → 扫描器 GET 一次假登录页就吃 403,
永远走不到「POST 提交账号密码」那一步,蜜罐的核心价值(拿到字典)归零。
处理:提交凭据 → RecordHoneypot(不受 enable_auto_ban 约束,立即封);
纯探测 → 常规 Record(阈值 10)。新增 TestHoneypotProbeDoesNotBanImmediately,
变异验证:把 GET 也塞进 RecordHoneypot,测试按预期失败。

三、落库边界(实测取证,非推断)
- Path 原样落库而列宽 512 → PG `SQLSTATE 22001 值太长了(512)`,整条证据丢失;
  实测 600 字符失败、500 字符成功;
- 三处按字节截断 → `SQLSTATE 22021 无效的 "UTF8" 编码字节顺序`(中文密码/URL
  可稳定触发)。统一改 rune 截断并按列宽逐项对齐。

四、资源与噪音
- alerted 无界 map → 带 TTL 的 deduper(上限 2 万,过期清理 + 超限硬裁),
  同时承担告警 24h 冷却与纯探测落库采样(每 IP 每分钟一条)——
  GET 洪泛不再变成等量 INSERT;
- credentialKeys 去掉 id/name,普通 JSON 探测不再被误判为凭据提交并触发假告警;
- canary 从 meta generator 移到 HTML 注释,generator 伪装成 WordPress,
  不再同时向扫描器自曝「这是蜜罐」和「这站是 TamaBox」;
- serveLoginFailure 复用 WriteLoginPage,不再手抄响应头(已开始漂移);
- Describer 死代码 → Prefixes(),接到新命令 honeypot paths;
- honeypot_paths 解析挪到 honeypot.SplitPaths(CLI 与 route 共用),
  补前导斜杠、拒绝 "/"(会让所有 404 变假登录页)。

五、凭据外置:数据库不再存用户名/密码
明文账号密码一旦入库就会跟着进备份、主从复制与任何能连库的报表工具,
事后无法彻底清除。改为:
- 新增 honeypot.CredRecord/CredStore:按天文件 creds-YYYY-MM-DD.jsonl,
  目录 0700、文件 0600、坏行跳过、PurgeBefore 只删整天过期的文件;
- db.HoneypotHit 删掉 Username/Password/Fields/RawBody,只留 HadFields 元数据;
- HoneypotStore 删掉 TopCredentials/ListSinceByIP(CLI 改读文件后零调用);
- 新增 LegacyCredentialColumns/PurgeLegacyCredentialColumns:AutoMigrate 不删
  既有列,老库升级后历史明文仍在 → 启动 WARN + `honeypot purge-creds` 显式 DROP;
- 清理器 HoneypotRetention 增加 CredPurgeable 参数,与 DB 记录共用保留期;
  honeypot prune 也一并删过期凭据文件;
- CLI:hits 不再连库(DB 挂了也能查),stats 的凭据排行读文件;
- 配置 honeypot_cred_dir(缺省 data/honeypot-creds),.gitignore 加 /data/。

验证:go build ./... / go vet ./internal/... / go test ./...(含 PostgreSQL)全绿;
新增 credstore_test(追加/倒序/IP 过滤/坏行/Top/Purge/权限)、
TestHoneypotLegacyCredentialColumns(ADD COLUMN 模拟老库 → 检测 → DROP)、
TestHoneypotWritesCredentialsToFile;CLI 端到端手动验证 hits/--ip/--show-password。
2026-10-21 15:43:25 +08:00
tamakyi 566b13eedd fix(conf): 样例配置去掉行尾注释(会被并入值导致静默失效)
conf.Init 用 ini.LoadOptions{IgnoreInlineComment: true},该选项的语义是
**不剥离**行尾注释:`port = 1440 ; 注释` 解析出来的值是 "1440 ; 注释",
int 解析失败,而 MapTo 仍然返回 nil error —— 配置项静默回退零值。

实测照抄 app.sample.ini 的后果:
- text_censor_source 的值带注释,被 validateTextCensorSource 拒绝,直接起不来;
- ip_blacklist / security_alert_email 变成带注释的垃圾字符串;
- honeypot_max_rows / honeypot_auto_clean 静默失效(后者靠 sanitize 兜底才没出事)。

改动:
- 17 处 `key = value ; 注释` 改成「注释独占一行 + 裸值」,保留 CRLF;
- 蜜罐段补 honeypot_cred_dir,以及「提交凭据才封禁」的语义说明;
- 新增 TestSampleIniValuesHaveNoInlineComment:遍历所有段的键,断言值不含 ';'
  (比逐字段断言彻底,新增配置项自动纳入)。
  变异验证:给 ban_window_seconds 加回行尾注释,测试按预期失败;
- keynorm_test 补 HoneypotBanMinutes 的缺省值断言(缺省 1440 / 显式 0 交给 autoban 回退)。
2026-10-21 15:43:13 +08:00
tamakyi bfdc5089c5 docs(memory): 记录 ini 键名静默失效的根因与配置约定 2026-10-21 13:41:49 +08:00
tamakyi d300167a30 fix(conf): 配置键名容错 + 未知键告警 + 蜜罐保留期缺省值修正
现场故障:配置写 `honeypot_retention_days <= 30`,日志却报
「蜜罐自动清理未启用:honeypot_retention_days <= 0」。

根因(实测 ini v1.67.0 行为确认):
1. 键名多一个 t(retention vs retain)→ ini 对未知键**静默忽略**,保留期成 0;
2. `<= 30` 这种写法让 ini 把键名解析成 "honeypot_retention_days <"
   (带尾随比较符),更加匹配不上;
3. 警告文案里写的也是错键名,等于在误导运维往错的键上配。

修复(三层):
- 键名归一化(internal/conf/keynorm.go):修剪首尾空白、剥掉尾随 `<` `>`
  `=` 等比较符残留,并按别名表纠正常见误拼。归一化结果必须命中该段
  合法键名白名单才会被采用 —— 不认识的键不猜,交给未知键告警。
- 未知键告警(internal/conf/unknown.go):启动时逐段比对键名,无法识别的
  键 WARN 出来并给出最接近的合法键名建议。合法键名由反射从结构体 ini tag
  得出(单一事实来源,新增配置项自动纳入,不会脱节)。
- 缺省值语义修正(internal/conf/honeypot_defaults.go):
  * 保留期缺省 30 天(原先零值 0 = 永不清理);显式 <= 0 也回退 30。
    要关闭自动清理请用 honeypot_auto_clean = false。
  * honeypot_auto_clean 缺省 true。原先靠 bool 零值,ini 的 bool 解析是
    「有键才赋值」,未配置与显式关闭无法区分 → 老配置升级上来自动清理
    实际是关着的。现改用 HasKey 判定。
  * honeypot_max_rows 缺省 200000;显式 0 = 不限(会告警提醒只增不减)。
  * honeypot_threshold 缺省 1。

实测 ini 行为(探针程序,已删):
- 键缺失或值非法时 MapTo **不覆盖**预置默认值 → 可安全预置缺省值;
- 键名大小写敏感(Honeypot_Auto_Clean 不生效);
- HasKey 能区分「缺失」与「显式 0」;
- 段不存在时 MapTo 返回 nil 且不修改目标。

顺带清理 app.sample.ini 里的两条死配置(app.title / server.xsrf_expire):
代码中从未读取(站点标题走 i18n),留着会让新检测对官方模板误报。

验证:
- 用现场原始配置端到端复现 → 归一化后保留期 30 天、自动清理开启、上限 20 万,
  日志明确说明「配置键名已自动纠正」;
- 误拼键(honeypot_autoclean / honeypot_max_row)自动纠正;未知键(totally_bogus)
  WARN 告警;
- 显式 honeypot_auto_clean = false 被尊重为关闭;完全未配置时缺省开启;
- 新增 TestSampleIniHasNoUnknownKeys 回归检测:官方样例配置零误报;
- go build ./... + go test -timeout 600s ./... 全绿,go vet 无输出。
2026-10-21 13:41:19 +08:00
tamakyi cbb0c51a14 docs(memory): 记录蜜罐表体积实测数据与 PostgreSQL 死元组/VACUUM 语义 2026-10-21 13:17:06 +08:00
tamakyi 63e851ddae feat(honeypot): 自动保留期清理 + 行数上限 + VACUUM 空间回收 + size 子命令
问题:honeypot_hits 是只追加审计表,写入完全由外部扫描流量驱动(不可预期、
不可节流),而原实现只在手动跑 CLI prune 时才清理,站点运行期表体积只增不减。

实测取证(本地 PostgreSQL 建同构表灌 10.5 万行真实数据):
- 典型行 244 字节(含索引),10.5 万行 24 MB
- 保留期清理对照(行数恒定 10 万,每轮删 25% 插 25%):
  有 VACUUM 22→24MB 收敛;无 VACUUM 22→52MB 持续膨胀
- VACUUM 不缩小文件,只让空间可复用;二次实测(5 万行、连续 4 轮删插)
  行数恒定 5 万、文件稳定 15MB 不再增长

改动:
- 新增 HoneypotRetention 后台清理器(每小时一轮):时间闸 honeypot_retain_days
  + 数量闸 honeypot_max_rows,清理后调 Compact 回收空间
- db: 新增 Compact()(PG 执行 VACUUM (ANALYZE),走 GORM Exec 非事务路径)、
  DeleteOldestBeyond()(跨方言通用)、ListSinceByIP()、Size()
- 去掉 honeypot_hits.provider 无用索引(10 万行占 648kB,全代码库零查询使用),
  并在启动时显式清理既有库上的旧索引(GORM 去 tag 不会自动删)
- 修 CLI hits --ip 过滤 bug:原先取回最近 N 条再在 Go 侧筛,导致指定 IP 的
  历史命中基本查不出来 → 改到 SQL 层过滤
- 新增 CLI honeypot size 子命令:报告行数、磁盘占用、时间跨度,并与两道闸对照
- 新增配置 honeypot_auto_clean / honeypot_max_rows
2026-10-21 13:16:14 +08:00
tamakyi 7d52155411 fix(honeypot): 蜜罐封禁与 enable_auto_ban 解耦 + 泄露扫描后缀匹配
- autoban: 新增 RecordHoneypot 独立通道(不受 enabled 约束)+ 独立阈值/时长;
  IsBanned 不再因 enable_auto_ban=false 而忽略已有封禁
- honeypot: 新增 honeypotSuffixes 后缀匹配,覆盖 /backup.zip、/db.sql、/id_rsa.pem
- 新增配置 honeypot_threshold / honeypot_ban_minutes
- 补测试:autoban/honeypot_test.go、route 侧禁用自动封禁仍封禁
2026-10-21 12:36:33 +08:00
tamakyi 79051acae8 miguan v1 2026-10-21 12:09:28 +08:00
tamakyi 2d4609dc0e docs(memory): test01 排查结论(窄屏全藏 / themeName 回退) 2026-10-20 18:24:21 +08:00
tamakyi 6c014b3ad2 修 test01:窄屏装饰全藏导致素材消失、主题名显示成 i18n key
- 生成器:勾「窄屏隐藏全部装饰层」时给橙色警告(几层会消失)、图层行标「窄屏隐藏」、加层与导出结果再提醒;「窄屏」下拉在被全藏覆盖时标注不生效
- 新增 ?narrow=hide 复现该状态(这是「素材在手机上看不到」的头号来源:@media 把 10 槽全写 none,桌面完全正常)
- 主题名:新增 theme.DisplayName + 模板函数 themeName,i18n 未登记 theme.name.<slug> 时回退 slug;原来模板直接调用 i18n.T,缺词条就显示 "theme.name.test01"
- test01.css 删掉 @media 全 none 段:装饰是 fit 单位,浏览器会自己按视口缩,不需要这段
- StaticVersion → 20261020g(主题 css 内容变了,必须换缓存键,否则浏览器还会用旧文件)
2026-10-20 18:23:21 +08:00
tamakyi 3d0239d8f1 fix themes maker v1 2026-10-20 18:01:09 +08:00
tamakyi 1b027df62e 生成器:实测尺寸取整显示,去掉面板里放不下的源图尺寸 2026-10-20 17:53:10 +08:00
tamakyi 4bd600fb46 主题生成器:素材尺寸按视口自适应 + 导出画质可调
- 尺寸单位 4 档:fit(按高)/fitw(按宽)/px/vh,输出 auto clamp(min(calc(100vh-24px),55%),基准,上限),下限让位视口 → 自适应单位永不把图撑出屏幕
- fitw 补上宽幅素材:fit 只按高度缩,16:9 素材在 390 竖屏仍出 1001px 宽(照裁)
- 视口预设带高度(含 390×844);layerMetrics 统一出 CSS/预览框/溢出检测,图层编辑区显示实测尺寸并报越界,可一键按当前视口修好
- 窄屏每层策略(原样/缩小/隐藏)+ 缩放系数;修 @media 只覆盖全局、独立配置页面在手机上从不生效的特异性 bug
- 导出画质:质量滑块(上限 0.95,Skia 在 q=1 切无损会反常)+ 长边上限,改动后从原图重算
- base.css 新增 --tb-page-position(默认 center center,旧主题不受影响),StaticVersion +1
- 新增 ?dump=fit / ?dump=quality 自检,README 与 _scaffold.css 同步
2026-10-20 17:51:31 +08:00
tamakyi d2a5177afe docs(memory): 图层/背景交互修复与 ensureOwn 约束 2026-10-20 15:57:09 +08:00
tamakyi 937a8901b2 fix(theme-builder): 图层换不了图、背景关不掉、草稿清不掉
三个问题都是交互设计造成的,核心逻辑其实没坏(实测换图是生效的):

1. 「每层都只有这张图、换不了」
   - 加层时 l.asset 默认绑 S.assets[0],连加三层全是同一张;
   - 换图的下拉只在「选中那一层」的编辑器里,用户根本看不出每层能各自换。
   改为:新层不自动绑图(素材池只剩一张时才绑),每行带缩略图 + 「图片」下拉直接换。

2. 「背景图一直保留、关不掉」
   - 「跟随全局」状态下背景下拉是 disabled,用户在箱主页看到背景还在又点不动。
   改为:一律可点,一改就自动把这一页切成独立配置,并把全局当前值复制过来当起点
   (改背景不会顺带把图案和图层弄丢;图层重新发 id,避免两个作用域共用一批 id)。

3. 「上次的数据关闭后还在」
   - 之前只能靠隐藏参数 ?fresh=1 绕过 localStorage 草稿,UI 上没有出口。
   顶栏加「新建」:清空工程 + 删草稿,二次确认。

顺带:
- 「加一层」不再因 inherit 而 disabled(点了自动切独立),改为独立后若全局层数已达 10 会明确提示;
- 跟随全局时不给拖拽框 —— 那时 cfgOf 返回的是 global 对象,一拖就改到全局去了;
- 编辑器里删掉重复的图片下拉,只留尺寸。

回归:?dump=layers 全绿 —— 加层后 asset 为 [null,null]、行内下拉 2 个、
换第 2 层后 [null,blue] 且 CSS 只出现 blue;box 页 inherit 时 addDisabled/bgDisabled 均为 false;
在 box 加层后 inherit=false、本页 3 层而 **全局仍是 2 层**(没被污染);
新建后 assets 0、层 0、草稿删除、scope 回 global。
另外 ?dump=fx 13 项、?dump=validate 空、生成的 CSS 过 TestThemeCSSComplete + TestDecoSlotCount。
2026-10-20 15:56:59 +08:00
tamakyi 927f378d9d docs(memory): 记录图片处理设计,并压缩 MEMORY.md 去掉过期的 6 槽位说法 2026-10-20 14:53:40 +08:00
tamakyi c3d34b53d2 feat(theme-builder): 图片处理——透明度 / 压白 / 去色 / 裁边 / 去白底
素材卡片左上角 fx 按钮进处理面板,非破坏:
- orig 存上传时的原始字节(不重编码),fx 只存参数,每次从 orig 重算。
  「压白 16% → 20%」不会叠加两次压缩;打开看一眼就关一个像素都不动
  (参数没变直接跳过,有损编码反复走会累积模糊)。
- 预览按 420px 宽算(滑杆要跟手),完成时用全分辨率重算;加了序号丢弃过期结果,
  否则快速拖动时旧渲染会覆盖新的。

功能:
- 不透明度 0~100%(CSS 背景图没法单独设 opacity,只能烘焙)
- 压白 v×(1-k)+255×k,与主题里场景的雾度同一公式
- 去色、裁掉透明边
- 去白底:四边漫水 + 封闭白孔检测 + 可选 1px 收边

去白底两个坑(都写进注释了):
- 手内侧 / 两腿之间是闭合白区,四边漫水进不去,得按连通域单独掏(面积≥24)
- 容差默认 16 而不是 22:背景 253、白袜子 237 只差 16,
  容差 30 会把袜子吃穿成一个透明洞

验证(都是跑出来的,不是看缩略图):
- ?dump=fx 13 项算法断言全过:封闭孔开/关、容差 16 保袜子/30 吃穿、
  收边后轮廓 1px、alpha 50%→128、压白 20% 时 237→241、去色后 R=G=B、裁边 80×40
- ?dump=fxpipe 链路:弹窗开关、预览 300×960、参数重开后仍在、
  改参后 dataURL 变、无改动时 dataURL 一个字节不变
- ?dump=fxkey 端到端:白底立绘去白底后透明占比 46.3%,
  与手写 floodfill 抠的 46.6% 差 0.3%
- 生成 CSS 仍通过 TestThemeCSSComplete + TestDecoSlotCount

顺带:工程文件升到 v3(存 orig + fx,未处理时不重复存 dataURL),
读 v2 旧工程时把 dataURL 当 orig 用。新增 ?loadurl= 把项目里的图直接当素材载入。
2026-10-20 14:52:36 +08:00
tamakyi bf1c994c8f docs(memory): 生成器 P0 四项的实现要点与踩到的坑 2026-10-20 13:33:17 +08:00
tamakyi 8ffa7b0ba7 feat(theme-builder): 补 P0 四项——配置留得住、图层可排序、窄屏段、导出前校验
之前只能算「试一次」,关掉标签页全部配置丢失,导出的主题在手机上也是废的。

1) 配置持久化
- 每次改动 700ms 后写 localStorage,打开自动恢复;?fresh=1 从空白开始
- 「保存工程 / 打开工程」落成 theme-<slug>.json(图片 base64 内嵌,单文件可带走)
- zip 里也带一份 json,下次改不用从零搭
- 配额超了只提示一次,并指向 json

2) 图层上移/下移
顺序就是层级(background-image 第一层在最上),配反了以前只能删了重加。
列表标注「最上层 / 第 N 层」。

3) 窄屏段
新增窄屏配置:竖版背景图、隐藏装饰层、断点(默认 900px),导出生成 @media 块。
只写浅色规则——深色块特异性 0,2,1 > 0,1,1,本来就压住它。
实测 390 视口下首层装饰从 url(char.webp) 变成 none,1512 下不变。

4) 导出前校验
slug、图片层是否选了图、是否引用已删素材、层数是否超 10、
生成 CSS 的每处装饰是否真的 10 个值(和 Go 的 TestDecoSlotCount 同规则)。
有问题直接列出来,不用导出后再等 go test。

顺带:面板按使用频率重排(图层→背景→窄屏→图片池→配色);
拖拽框改成虚线+左上角标签,不再用半透明填充罩住人物;示例重复载入不再叠加素材。

验证:示例 CSS 通过 TestThemeCSSComplete + TestDecoSlotCount(27 处声明);
草稿往返(带 demo 保存 → 不带 demo 打开仍 validate 为空)实测通过。
2026-10-20 13:32:26 +08:00
tamakyi 7b532574bb chore: outputs 不再纳入版本控制
.gitignore 里原本写的是 `./outputs` —— gitignore 不认 `./` 前缀,整条规则失效,
于是 247 个产物(预览快照、截图、历史 review 报告)一直被跟踪,
每次跑 TestGenPreview 都会脏一遍工作区。

改成 `/outputs/` 并 git rm -r --cached outputs:索引里删除,磁盘文件全部保留。
克隆者不再拿到这些一次性产物;需要预览快照时跑
TEST_PREVIEW=1 go test ./templates -run TestGenPreview 重新生成。
2026-10-20 12:17:13 +08:00
tamakyi ef18d91920 docs(memory): 页面作用域机制、10 槽位约束与生成器重做的坑 2026-10-20 10:54:34 +08:00
tamakyi ddee221de2 feat(theme): 主题支持按页面分别配装饰,生成器改用真实页面画布
原来的生成器只能配「背景 + 人物 + 道具」三件套,预览还是自己画的仿制页面 ——
跟真实页面不像,也没法给不同页面配不同内容。

机制层:
- internal/context.pageScope() 按路径归类,输出 <html data-page-scope>:
  box / question / admin / profile / vote(首页与 /explore 不加载主题,不列)
- /user/questions 单独归为 admin:题目列表信息密,和设置表单的装饰诉求不同
- base.css 装饰槽位 6 -> 10,背景 8 层 -> 12 层;主题按作用域覆盖时同样写满 10 个
- 新增 TestDecoSlotCount:逐文件逐声明检查 10 个值。少写一个不报错,
  只会让图层与 size/position 错位(整页重复同一张图 / 人物跑到左上角),注释守不住

生成器:
- 画板改成 iframe 加载 outputs/preview-*.html(真实模板渲染),配 serve.py 起本地服务
  解决 file:// 下父页面读不到 iframe DOM 的问题
- 八个页面作用域(去掉不生效的首页/探索),每个可独立配背景与最多 10 层装饰
- 图层类型:图片 / 径向光斑 / 线性光;9 宫格锚点 + 像素偏移,可在画板上直接拖
- 位置统一写 calc(0%|50%|100% ± Npx):百分比是相对 (容器-图片),
  不用 left/bottom 关键字混搭,避免三值语法非法导致整条声明作废
- 配色只填 6 个基础色,其余 4 个必需令牌按方向派生(surface-2 往 text 靠,
  浅色变深、深色变亮,方向自动正确)

配套:
- zz_preview_test.go 增加单题页 / 题目列表 / 投票页预览,作为生成器画板
- docs/theme-page-scope.md 记录作用域取值、选择器特异性与 10 槽位约束
- StaticVersion 20261020e(base.css 变了)

验证:生成器导出的 CSS 通过 TestThemeCSSComplete + TestDecoSlotCount;
真实箱主页计算样式为 12 层对齐;临时主题验证「question 覆盖为 none」生效、
未覆盖时回退全局。
2026-10-20 10:53:44 +08:00
tamakyi feb5b1f78d feat(dev): 主题生成器——选图/拖位置/导出 zip
- dev/theme-builder/index.html:单文件、不联网、图片本地转 WebP
- 预览与线上同机制(pattern + 6 槽 deco + page-image),拖拽反解 calc(50% - Npx)
- 导出 CSS + 图片 + 安装说明,CSS 通过 TestThemeCSSComplete
- 自检:?selftest=1 吐 CSS/zip 字节,?demo=1 载入示例
2026-10-20 10:24:35 +08:00
tamakyi b150e4b49f docs(memory): 素材规格书要点 2026-10-20 09:54:52 +08:00
tamakyi 84c2cab278 docs(theme): 补素材规格书(尺寸/比例/雾度/换图清单) 2026-10-20 09:54:33 +08:00
tamakyi 0eff8dffdb docs(memory): 耳朵/白孔/场景站位与 background-position 百分比陷阱 2026-10-20 09:29:29 +08:00