diff --git a/locales/en.json b/locales/en.json index e48ba65ce..f4d4492ab 100644 --- a/locales/en.json +++ b/locales/en.json @@ -503,6 +503,7 @@ "migration_0023_not_enough_space": "Make sufficient space available in {path} to run the migration.", "migration_0023_postgresql_11_not_installed": "PostgreSQL was not installed on your system. Nothing to do.", "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 is installed, but not PostgreSQL 13!? Something weird might have happened on your system :(...", + "migration_0024_rebuild_python_venv_failed": "Unable to rebuild the python virtual env {venv}, this app is probably broken. If your app is broken, you probably should force the upgrade of this app thanks to `yunohost app upgrade --force APP`", "migration_description_0021_migrate_to_bullseye": "Upgrade the system to Debian Bullseye and YunoHost 11.x", "migration_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4", "migration_description_0023_postgresql_11_to_13": "Migrate databases from PostgreSQL 11 to 13", diff --git a/src/migrations/0024_rebuild_python_venv.py b/src/migrations/0024_rebuild_python_venv.py new file mode 100644 index 000000000..7d6530c8c --- /dev/null +++ b/src/migrations/0024_rebuild_python_venv.py @@ -0,0 +1,74 @@ +import subprocess +import os + +from moulinette import m18n +from moulinette.utils.log import getActionLogger +from moulinette.utils.process import call_async_output + +from yunohost.tools import Migration +from yunohost.utils.filesystem import read_file, rm + +logger = getActionLogger("yunohost.migration") + +VENV_REQUIREMENTS_SUFFIX = ".requirements_backup_for_bullseye_upgrade.txt" +VENV_IGNORE = "ynh_migration_no_regen" + + +def _get_all_venvs(dir, level=0, maxlevel=3): + """ + Returns the list of all python virtual env directories recursively + + Arguments: + dir - the directory to scan in + maxlevel - the depth of the recursion + level - do not edit this, used as an iterator + """ + # Using os functions instead of glob, because glob doesn't support hidden + # folders, and we need recursion with a fixed depth + result = [] + for file in os.listdir(dir): + path = os.path.join(dir, file) + if os.path.isdir(path): + if os.path.isfile(os.path.join(path, VENV_IGNORE)): + continue + activatepath = os.path.join(path, "bin", "activate") + if os.path.isfile(activatepath): + content = read_file(activatepath) + if ("VIRTUAL_ENV" in content) and ("PYTHONHOME" in content): + result.append(path) + continue + if level < maxlevel: + result += _get_all_venvs(path, level=level + 1) + return result + + +class MyMigration(Migration): + """ + After the update, recreate a python virtual env based on the previously + generated requirements file + """ + + dependencies = ["migrate_to_bullseye"] + + def run(self): + + venvs = _get_all_venvs("/opt/") + _get_all_venvs("/var/www/") + for venv in venvs: + if not os.path.isfile(venv + VENV_REQUIREMENTS_SUFFIX): + continue + + # Recreate the venv + rm(venv, recursive=True) + callbacks = ( + lambda l: logger.info("+ " + l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()) + ) + call_async_output(["python", "-m", "venv", venv], callbacks) + status = call_async_output([ + "{venv}/bin/pip", "install", "-r", + venv + VENV_REQUIREMENTS_SUFFIX], callbacks) + if status != 0: + logger.warning(m18n.n("migration_0024_rebuild_python_venv", + venv=venv)) + else: + rm(venv + VENV_REQUIREMENTS_SUFFIX)