19 Commits
Author SHA1 Message Date
mole404 4cf737dd42 diag(entrypoint): 迁移改用绝对路径调用 artisan + 失败时自动打印诊断
CI 上那条 `runuser -u www-data -- sh -c 'cd /var/www/html && php artisan migrate --force'`
出现过「零输出 + 退出码 1」,而本机用同一条 CI 播种脚本复刻出的卷跑同样的代码却成功,
一时定位不到环境差异。这次:
  · 迁移改成 `runuser -u www-data -- php /var/www/html/artisan migrate --force`
    (artisan 自己按 __DIR__ 解析 base path,不必 cd,少一层 sh -c 的失败面);
  · 万一还是失败,失败分支会打印诊断:runuser 路径、php -v、artisan --version、
    卷里有没有 storage/logs、database/ 的属主与内容 —— 让日志自己说明原因。
2026-10-02 17:28:52 +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 2da63c7e9a chore(mem): MaxConnectionsPerChild 默认值改回 5(低流量下取最严内存纪律)
老师决定:他这台图床流量约 3 请求/分钟,MCP=5 意味着每 ~3 分钟才退休一个 worker,
换班开销可忽略;换班越勤,单个 worker 允许积累的内存天花板越低。故取回他原来的 5。
(中途试过 30,现按实测与他的偏好改回;env 覆盖机制 APACHE_MAX_CONNECTIONS_PER_CHILD
与 0=永不回收 的语义完全保留。)

同步改回:entrypoint.sh 默认值与旁注、build-image.yaml 6 处期望值(断言 + 自证清单 + 覆盖用例)、
Dockerfile 概览注释、docker-compose.yaml 注释。README 参数表本来就是 5,无需改。

验证:一次性容器集成跑 5 用例(默认→5、env=10→10、env=0→0、脏值 abc→回退 5 且不启动失败、
PHP_MEMORY_LIMIT 仍生效)+ apache2ctl -t = Syntax OK + memory_limit 仍 64M;
全仓 grep 无遗留的 30 期望值;Dockerfile 9 条 md5 自证 9/9。
2026-10-01 18:06:03 +08:00
mole404 5554c28c02 feat(mem): 测试版 —— PHP/opcache 收紧 + MaxConnectionsPerChild 放宽 + CI 断言
本提交只在 test/memory-tuning 分支上,master 保持 = 线上稳定版不动。

- 先量后改:镜像里实际生效的 memory_limit 是 512M(上游 memory-limit.ini),
  不是 PHP 编译期默认的 128M。本轮改为默认 64M,并做成运行期可覆盖:
  entrypoint 新增 PHP_MEMORY_LIMIT(默认 64M),只接受 <数字>[KMG],脏值只忽略该项
  并警告、绝不启动失败,幂等;改 env 即可回 128M/256M,不用重建镜像。
- opcache 实测:配置 128M/10000;真实预热集合只用了 827 个脚本 / 25.0MB;
  全树编译上限 9949 个脚本 / 255.9MB(走不到)→ memory_consumption 收成 64(约实测 2.6 倍,
  省下 64MB 共享内存),max_accelerated_files 保持 10000,validate_timestamps=1 显式写死。
- MaxConnectionsPerChild 默认 5 → 30(减少无谓的 worker 换班),
  APACHE_MAX_CONNECTIONS_PER_CHILD 覆盖机制与 0=永不回收 的语义原样保留。
- CI:Verify published image 新增两条断言(memory_limit == 64M、mpm.conf 有
  MaxConnectionsPerChild 30),沿用先落文件再比较(不碰 pipefail + grep -q 那个坑);
  MPM 自证步骤里的期望值 5 → 30。
- 分支安全:push 触发增加 test/*(保留 master);Promote to latest 本就有
  github.ref == refs/heads/master 的条件(本轮补强注释),分支构建只产不可变 sha- 镜像;
  retag.yaml 只由 workflow_dispatch 触发且硬拒 latest 目标,无需额外守卫。
2026-10-01 12:57:09 +08:00
mole404 7dd45a9339 fix(entrypoint): 版本标记原子化、端口校验兜底、缓存作废对称、运行期自签证书
F2:同步分支排除 .code-revision 并在解包完成后才写标记(原先靠 tar 流带进去,解包中断会留下「新标记+半套代码」永久跳过同步)。F11:WEB_PORT/HTTPS_PORT 校验+兜底,渲染失败保留旧配置而非截成空文件。F13:清编译视图/packages.php/services.php 抽成函数,播种与同步两条分支都调用。F21:不再随镜像发货公开的 snakeoil 私钥,改为入口脚本幂等生成到 /etc/apache2/ssl/(无 openssl 只警告不阻断)。
2026-09-30 06:18:56 +08:00
mole404 c5b74cca9e feat(apache): MPM 参数内置为镜像默认值(低配单用户),支持 APACHE_* 逐项覆盖
背景:镜像此前完全吃 Debian/php-apache 的出厂 MPM 值(prefork: StartServers 5 /
MinSpareServers 5 / MaxSpareServers 10 / MaxRequestWorkers 150 /
MaxConnectionsPerChild 0),低配单用户机上光是常驻 10 个空闲 PHP 子进程就已过重。

① 新增 mpm.conf.template(照 000-default.conf.template / ports.conf.template 的既有套路),
   Dockerfile COPY 进 /etc/apache2/ 并加构建期护栏(模板缺失 / 占位符被删 → 构建失败)。
② entrypoint.sh 在 exec 之前渲染到 /etc/apache2/conf-enabled/mpm.conf —— Debian 的
   apache2.conf 先 include mods-enabled 再 include conf-enabled,后者覆盖出厂 prefork 值。
   内置默认:StartServers 2 / MinSpareServers 1 / MaxSpareServers 3 /
   MaxRequestWorkers 5 / MaxConnectionsPerChild 5 / KeepAlive Off。
③ 六项各自独立覆盖(写哪个改哪个,没写的保持默认),并带校验与兜底:
   非整数 / 越下限 / 一致性不满足(MaxRequestWorkers ≥ MaxSpareServers ≥ MinSpareServers)
   → 该项退回默认值 + [lsky] 警告;KeepAlive 只认 On/Off(大小写不敏感,统一渲染成 On/Off);
   MaxConnectionsPerChild 允许 0(0 = 永不回收,合法值)。
   渲染或落盘失败只警告,绝不影响容器启动(set -eu 下全部走 if 守卫,另挡掉
   「目标路径被目录占住时 mv 静默假成功」那条实测发现的坑)。
④ APACHE_KEEPALIVE_TIMEOUT / APACHE_MAX_KEEPALIVE_REQUESTS 仅在 KeepAlive=On 且显式
   设置时才写入;未设则不写该行,跟随 Apache 发行版默认(5 秒 / 100 个请求)。
⑤ README 新增「Apache MPM」一节:默认值、变量表、脏值策略、默认态日志摘要与自查命令、
   高配参考值、以及「MaxRequestWorkers × 单进程内存 ≈ 可用内存」的估算提醒;
   docker-compose.yaml 的 environment 加注释示例(不改任何生效值)。
⑥ CI 新增真容器步骤「Verify Apache MPM defaults and overrides」:默认态与期望的 6 行
   逐行 diff;单项覆盖生效且其余仍默认;脏值退回默认且日志有警告;KeepAlive=On 时
   「不设 timeout → 两行都不出现」「设了 → 两行出现且值正确」;大小写归一化;
   并跑 apache2ctl -t 确认渲染结果不会让 Apache 报语法错。

本机验证(无 docker,镜像层由 CI 验):bash -n 通过;用抽取的入口脚本真代码跑 15 个场景
(全默认 / 单项覆盖 / 六项全脏 / KeepAlive 各形态 / 前导零 / 一致性破坏 / 目标被目录占住 /
模板缺失)全部 exit 0 且输出符合预期;Dockerfile 的 8 组 md5 自检与补丁字节校验未受影响
(未碰 patches/ 与 src/)。
2026-09-30 00:26:45 +08:00
mole404 a4efab82e8 fix(严重): 默认头像挪出 public/images —— 它撞上了 /images 路由,导致整站打不开
事故:为放默认头像新建了真实目录 public/images/,而「我的图片」页路由正是 /images。
Apache 的文件系统优先,遇到真实目录就 301 跳到 /images/(那里没有 index → 403),
而首页 / 会 302 到 /images → 用户一打开就中招(日志证据:GET / → 302、GET /images → 301)。

修复:
① 头像移到 public/static/default-avatar.svg(static 不在顶层路由里;上游自己也只用
   css/js/fonts/thumbnails 这类不撞路由的目录名),Utils::getAvatar() 同步改路径。
② entrypoint 无条件清掉卷里残留的 public/images/default-avatar.svg 并删空目录 ——
   卷同步是「只增不删」,不做这步旧卷永远残留这个目录。只删我们自己放过的那一个文件。
③ CI 冒烟增加回归断言:/images 首跳不许是 301、跟随重定向必须 200、默认头像必须 200。
2026-09-29 20:41:38 +08:00
mole404 e68476b715 deps: 镜像改用 composer install --no-dev(去掉 40 个开发包)+ 处理连带的包发现缓存
- 开发包(debugbar / ignition / whoops / phpunit / faker / sail / mockery / collision 等 40 个)
  对线上没有用途:既占体积,也是暴露面(debugbar 那类在调试模式下会漏内部信息)
- 连带修复(这是关键,不然会 500):卷里的 bootstrap/cache/packages.php 是"当年装着开发包"时
  生成的,记录了这些包的 ServiceProvider 类名。包没了之后 Laravel 引导时加载该清单会
  Class not found → 整站 500。entrypoint 的同步分支现在会作废 packages.php / services.php
  (两者都是纯缓存,下次请求会按当前 vendor/ 重新生成,删掉无副作用)
- CI 场景 B 新增回归:故意往卷里种一份引用 debugbar 的过期清单 → 断言同步后已被作废;
  并新增断言"镜像里确实没有 vendor/phpunit | barryvdh | spatie/ignition | fakerphp"
- 新增 .github/workflows/retag.yaml:把某个 tag 重新指向**已存在**的镜像(用于修正历史贴错的 tag)。
  安全闸:不允许 latest 当目标、源必须是 sha- 不可变 tag、打印改前/改后 digest
2026-09-28 23:48:54 +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 7ddb15b7bd fix(sync): 改成「按镜像版本标记」条件同步,并修掉首启分支把 .env 带进空卷的 500
老师选定方案甲(按版本标记条件同步),并修掉上一次 CI 抓到的真问题。

1) 修 500(我上一次改动引入的):首启分支还原成与上游逐字一致
   `cp -a /var/www/lsky/* /var/www/html/` + 单独拷 .env.example。
   镜像是带 .env 的(构建时由 .env.example 生成、APP_KEY 为空),
   上一版用 `cp -a /var/www/lsky/.` 把它带进了空卷,Lsky 安装向导撞上已存在的
   空 .env 直接 500 —— 全新建站会挂。现在钉了构建期护栏:首启分支必须保持
   `/*` 形态且必须拷 .env.example,否则构建失败。

2) 版本标记:build 阶段生成 .code-revision(fork sha + 源码 commit + 两个补丁 md5),
   入口脚本比较镜像内与卷内这一份:
     空卷               → 全量播种(并写上标记)
     已有卷 + 标记不同  → 同步代码 + 清编译视图缓存(= 换了镜像)
     已有卷 + 标记相同  → 跳过(不白写整棵应用树,手改保留)
   排除清单不变:.env / storage / database / bootstrap/cache / public/i 一律不碰。
   附带好处:回滚也变成「换一行 image」—— 旧镜像标记不同会自动把代码同步回去。

3) CI 自证重写为三个真实 docker 场景(不过就不 promote latest):
   A) 空卷:安装页 200 + 卷里必须没有 .env(钉住上面那个 500 的根因)
   B) 已有卷 + 旧标记:代码被同步、.env(含权限位)/数据库/上传/config 缓存一项不被碰、视图缓存已清
   C) 已有卷 + 同标记:跳过同步、手动修补保留、对外服务的 JS 仍是补丁版

本地自证:入口脚本三分支仿真 15/15、jsdom 行为测试 25/25、
Dockerfile 全部构建期断言 grep 命中、workflow 三个 run 脚本 bash/sh -n 通过。
2026-09-28 18:59:14 +08:00
mole404 90e151eef0 feat: 长按阈值 500→250ms;入口脚本改为每次启动强制同步代码进已有卷
老师的两个要求:

1) 长按 0.5s 偏长 → LONG_PRESS_DELAY 减半到 250ms。
   - 配套保留「手指移动 >10px 即取消」,用来抵消阈值变短带来的误触风险
   - blade 的资源版本串递增为 ?v=ios-longpress2,击穿 Safari 的启发式缓存
     (否则老浏览器还在用 500ms 那份 JS)
   - test/ 增加两个阈值回归用例(320ms 必须触发、160ms 必须不触发)

2) 升级不再需要人工 cp 文件:entrypoint.sh 在「已有部署」分支里每次启动都把
   镜像代码同步进卷(tar 覆盖式),并自动清掉编译视图缓存。
   - 绝不覆盖:.env / database/(SQLite)/ storage/(上传·日志·会话)/
     bootstrap/cache/ / public/i —— 这五项是站点数据与配置,不是镜像代码
   - Dockerfile 增加静态护栏:排除清单少一项就构建失败
   - CI 新增「Verify forced code sync」步骤:模拟已有部署的卷(塞旧代码 +
     .env/数据库/上传/编译缓存的哨兵文件),断言代码被更新、数据一项没被碰;
     不过就 exit 1,不会 promote 到 latest
   - README 重写升级/回滚章节并写明代价:手工改卷里的代码会失效、回滚必须连镜像一起回滚

自证:本地仿真 9/9(代码更新 + 五项数据保全)、jsdom 行为测试 25/25、
patch 能干净打到上游 911275c、Dockerfile 全部断言 grep 命中。
2026-09-28 18:24:08 +08:00
HisAtri e3691aaf0c 增加SSL配置(已测试,附带自签名证书),解决Nginx反向代理出现的Mixed-Content问题 2024-03-04 02:51:37 +08:00
HalcyonAzure 0a8650b2ba fix. web_port env 2023-04-21 13:36:18 +08:00
HalcyonAzure fa3cf6eabe fix. replace readme 2023-04-21 13:30:48 +08:00
HalcyonAzure 92fa266d1c fix. apache port configuration 2023-04-21 13:05:56 +08:00
HalcyonAzure 3a62103ae3 feat. port specify 2023-04-21 11:41:46 +08:00
HalcyonAzure 6f56686afd 🔧 将权限修改为755 2022-04-01 15:22:39 +08:00
HalcyonAzure 540e5a6c97 Docker init 2022-03-15 19:01:16 +08:00