From 5424e1e23faffe162b7f8a97760e9ab861635670 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Wed, 24 Jun 2020 19:50:47 -0400 Subject: [PATCH] apt: Run `apt-get -f install` before other commands Run `apt-get --fix-broken install` before installing package or manual update. This will attempt to correct broken dependencies. Tests: - Install a package without its dependencies using `dpkg -i`. - Both app install and manual update successfully recover from this situation. Signed-off-by: James Valleroy Reviewed-by: Sunil Mohan Adapa --- actions/packages | 28 +++++----------------------- actions/upgrades | 2 ++ plinth/action_utils.py | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/actions/packages b/actions/packages index 6f853c0de..fbce81755 100755 --- a/actions/packages +++ b/actions/packages @@ -19,6 +19,7 @@ import apt.cache import apt_inst import apt_pkg from plinth import cfg +from plinth.action_utils import run_apt_command LOCK_FILE = '/var/lib/dpkg/lock' @@ -68,29 +69,9 @@ def _apt_hold(): subprocess.run(['apt-mark', 'unhold', 'freedombox'], check=True) -def _run_apt_command(arguments): - """Run apt-get with provided arguments.""" - # Ask apt-get to output its progress to file descriptor 3. - command = [ - 'apt-get', '--assume-yes', '--quiet=2', '--option', 'APT::Status-Fd=3' - ] + arguments - - # Duplicate stdout to file descriptor 3 for this process. - os.dup2(1, 3) - - # Pass on file descriptor 3 instead of closing it. Close stdout - # so that regular output is ignored. - env = os.environ.copy() - env['DEBIAN_FRONTEND'] = 'noninteractive' - process = subprocess.run(command, stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, close_fds=False, - env=env) - return process.returncode - - def subcommand_update(arguments): """Update apt package lists.""" - sys.exit(_run_apt_command(['update'])) + sys.exit(run_apt_command(['update'])) def subcommand_install(arguments): @@ -114,8 +95,9 @@ def subcommand_install(arguments): extra_arguments += ['-o', 'Dpkg::Options::=--force-confnew'] with _apt_hold(): - returncode = _run_apt_command(['install'] + extra_arguments + - arguments.packages) + run_apt_command(['--fix-broken', 'install']) + returncode = run_apt_command(['install'] + extra_arguments + + arguments.packages) sys.exit(returncode) diff --git a/actions/upgrades b/actions/upgrades index 86c91f10a..82098a225 100755 --- a/actions/upgrades +++ b/actions/upgrades @@ -11,6 +11,7 @@ import re import subprocess import sys +from plinth.action_utils import run_apt_command from plinth.modules.apache.components import check_url AUTO_CONF_FILE = '/etc/apt/apt.conf.d/20auto-upgrades' @@ -83,6 +84,7 @@ def parse_arguments(): def subcommand_run(_): """Run unattended-upgrades""" + run_apt_command(['--fix-broken', 'install']) try: subprocess.Popen(['systemctl', 'start', 'freedombox-manual-upgrade'], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, diff --git a/plinth/action_utils.py b/plinth/action_utils.py index afe7b6715..55411a7b5 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -197,6 +197,7 @@ def webserver_disable(name, kind='config', apply_changes=True): class WebserverChange(object): """Context to restart/reload Apache after configuration changes.""" + def __init__(self): """Initialize the context object state.""" self.actions_required = set() @@ -388,3 +389,23 @@ def is_disk_image(): - Installing packages on a Debian machine using apt. """ return os.path.exists('/var/lib/freedombox/is-freedombox-disk-image') + + +def run_apt_command(arguments): + """Run apt-get with provided arguments.""" + # Ask apt-get to output its progress to file descriptor 3. + command = [ + 'apt-get', '--assume-yes', '--quiet=2', '--option', 'APT::Status-Fd=3' + ] + arguments + + # Duplicate stdout to file descriptor 3 for this process. + os.dup2(1, 3) + + # Pass on file descriptor 3 instead of closing it. Close stdout + # so that regular output is ignored. + env = os.environ.copy() + env['DEBIAN_FRONTEND'] = 'noninteractive' + process = subprocess.run(command, stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, close_fds=False, + env=env) + return process.returncode