From 3b74c8aae8ebc157c2320fa554dd3f5e9d3ebae4 Mon Sep 17 00:00:00 2001 From: mole404 <248403041+mole404@users.noreply.github.com> Date: Fri, 2 Oct 2026 17:20:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(entrypoint):=20pending=20=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E4=B8=8D=E8=B5=B0=20Laravel=EF=BC=88=E5=9D=8F/=E5=A4=96?= =?UTF-8?q?=E6=9D=A5=E7=9A=84=E9=85=8D=E7=BD=AE=E7=BC=93=E5=AD=98=E4=BC=9A?= =?UTF-8?q?=E6=89=93=E6=AD=BB=20artisan=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) · 表在但记账行被删的半迁移状态 → 同样只警告不崩,下次启动自动重试 --- entrypoint.sh | 46 +++++++++++++++++++++++++++++++++++-------- tools/upgrade-test.sh | 5 +++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 7635c0f..ab89a75 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -434,14 +434,44 @@ auto_migrate() { fi # ③ 真的有 pending 才备份 + 迁移(否则每次重启都白备份一份库,纯属糟蹋磁盘) - status=$(runuser -u www-data -- sh -c 'cd /var/www/html && php artisan migrate:status' 2>&1) || true - case "$status" in - *Pending*|*pending*) pending=1 ;; - *) pending=0 ;; - esac - if [ "$pending" -eq 0 ]; then - echo "[lsky] 自动迁移检查完成:没有待执行的迁移" - return 0 + # · SQLite:直接**只读**比对「盘上的迁移文件」vs「记账表里的行」—— + # 刻意不走 Laravel:站点自己的 bootstrap/cache/config.php 一旦是坏的/外来的, + # 任何 artisan 命令都会报 "Target class [files] does not exist"(CI 的真升级用例 + # 里那个哨兵配置缓存就是这么把 migrate:status 打死的,实测踩到)。 + # · 其它库:走 migrate:status(纯只读查询)。 + if [ "$conn" = "sqlite" ]; then + pending=$(runuser -u www-data -- php -r ' + $dir = "/var/www/html/database/migrations"; + $p = $argv[1]; + $ran = []; + try { + $db = new PDO("sqlite:file:" . $p . "?mode=ro", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); + foreach ($db->query("SELECT migration FROM migrations") as $r) { $ran[$r[0]] = true; } + } catch (Throwable $e) { echo "-1"; exit; } + $n = 0; + foreach (glob($dir . "/*.php") as $f) { + if (!isset($ran[basename($f, ".php")])) { $n++; } + } + echo (string) $n; + ' "$dbpath" 2>/dev/null) || pending=-1 + case "$pending" in ''|*[!0-9]*) pending=-1 ;; esac + if [ "$pending" -lt 0 ]; then + echo "[lsky] 警告:读不出 SQLite 的迁移记账表,跳过自动迁移(站点不受影响)" + return 0 + fi + [ "$pending" -eq 0 ] && { echo "[lsky] 自动迁移检查完成:没有待执行的迁移"; return 0; } + echo "[lsky] 检测到 $pending 条待执行迁移" + else + status=$(runuser -u www-data -- sh -c 'cd /var/www/html && php artisan migrate:status' 2>&1) || true + case "$status" in + *Pending*|*pending*) pending=1 ;; + *) pending=0 ;; + esac + if [ "$pending" -eq 0 ]; then + echo "[lsky] 自动迁移检查完成:没有待执行的迁移" + return 0 + fi + echo "[lsky] 检测到待执行的迁移" fi # 迁移前备份(仅 SQLite 三件套;保留最近 3 份。MySQL/Postgres 请自行确保有备份) diff --git a/tools/upgrade-test.sh b/tools/upgrade-test.sh index 8a6ae3b..b17880c 100755 --- a/tools/upgrade-test.sh +++ b/tools/upgrade-test.sh @@ -557,6 +557,9 @@ echo " ✓ 数据面快照(含整份 md5 清单)逐字节相同" # 所以那段证明不了「真老站点会自动补表」。这一段专门构造真站点形状: # ① 建 migrations 记账表并写入上游那 10 条(= 2022-08 的 v2.1 站点就是这么记账的) # ② 把卷里那两个 fork 迁移文件删掉(模拟「镜像里的新迁移还没进卷」这个原始坑) +# ③ 把上面用过的哨兵 bootstrap/cache/config.php 挪开:它是**故意写坏**的,任何 artisan +# 命令都会被它打成 "Target class [files] does not exist";而真站点的配置缓存要么 +# 不存在、要么是有效的。不挪开就测不出「真站点能自动迁移」这件事。 # 然后重启新镜像,断言:迁移文件被自动补进卷、两张新表被自动建好、**哨兵数据一字未变**。 echo "== 4b) 自动迁移用例:v2.1 形状老站点 → 新镜像(无需人工干预)==" docker run --rm -i -v "$VOL":/var/www/html --entrypoint sh "$NEW_REF" > "$LOGDIR/phase4b-seed.out" 2>&1 <<'EOSH' @@ -564,6 +567,8 @@ set -eu cd /var/www/html # ② 删掉卷里那两个 fork 迁移文件(模拟「新迁移还没进卷」;镜像里那份干净树仍在) rm -f database/migrations/2026_10_01_*.php +# ③ 挪开故意写坏的哨兵配置缓存(见上面第 ③ 条说明) +rm -f bootstrap/cache/config.php # ① 建记账表并写入上游 10 条:v2.1 站点就是这样被 Laravel 记账的 php -r ' $p = "/var/www/html/database/database.sqlite";