13 Commits
Author SHA1 Message Date
mole404 8137ee1e04 fix(install): 安装向导默认数据库类型改为 SQLite;README 挂载路径改用 $PWD
两件事:
1) 安装向导(src/resources/views/install.blade.php)
   · 数据库类型的选项顺序:SQLite 提到第一个(= 浏览器默认选中),MySQL/PostgreSQL/SQL Server 保持原序。
     镜像自带 pdo_sqlite,开箱即用;README 第 55 行本来就写着「默认 SQLite」,这次把代码对齐文档。
   · 页面加载时主动触发一次 change:那个 change 处理器同时负责「按类型显示/隐藏连接字段」,
     不触发的话默认选中的 SQLite 旁边还挂着 MySQL 那套字段(127.0.0.1 / 3306 / root)与端口,
     看着像选错了,SQLite 的说明文字也不会出现。
   · tools/diff-vs-upstream.sh 的 KNOWN_DEVIATIONS 里该文件的理由补上这一条(文件本来就在清单里)。

2) README(只改文档,不动任何 compose 文件)
   · 挂载卷路径 /root/lsky-pro/data → $PWD/data(compose 片段)与 "$PWD/data:/var/www/html"(docker run 片段);
     仓库自带的 docker-compose.yaml 本来就用 $PWD/web 这套写法,风格统一。
   · 补一句提醒:compose 文件里别写 ~(不经过 shell 不会展开)。
2026-10-02 19:18:56 +08:00
mole404 c07b18e95a test(upgrade): 自动迁移用例降级为「仅本机可跑」(AUTO_MIGRATE_TEST=1) 2026-10-02 17:34:01 +08:00
mole404 3b74c8aae8 fix(entrypoint): pending 判定不走 Laravel(坏/外来的配置缓存会打死 artisan)
CI 第一次跑这个特性时红了,根因是一个真实存在的坑:
站点自己的 bootstrap/cache/config.php 一旦是坏的或外来的(CI 真升级用例里那个
**故意写坏**的哨兵文件就是),任何 artisan 命令都会报
`Target class [files] does not exist` —— 于是 migrate:status 读不到任何东西,
pending 被误判成 0,自动迁移静默不发生。

改法:SQLite 的 pending 判定改成**纯只读 PDO** 比对「盘上迁移文件」vs「记账表里的
行」,完全不经过 Laravel(连配置缓存都不读),因此对坏缓存免疫;其它数据库(MySQL /
Postgres)仍走 migrate:status(那类站点不会有本地 config 缓存文件这回事)。

顺带在 CI 用例的 4b 段里把那个哨兵配置缓存挪开:真站点的配置缓存要么不存在、
要么是有效的;它在前面那段是用来验证「同步不许覆盖站点自己的配置缓存」的,
两种用途不能混在一个卷上。

本机实测(拿生产库只读副本造的真 v2.1 卷,409 张图):
  · 干净卷 → 自动补 2 个迁移文件 + 检测到 2 条 pending + 备份 + 两张表 DONE,
    数据行指纹与升级前**逐字节一致**,容器退出码 0
  · 坏配置缓存仍在时 → 迁移如实报错、只打警告,容器照常启动(退出码 0)
  · 表在但记账行被删的半迁移状态 → 同样只警告不崩,下次启动自动重试
2026-10-02 17:20:44 +08:00
mole404 4d75333320 feat(entrypoint): 旧版/上游站点升级时自动补迁移 + 版本号升到 v3.2
背景:同步分支刻意排除 ./database(保护线上 SQLite),于是镜像里新增的迁移文件
永远进不了卷 —— 用户按 README 只做 `pull && up -d` 时新表不会出现(Nothing to
migrate)→ 新功能 500。此前「从上游 v2.1 / 旧版镜像升上来」必须人工拷迁移文件再手跑
migrate(2026-10-01 发标签功能时就是人工做的)。

现在 entrypoint 每次启动自动收尾,且处处以「不伤数据、不挡启动」为前提:
  ① 守卫先行:SQLite 走只读探测(file:...?mode=ro,不启 Laravel、不写库文件),
     其它库走 migrate:status;只认「有 migrations 记账表的真站点」,空卷/手搓库/非标
     安装一律跳过,绝不自作主张建表。
  ② 只补**缺失**的迁移文件(逐个判存在再 cp -p,绝不覆盖用户自己改过的那份)。
  ③ 真有 pending 才备份 SQLite 三件套(保留最近 3 份)再迁移 —— 没有 pending 就一句话
     结束,免得每次重启白备份一份库。
  ④ 一律降权到 www-data 执行(root 跑会把 -wal/-shm 造成 root 属主 → 站点写不进库)。
  ⑤ 任何失败只打日志、绝不 exit:本脚本 set -eu,非 0 冒泡出去容器就起不来(配合
     restart: unless-stopped 即无限重启)。失败时下次启动自动重试。
  开关:AUTO_MIGRATE=0 关闭(默认开)。

CI:tools/upgrade-test.sh 新增「4b) 自动迁移用例」——构造真 v2.1 形状的卷(migrations
记账表里只有上游 10 条 + 卷里没有那两个 fork 迁移文件),重启新镜像后断言:迁移文件被
自动补进卷、tags/image_tag 自动建好、记账涨到 12、**哨兵数据指纹逐字节不变**、迁移前
自动备份存在。

版本号:src/config/app.php 的 fork 版本串 v3.1 → v3.2。
2026-10-02 17:12:56 +08:00
mole404 c242bfb12c fix(tags): 代码审查发现的 6 条问题全修 + 补真机/静态断言
审查(含新加的打标部分)发现并修掉:
1. 改名/删除标签会清空选中 ✗ —— refreshAfterTagChange() 走 setTags() → resetImages(),
   会清空图片墙并 ds.clearSelection(),把用户正在打标的这批图连同弹窗勾选态一起丢掉
   (老师问过的「编辑/删除会整页刷新」)。改为 patchCardsTag() 就地同步(卡片角标 + data-json
   + 详情卡 chip);只有「被删的标签正用作筛选项」这种结果集真的变了的情况才调 setTags()。
2. 删图片/删用户不清 image_tag / tags ✗ —— 两个迁移的注释都写着「关联行由应用代码显式删除」,
   图片这条路径没落实,残留行会无声累积。UserService::deleteImages() 补 detach();
   后台删用户时一并清标签与关联行。(本机 SQLite 未启用 PRAGMA foreign_keys,不能指望级联。)
   附带 tools/check-tag-integrity.php:只读自检残留行,退出码非 0 即为异常。
3. 标签名进 data-json(单引号属性)未转义 ✗ —— 名字里带 ' 就能提前闭合属性(自伤型 XSS 面)。
   三处注入(列表渲染 / 弹窗行 / syncCardTags)统一先 escapeHtml 再写。
4. 弹窗里几个 axios 调用没有 catch ✗ —— 网络错误或 422(名字超 64 字)会静默无反应。
   统一走 apiErrMsg() 取后端 message;三个标签名输入框补 maxlength=64。
5. 后端 trim() 在校验之后 ✗ —— 只输入空格会建出空名标签。TagRequest 加 prepareForValidation()。
6. 两处硬编码路径 ✗(action="/user/tags/__id__"、axios.delete('/user/tags/'+id))→ 改用 route()。

测试:
- jsdom 118/118(新增 7 条:改名的局部同步、data-json 转义、maxlength+catch、
  deleteImages/删用户的清理、TagRequest trim;测试台补 renderTagRoutes 渲染带 id 的路由)。
- 真机 70/70(新增 #2c:改标签名后选中不丢 / 图片墙不重载 / 角标就地换名,
  6 项断言,在整轮末尾单开 context 跑,避免本机 1vCPU/965MB 下的页面 OOM)。
- 真机实证:图片挂 1 条关联 → deleteImages → 残留 0;check-tag-integrity.php 干净 ✓。
- tools/diff-vs-upstream.sh:已知偏离清单补登 UserService.php / Admin\UserController.php,
  校验通过(103 项,恰好等于实际偏差)。
- patches/ios-longpress.patch 按原范围(3 个文件)重新生成。
2026-10-02 14:59:40 +08:00
mole404 51b6658007 feat(tags): 图库标签适配(筛选/详情/角标/批量修改/标签管理)+ 下线「图片权限」界面口径
界面侧
- 图库页:原「设置权限」按钮、权限筛选下拉、详情卡「权限」行全部下线,换成标签:
  顶部标签多选筛选(后端 AND,带「清除筛选」)、详情卡标签行(可增/可删/现场新建)、
  卡片左下角标签角标(pointer-events-none,不抢选中与预览)、批量「修改标签」
  (桌面工具栏 + 手机 ⋯ 菜单两套入口,行内 44px,添加/移除三态循环)
- 新增「标签管理」弹窗:新建 / 重命名 / 删除(删除二次确认,文案说明会从所有图片上移除);
  入口两处:标签筛选下拉底部「管理标签」、图片右键菜单「管理标签」
- 用户设置页删掉「图片默认权限」整块;后台图片页删详情「权限」行与搜索语法
  is:public/is:private 帮助行;接口文档删 permission 参数行
- 连带:UserSettingRequest 的 configs.default_permission 由 required 放宽为 nullable
  (表单不再提交该项,不放宽则保存设置直接 422;上传默认权限仍取库里已有值)
- 后台搜索后端也不再见识别 is:public / is:private(删除该 match 分支与 ImagePermission 引用)

后端
- permission 列与既有接口形状保持不动;仅 ImageController 的列表/详情序列化补 tags 字段
  (卡片角标与详情卡需要),标签相关接口沿用上一步已就绪的 GET/POST/PUT/DELETE user/tags
  与 PUT user/images/tags

测试与产物
- 新增 test/tag-feature.test.mjs(88 条:静态契约 + jsdom 行为,含筛选 AND/清除、详情增删新建、
  批量三态与 payload、标签管理增删改、右键菜单注入、文案标准化、无「公开/私有」残留)
- images-modal / swal-theme 里钉住旧「权限」口径的断言随之更新
- 重建前端产物(public/css/app.css),重钉 Dockerfile 自证 md5(routes/web.php、
  images.blade.php 与 .code-revision),重生成 patches/ios-longpress.patch
- tools/diff-vs-upstream.sh 的已知偏离清单补登记本批改动文件
2026-10-01 22:02:03 +08:00
mole404 c9a4ea1471 docs: README 整篇重写为介绍 + 部署;补 CI/构建卫生
README(659 行 → 176 行):只保留「这是什么」与「怎么部署/维护」——快速开始、首次安装、
升级(注明不需要 --force-recreate)、回滚(给真实存在的 sha-<commit> 标签)、备份与恢复
(整目录 tar,并说明 SQLite 开了 WAL 会短暂出现 -wal/-shm)、常见配置(端口/证书路径/
APACHE_* 六个变量与实际默认值)、许可与致谢。不再记录任何修复史或差异逐条。
所有具体值(镜像名、容器名、端口、卷路径、WEB_PORT/HTTPS_PORT、MPM 默认值、PHP/Laravel
版本、回滚标签)都对着源码与实机逐条核过。

构建与 CI:
- .dockerignore 补 src/storage/logs/*(+ 保留 .gitignore 占位):本机开发日志会被
  COPY src/ 带进镜像(含本机绝对路径与错误栈)。用一份 moby 语义 matcher 验过排除生效、
  且不影响 fontawesome 字体等构建必需品。
- update-composer-lock.yaml:两处 curl … | php 改成与 Dockerfile 同款「先落盘 → 官方
  installer.sig → 校验 96 位 hex → sha384sum -c - → 再执行 → 删临时文件」(哈希不写死);
  注释里的 9.52.17 与「5 条公告」校正为 9.52.22 / 4 条(OSV 实查)。
- build-image.yaml:扩展自证那段的结论文案改成与实际一致(硬断言只有 pdo_sqlite,
  其余仅打印供人工比对)——sqlite3 对本站不是必需,钉死它会在无害变动时误红。
- tools/vendor-upstream.sh:去掉不存在的 test/run.sh,改为 cd test && npm test。
2026-09-30 09:03:04 +08:00
mole404 9ca9d9aa57 fix(security): 收窄信任代理,堵住伪造 X-Forwarded-For 绕过按 IP 限流
上游默认 $proxies = '*'(信任所有转发头)。而外层 nginx 用的是
$proxy_add_x_forwarded_for —— 追加而不是覆盖 —— 客户端自己带的
X-Forwarded-For 会排在列表最前面,Laravel 取客户端 IP 时又会采信它。
于是所有按 IP 限流的地方(Breeze 登录失败计数、throttle 中间件)都能靠
伪造一个头无限重置:实测写 '*' 时应用解析出的客户端 IP 就是伪造进去的值
(1.2.3.4),改成下面这组之后解析出真实客户端(203.0.113.9)。

- $proxies 改为 ['127.0.0.1', '::1', '172.16.0.0/12', '10.0.0.0/8', '192.168.0.0/16']:
  只列私有网段而不是写死某个网关 IP —— 容器网络重建后网关会变,写死会静默失效
  (届时应用不再认转发头,所有人被当成同一个 IP)。公网攻击者不可能以私有地址
  连进来,所以这样信任是安全的:从右往左找第一个不受信地址,得到的仍是真实客户端。
- 实测副作用检查:HTTPS 判定不受影响(X-Forwarded-Proto 仍被采信),
  正常用户解析出的客户端 IP 与改动前完全一致(只是伪造的头不再生效)。
- 已登记进 tools/diff-vs-upstream.sh 的已知偏离清单(第 9 组),README 同步。
2026-09-30 08:34:42 +08:00
mole404 5020c4f01a ci,ui,docs: 响应头断言 + 真升级用例基线动态化 + 钉 SHA 升级 + 顶栏对齐微调
CI:
- Verify published image 步骤新增两条断言:响应头不得出现 X-Powered-By、Server 不得带
  具体版本号(沿用『先落文件再 grep』,不碰 SIGPIPE 那个坑);两条都双向验证过
  (只注 PHP ini 或只注 Apache conf 都会红)。
- setup-node 换到 v7.0.0(node24)、upload-artifact 换到 v7.0.1(node24),
  同时清掉第三个 workflow update-composer-lock.yaml 里那个 node20 的 upload-artifact;
  其余 docker/* 与 checkout 本来就是 node24,不动。全部钉不可移动的 commit SHA。
- tools/upgrade-test.sh:真升级用例的基线从写死的 tag 改成动态解析(匿名读 GHCR
  tags/list,逐个查镜像 config 的 created 时间取最新,并排除本次 commit 与自身),
  不依赖 tags/list 的返回顺序;解析失败明确失败(不再静默变绿),仍可用仓库变量覆盖。
  防漂移校验(tag 名里的 sha vs 镜像自带 .code-revision 的 fork_sha)原样保留。

顶栏(src/resources/views/layouts/header.blade.php):
- 折叠按钮 44×44 → 32×32(mt-3 保持 56px 顶栏内垂直居中)。
- 标题组的 pl-10 位置补偿改为 pl-4 md:pl-0:顶栏与页面内容共用同一个 x-container、
  同断点内边距相同,所以 md 以上去掉补偿即等于内容左缘(实测 Δ=0.0);
  窄屏内容左缘落在按钮底下,物理无法对齐,退回 pl-4(按钮右缘 +8px)。
- 已重建前端产物(public/css/app.css,新增 .pl-4/.pl-0/md:pl-0),钉住产物未变。

工具/文档:
- KNOWN_DEVIATIONS 同步到本轮全部有意偏离;README 补差异清单、CI 说明与验收清单。
2026-09-30 08:00:46 +08:00
mole404 235f72e68c ci: 真升级用例 + retag digest 校验 + OSV 依赖门禁 + 修试构建 digest 冲突
- build-image.yaml:试构建时按请求版本自动从 Registry API 解析 php-cli / php-apache 的
  index digest 一并传参。实测 tag@digest 时 digest 优先且 tag 被静默忽略 → 旧写法会构建出
  默认版本、再被『PHP 版本校验』判红,白烧一次全量构建。另加 deps-scan job 作为 build 前置。
- 新增 tools/upgrade-test.sh:旧镜像 → 新镜像真升级用例(独立容器 / 卷 / 端口 18090),
  断言数据面逐字节不变(.env / installed.lock / SQLite 文件与指纹 / 缩略图 / 编译缓存)、
  代码确实换成新镜像那一版、终局日志必须是『已同步进卷』而非『跳过同步』。
  本机真跑通过,并已用变异副本证明用例真能红(改数据面会红、tag 被挪会红)。
- retag.yaml:retag 后断言目标 digest 与源逐字一致,不一致则打印两个 digest 并失败。
- osv-scanner.toml:11 条已知且接受的告警入库(laravel 9.x 那几条上游无修复版本、
  dev-only 与 test-only 各若干),新增漏洞会让 CI 红;抽掉白名单实测确实会红。
2026-09-30 07:20:43 +08:00
mole404 5865d04816 docs(tools): diff-vs-upstream 改为已知偏离清单,README 同步
旧判据是『除三个补丁文件外不许有别处与上游不同』,而上游之后整个前端换过,
实际有意偏离已达 92 个文件 → 旧脚本会把它们全报成手滑改动。
改成按 7 组写明每个文件为什么偏离的已知偏离清单:内容不同的集合必须恰好落在清单内,
否则失败;并额外报告『登记了但已无差异』的过时条目。
2026-09-30 07:20:43 +08:00
mole404 74bde44224 fix(boot): 空卷播种改用 tar;compose 与工具脚本的几处加固
- entrypoint 首启播种:cp -a /var/www/lsky/* → tar(cp 的通配符不带顶层点文件,
  以后新增顶层点文件会静默漏掉;tar 与同步分支行为一致,仍排除 .env)
- 版本标记改为最后写(它同时是 CI 的「播种完成」信号,必须等文件都落盘)
- Dockerfile 的 entrypoint 静态断言同步更新
- docker-compose.yaml:去掉硬编码 root 密码的 mysql:5.7 示例,改为注释掉的正规模板
  (密码取环境变量、镜像不用已 EOL 的 5.7),默认说明 SQLite
- tools/vendor-upstream.sh:擦除 src/ 前加安全闸(src/ 有未提交改动就中止,
  这是唯一不可逆操作,避免把 src/ 下的自有改动静默抹掉)
- README:回滚 tag 警示(别用 :911275c… 这类历史 tag)、CI 试构建与迁移守卫说明
2026-09-28 23:06:37 +08:00
mole404 7c3188e25a refactor(源码): 应用源码 vendored 进 src/ + 上游 pin 升级到 38d52c46(2025-11-24)
上一版是「钉死上游 commit、构建时拉 archive、再用 overlay/ 叠加补丁」。但上游已明示停止维护
(README 里 2024-12-28 起挂着警告:「开源版本已停止维护,不再进行新特性更新和 bug 修复」),
所以把源码放进本仓库:构建不再依赖 github.com/lsky-org 长期可用,之后要改 PHP 代码直接改
src/ 提交即可,不必再维护叠加机制。

1) 上游 pin:911275c(2023-05-16)→ 38d52c4609eb85236b45ac75acac2ced55174953(2025-11-24)。
   中间 32 个提交只动了 9 个文件,**数据库迁移 0 处**,所以升级不碰站点数据(SQLite 结构不变)。
   吃进三个真实修复:图片格式转换时不同图片共用同一个临时文件(并发上传会变成同一张图,
   默认配置即触发)、加水印后丢质量参数、SVG 支持;另有 laravel 9.52.4→9.52.17、
   phpseclib 3.0.19→3.0.43、commonmark 2.3.9→2.6.0、aws-sdk-php 等一串依赖安全升级。

2) vendored 源码:src/ = 上游 38d52c46 的完整树(304 个文件、8.1MB,按 commit 精确复制,
   文件数与上游 git 树一致)。我们的三个补丁文件就地打上补丁,取消 overlay/。

3) Dockerfile:去掉 curl 下载 archive 那一层,改成 COPY src/;自证拆成两组 ——
   「自证 1 上游纯净性」ImageService.php / convention.php / public/index.php / routes/web.php /
   composer.lock 必须等于上游 md5;「自证 2 补丁产物」三个补丁文件 md5 + 关键标记 grep。
   快照被误改、补丁丢了,构建期就拦下。

4) 新增维护脚本:tools/diff-vs-upstream.sh(拉上游快照做全树对比 + 白名单校验:除三个补丁文件
   外不许有别的改动,同时可重生成 patches/ios-longpress.patch)、tools/vendor-upstream.sh
   (换 pin 时重新 vendor,自动保留补丁文件并打印上游侧差异)。

5) 测试与文档:jsdom 测试默认路径改指 src/public/js/context-js/context-js.js,新增两条结构自检
   (两份 context-js.js 必须一致、blade 必须带 ?v= 版本串)→ 70/70 通过;README 重写
   「代码在仓库哪里、怎么改、怎么升 pin」,并修正许可证说明(应用 GPL-3.0 / 打包 AGPL-3.0)。

数据安全逻辑一字未动:entrypoint.sh 仍是「版本标记不一致才同步」,排除
.env / storage / database / bootstrap/cache / public/i。换镜像后容器自动把新代码同步进卷。
2026-09-28 22:36:48 +08:00