upgrade: rebuild venvs after a Python major-version change

A Debian major upgrade (e.g. 12 -> 13) replaces /usr/bin/python3
(3.11 -> 3.13).  Existing venvs still resolve `python3` to the system
interpreter, but their packages live in lib/python3.11/site-packages,
which the new interpreter never searches -- every import fails and
`pip install --upgrade` cannot repair them, taking down the API,
Controller, yt-dlp, and Help.

upgrade.sh now compares each venv's recorded Python version against the
current one and does a full rebuild via reset_virtual_environments.sh
when they differ.  Detecting a mismatch (rather than literally "3.11")
also self-heals future bumps and is a no-op once versions match or on a
fresh install with no pyvenv.cfg yet.

reset_virtual_environments.sh gains a -y/--yes flag to skip its
confirmation prompt; the interactive yes_or_no read loops forever on EOF
under the tty-less wrolpi-upgrade.service.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Roland 2026-07-20 08:55:41 -06:00
parent 142447f43b
commit 950a53017f
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 46 additions and 1 deletions

View file

@ -1,6 +1,18 @@
#!/usr/bin/env bash
# Deletes and recreates all WROLPi Python virtual environments.
# This script requires internet access for pip install.
#
# Usage: reset_virtual_environments.sh [-y|--yes]
# -y, --yes Skip the confirmation prompt. Used by automated upgrades
# (wrolpi-upgrade.service), which run without a tty -- the
# interactive prompt would otherwise loop forever on EOF.
ASSUME_YES=false
for arg in "$@"; do
case "${arg}" in
-y | --yes) ASSUME_YES=true ;;
esac
done
# Re-execute this script if it wasn't called with sudo.
if [ $EUID != 0 ]; then
@ -10,7 +22,9 @@ fi
source /opt/wrolpi/wrolpi/scripts/lib.sh
yes_or_no "This script requires internet access. Do you want to continue?" || exit 0
if [ "${ASSUME_YES}" != true ]; then
yes_or_no "This script requires internet access. Do you want to continue?" || exit 0
fi
set -e
set -x

View file

@ -119,6 +119,37 @@ EOF
debian12_guard "$@"
# ---------------------------------------------------------------------------
# --- Rebuild virtual environments after a Python major-version change -------
# A Debian major upgrade (e.g. 12 -> 13) replaces /usr/bin/python3 (3.11 -> 3.13).
# The existing venvs still resolve their `python3` to the system interpreter,
# but their packages live in lib/python3.11/site-packages, which the new
# interpreter never searches -- so every import fails and `pip install --upgrade`
# cannot repair them. Detect this by comparing each venv's recorded Python
# version against the current one, and do a full rebuild (delete + recreate +
# reinstall) via reset_virtual_environments.sh when they differ.
rebuild_stale_venvs() {
local current
current=$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null) || return 0
local cfg venv_version
for cfg in /opt/wrolpi/venv/pyvenv.cfg \
/opt/wrolpi/controller/venv/pyvenv.cfg \
/opt/wrolpi-help/venv/pyvenv.cfg; do
[ -r "${cfg}" ] || continue
# pyvenv.cfg records e.g. "version = 3.11.2"; match that key exactly so
# the newer "version_info = ..." line is ignored. Reduce to major.minor.
venv_version=$(awk -F'=' '$1 ~ /^[[:space:]]*version[[:space:]]*$/ {gsub(/[[:space:]]/,"",$2); print $2; exit}' "${cfg}")
venv_version=${venv_version%.*}
if [ -n "${venv_version}" ] && [ "${venv_version}" != "${current}" ]; then
echo "Python changed (venv at ${cfg%/pyvenv.cfg} built for ${venv_version}, system is now ${current}); rebuilding all virtual environments..."
/opt/wrolpi/scripts/reset_virtual_environments.sh -y
return 0
fi
done
}
rebuild_stale_venvs
# ---------------------------------------------------------------------------
# Upgrade Controller first so users can monitor the rest of the upgrade.
upgrade_controller() {
echo "Upgrading WROLPi Controller..."