From 1d8d3c282861d3889826d0db493bedf3a2aa1f06 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 15:58:05 +0100 Subject: [PATCH 1/9] Add small helper to check if dpkg is in a broken state --- src/yunohost/utils/packages.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 5ef97618b..9cb3bd974 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -19,6 +19,7 @@ """ import re +import os import logging from collections import OrderedDict @@ -470,3 +471,11 @@ def ynh_packages_version(*args, **kwargs): 'yunohost', 'yunohost-admin', 'moulinette', 'ssowat', with_repo=True ) + + +def dpkg_is_broken(): + # If dpkg is broken, /var/lib/dpkg/updates + # will contains files like 0001, 0002, ... + # ref: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174 + return any(re.match("^[0-9]+$", f) + for f in os.listdir("/var/lib/dpkg/updates/")) From 19bd4da10400497bfa8de70d22d4c105ad4e6b5a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 15:58:24 +0100 Subject: [PATCH 2/9] Assert that dpkg is not broken when trying to install an app --- locales/en.json | 1 + src/yunohost/app.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/locales/en.json b/locales/en.json index 8528c2576..477563c8b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -146,6 +146,7 @@ "diagnosis_monitor_network_error": "Can't monitor network: {error}", "diagnosis_monitor_system_error": "Can't monitor system: {error}", "diagnosis_no_apps": "No installed application", + "dpkg_is_broken": "You cannot do this right now because dpkg/apt (the system package managers) seems to be in a broken state... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dnsmasq_isnt_installed": "dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Unable to generate certificate", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0bca68787..d332971c4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -722,6 +722,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on }, } + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + def confirm_install(confirm): # Ignore if there's nothing for confirm (good quality app), if --force is used From 53ba867f30349b953216302badf491e99a93dea4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 14:46:53 +0100 Subject: [PATCH 3/9] Also forbid to app_upgrade if dpkg is broken --- src/yunohost/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d332971c4..05d5b1e4c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -564,6 +564,9 @@ def app_upgrade(auth, app=[], url=None, file=None): url -- Git url to fetch for upgrade """ + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback # Retrieve interface From e7241394afb33b0ddb46531addff8727a17d3b4b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:00:18 +0100 Subject: [PATCH 4/9] Adding dpkg checks after removal of an application --- src/yunohost/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 05d5b1e4c..61b9c69e4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -880,6 +880,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on app_ssowatconf(auth) + if packages.dpkg_is_broken(): + logger.error(m18n.n("this_action_broke_dpkg")) + if install_retcode == -1: msg = m18n.n('operation_interrupted') + " " + error_msg raise YunohostError(msg, raw_msg=True) @@ -962,6 +965,9 @@ def app_remove(operation_logger, auth, app): hook_remove(app) app_ssowatconf(auth) + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("this_action_broke_dpkg")) + def app_addaccess(auth, apps, users=[]): """ From c824f10cc29b98b551f042fde10a894f08791855 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:04:34 +0100 Subject: [PATCH 5/9] Also check for dpkg corruption before upgrading system or app... --- src/yunohost/tools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b2fbf380c..254e0533c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -524,6 +524,10 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal ignore_packages -- Ignore APT packages upgrade """ + from yunohost.utils import packages + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + failure = False # Retrieve interface From dcf2ca8b550dae29e9085288bd0380e4ca463fd9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:06:11 +0100 Subject: [PATCH 6/9] Check dpkg status asap in app_install --- src/yunohost/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 61b9c69e4..159017c89 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -708,6 +708,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on no_remove_on_failure -- Debug option to avoid removing the app on a failed installation force -- Do not ask for confirmation when installing experimental / low-quality apps """ + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger @@ -725,9 +728,6 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on }, } - if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) - def confirm_install(confirm): # Ignore if there's nothing for confirm (good quality app), if --force is used From ba7bdb8f142c0d94eeecb12ca9feb3ddc3759c0f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:07:09 +0100 Subject: [PATCH 7/9] No m18n.n needed in YunohostErrors --- src/yunohost/app.py | 6 +++--- src/yunohost/tools.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 159017c89..0bd8e412f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -565,7 +565,7 @@ def app_upgrade(auth, app=[], url=None, file=None): """ if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) + raise YunohostError("dpkg_is_broken") from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback @@ -709,7 +709,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on force -- Do not ask for confirmation when installing experimental / low-quality apps """ if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) + raise YunohostError("dpkg_is_broken") from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger @@ -966,7 +966,7 @@ def app_remove(operation_logger, auth, app): app_ssowatconf(auth) if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("this_action_broke_dpkg")) + raise YunohostError("this_action_broke_dpkg") def app_addaccess(auth, apps, users=[]): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 254e0533c..9a6c7ccba 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -526,7 +526,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal """ from yunohost.utils import packages if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) + raise YunohostError("dpkg_is_broken") failure = False From 2fec4a6c6415b7ade56f3d0227d09b3018ee59d9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:13:59 +0100 Subject: [PATCH 8/9] Implement second message, this_action_broke_dpkg --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index 477563c8b..5c87106ec 100644 --- a/locales/en.json +++ b/locales/en.json @@ -464,6 +464,7 @@ "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "system_upgraded": "The system has been upgraded", "system_username_exists": "Username already exists in the system users", + "this_action_broke_dpkg": "This action broke dpkg/apt (the system package managers)... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "An unexpected error occured: {error}", "unit_unknown": "Unknown unit '{unit:s}'", From 6fc6d6cfe6b6045de4251cb9dac01d2acae370f9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:16:07 +0100 Subject: [PATCH 9/9] Who knows, maybe this folder doesn't exist in some context --- src/yunohost/utils/packages.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 9cb3bd974..e10de6493 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -477,5 +477,7 @@ def dpkg_is_broken(): # If dpkg is broken, /var/lib/dpkg/updates # will contains files like 0001, 0002, ... # ref: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174 + if not os.path.isdir("/var/lib/dpkg/updates/"): + return False return any(re.match("^[0-9]+$", f) for f in os.listdir("/var/lib/dpkg/updates/"))