fix(update): don't gate SQLite backup/verify on a 3.11/3.12 interpreter

copy_sqlite_db and verify_sqlite_db reused locate_python, which enforces
3.11 <= v <= 3.12. That bound is correct for rebuild_virtualenv and
preflight_check, but snapshotting or integrity-checking a SQLite file needs
nothing beyond the stdlib sqlite3 module. Reusing it coupled the DATA-safety
paths to the venv-builder constraint and blocked updates for a reason unrelated
to the data:

  * an all-Docker install returns before preflight_check ever runs and does
    every real step in the container -- yet backup_docker_db snapshots with the
    CONTAINER's python and then verified the extracted file with the HOST's.
    On a stock EL9 (python3.9) or Ubuntu 22.04 (python3.10) host that halted
    with "Could not obtain a VALID database backup", blaming the backup for a
    missing interpreter.
  * backup_current halts when it cannot snapshot, so those hosts could never
    update at all.

Split out locate_sqlite_python: any candidate that can `import sqlite3` wins,
and the install's own venv interpreter is preferred -- on a real box it is
guaranteed present, guaranteed to carry the module, and independent of PATH.
locate_python keeps the 3.11/3.12 bound where it belongs.

Surfaced by the scripts-ci unit matrix: almalinux:9 and rockylinux:9 ship
python3.9, so the suite's own probe accepted an interpreter locate_python
rejected, and copy_sqlite_db/verify_sqlite_db failed there while passing on
python3.12 runners.

T10i pins the contract: on a box where every candidate reports 3.9 (locate_python
fails), the helpers still resolve an interpreter, prefer the slot's venv, and
round-trip 100 rows. Fails on the parent commit, passes here.

Verified on the full 7-image CI matrix (ubuntu 22.04/24.04, debian:12,
rockylinux:9, almalinux:9, fedora:40, opensuse/leap:15.5), all six shell suites:
0 failed. test_update is 63 passed / 0 failed on every image carrying a python3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Juan DenisandClaude Opus 5 committed 2026-07-26 23:33:51 -04:00
1 parent 69b6ffb94a
commit 664a5e62bd
2 files changed
+99 -4

No files matched your search

+58
View File
@@ -475,6 +475,64 @@ else
fi
rm -f "$STUB_BIN/pg_restore"
# --------------------------------------------------------------------------
# T10i — the SQLite helpers must NOT inherit the venv-builder's 3.11/3.12
# constraint. Snapshotting and integrity-checking a DB needs only an
# interpreter carrying the stdlib sqlite3 module; demanding 3.11/3.12 blocked
# updates on stock EL9 (python3.9) and Ubuntu 22.04 (python3.10) boxes for a
# reason unrelated to the data — including all-Docker installs, which return
# before preflight_check and do every real step inside the container, yet
# verified the extracted DB with the HOST's interpreter.
# --------------------------------------------------------------------------
t="$WORK/t10i"; mkdir -p "$t/bin" "$t/slot/backend/instance" "$t/slot/venv/bin"
if [ -z "$PY" ]; then
skip "SQLite helpers work without a 3.11/3.12 interpreter (no working python3)"
else
REAL_PY="$(command -v "$PY")"
# An interpreter that answers a version probe with 3.9 but is otherwise a
# real Python: makes locate_python fail exactly as it does on EL9, while
# `import sqlite3` keeps working. Shadowing all four candidate names keeps
# the precondition true regardless of what the host actually has.
for n in python3.12 python3.11 python3 python; do
cat > "$t/bin/$n" <<EOF
#!/usr/bin/env bash
case "\$*" in
*version_info*) echo "3.9"; exit 0 ;;
esac
exec "$REAL_PY" "\$@"
EOF
chmod +x "$t/bin/$n"
done
cp "$t/bin/python3" "$t/slot/venv/bin/python"
"$PY" - "$t/slot/backend/instance/serverkit.db" <<'PYEOF'
import sqlite3, sys
c = sqlite3.connect(sys.argv[1])
c.execute('CREATE TABLE t(id INTEGER PRIMARY KEY, v TEXT)')
c.executemany('INSERT INTO t(v) VALUES (?)', [('row%d' % i,) for i in range(100)])
c.commit(); c.close()
PYEOF
el9_rc=0
(
set -Eeuo pipefail
PATH="$t/bin:$PATH"
DRY_RUN=0
# Precondition: to the venv builder this box now looks like EL9.
! locate_python
# The sqlite helpers still resolve an interpreter — and prefer the
# install's own over anything on PATH, so they stay correct on a box
# whose system python is unusable.
[ "$(locate_sqlite_python "$t/slot/venv/bin/python")" = "$t/slot/venv/bin/python" ]
copy_sqlite_db "$t/slot/backend/instance/serverkit.db" "$t/snap.db"
verify_sqlite_db "$t/snap.db"
) >/dev/null 2>&1 || el9_rc=$?
rows="$("$PY" -c "import sqlite3,sys;print(sqlite3.connect(sys.argv[1]).execute('SELECT COUNT(*) FROM t').fetchone()[0])" "$t/snap.db" 2>/dev/null || true)"
if [ "$el9_rc" -eq 0 ] && [ "$rows" = "100" ]; then
ok "SQLite snapshot/verify need only sqlite3, not Python 3.11/3.12 (EL9/22.04 boxes)"
else
bad "sqlite helpers still gated on locate_python: rc=$el9_rc, rows=[$rows], expected 100"
fi
fi
# --------------------------------------------------------------------------
# T10g — a failed IN-PLACE PostgreSQL migration must trigger an automatic
# restore from the verified pre-upgrade dump: the DB is shared, so without a
+41 -4
View File
@@ -843,9 +843,40 @@ atomic_switch() {
# even while the panel is writing. Python 3.11/3.12 is guaranteed by
# pre-flight; without it we fail rather than fall back to a racy cp.
# ---------------------------------------------------------------------------
# Snapshotting or integrity-checking a SQLite file needs nothing more than an
# interpreter carrying the stdlib `sqlite3` module — NOT the 3.11/3.12 that
# rebuild_virtualenv and preflight_check legitimately demand. Reusing
# locate_python here silently coupled the DATA-safety paths to the venv-builder
# constraint, and blocked updates for a reason unrelated to the data:
# * an all-Docker install returns before preflight_check ever runs and does
# all its work in the container, yet backup_docker_db verified the extracted
# DB on the HOST — so a stock EL9 (python3.9) or Ubuntu 22.04 (python3.10)
# host halted with "Could not obtain a VALID database backup", blaming the
# backup for a missing interpreter.
# * backup_current halts when it cannot snapshot, so the same boxes could
# never update at all.
# Prefer the install's own venv interpreter: on a real box it is guaranteed
# present, guaranteed to have the module, and independent of what is on PATH.
locate_sqlite_python() {
local c
for c in "$@" python3.12 python3.11 python3 python; do
[ -n "$c" ] || continue
command -v "$c" >/dev/null 2>&1 || continue
"$c" -c 'import sqlite3' >/dev/null 2>&1 || continue
printf '%s' "$c"
return 0
done
return 1
}
copy_sqlite_db() {
local src="$1" dest="$2" py
py="$(locate_python)" || { warn "No Python 3.11/3.12 available for a consistent DB snapshot"; return 1; }
local src="$1" dest="$2" py slot
# The DB lives at <slot>/backend/instance/serverkit.db — derive that slot's
# interpreter as the preferred candidate. A src outside a slot leaves the
# path unchanged, yielding a non-executable candidate that is simply skipped.
slot="${src%/backend/instance/*}"
py="$(locate_sqlite_python "$slot/venv/bin/python")" \
|| { warn "No Python with the sqlite3 module available for a consistent DB snapshot"; return 1; }
"$py" - "$src" "$dest" <<'PYEOF'
import sqlite3, sys
src, dest = sys.argv[1], sys.argv[2]
@@ -860,9 +891,15 @@ PYEOF
# A backup that was never verified is a hope, not a safety net. Verify every
# artifact BEFORE it is allowed to justify proceeding with the update.
verify_sqlite_db() {
local db="$1" py
local db="$1" py active hint=""
[ -s "$db" ] || return 1
py="$(locate_python)" || return 1
# Backups live in BACKUP_DIR, outside any slot, so hint at the active
# install's interpreter instead of deriving one from the file's path.
active="$(active_real_dir 2>/dev/null || true)"
if [ -n "$active" ]; then
hint="$active/venv/bin/python"
fi
py="$(locate_sqlite_python "$hint")" || return 1
[ "$("$py" -c "import sqlite3,sys;print(sqlite3.connect(sys.argv[1]).execute('PRAGMA integrity_check').fetchone()[0])" "$db" 2>/dev/null || true)" = "ok" ]
}
verify_pg_dump() {