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 <jvalleroy@mailbox.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
James Valleroy 2020-06-24 19:50:47 -04:00 committed by Sunil Mohan Adapa
parent bf53fd8b17
commit 5424e1e23f
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
3 changed files with 28 additions and 23 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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