diff --git a/actions/packages b/actions/packages index d7112413c..e98706224 100755 --- a/actions/packages +++ b/actions/packages @@ -40,6 +40,12 @@ def parse_arguments(): subparser.add_argument( '--force-configuration', choices=['new', 'old'], help='force old/new configuration files during install') + subparser.add_argument( + '--reinstall', action='store_true', + help='force re-installation of package even if it is current') + subparser.add_argument( + '--force-missing-configuration', action='store_true', + help='force installation of missing configuration files') subparser.add_argument( 'module', help='name of module for which package is being installed') subparser.add_argument('packages', nargs='+', @@ -94,6 +100,12 @@ def subcommand_install(arguments): elif arguments.force_configuration == 'new': extra_arguments += ['-o', 'Dpkg::Options::=--force-confnew'] + if arguments.reinstall: + extra_arguments.append('--reinstall') + + if arguments.force_missing_configuration: + extra_arguments += ['-o', 'Dpkg::Options::=--force-confmiss'] + subprocess.run(['dpkg', '--configure', '-a']) with _apt_hold(): run_apt_command(['--fix-broken', 'install']) diff --git a/plinth/package.py b/plinth/package.py index fdcaa59b7..a61fb5ed1 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -17,6 +17,7 @@ logger = logging.getLogger(__name__) class PackageException(Exception): """A package operation has failed.""" + def __init__(self, error_string=None, error_details=None, *args, **kwargs): """Store apt-get error string and details.""" super(PackageException, self).__init__(*args, **kwargs) @@ -32,6 +33,7 @@ class PackageException(Exception): class Transaction(object): """Information about an ongoing transaction.""" + def __init__(self, module_name, package_names): """Initialize transaction object. @@ -52,13 +54,10 @@ class Transaction(object): self.percentage = 0 self.stderr = None - def install(self, skip_recommends=False, force_configuration=None): + def install(self, skip_recommends=False, force_configuration=None, + reinstall=False, force_missing_configuration=False): """Run an apt-get transaction to install given packages. - FreedomBox Service (Plinth) needs to be running as root when calling - this. Currently, this is meant to be only during first time setup when - --setup is argument is passed. - If force_configuration is set to 'new', dpkg options will be enabled to make it force overwrite (without prompts) new configuration in place of old configuration (with a backup). This is useful when writing @@ -75,6 +74,13 @@ class Transaction(object): If force_configuration is None, no special options are passed to apt/dpkg for configuration file behavior. + If reinstall is True, packages will be reinstalled, even if they are + the latest version. + + If force_missing_configuration is True, any configuration files that + have been removed after the first package has been installed will be + restored. + """ try: self._run_apt_command(['update']) @@ -86,6 +92,12 @@ class Transaction(object): extra_arguments.append( '--force-configuration={}'.format(force_configuration)) + if reinstall: + extra_arguments.append('--reinstall') + + if force_missing_configuration: + extra_arguments.append('--force-missing-configuration') + self._run_apt_command(['install'] + extra_arguments + [self.module_name] + self.package_names) except subprocess.CalledProcessError as exception: diff --git a/plinth/setup.py b/plinth/setup.py index 7809215c0..fe50ca410 100644 --- a/plinth/setup.py +++ b/plinth/setup.py @@ -29,6 +29,7 @@ _force_upgrader = None class Helper(object): """Helper routines for modules to show progress.""" + def __init__(self, module_name, module): """Initialize the object.""" self.module_name = module_name @@ -90,7 +91,8 @@ class Helper(object): self.current_operation = None def install(self, package_names, skip_recommends=False, - force_configuration=None): + force_configuration=None, reinstall=False, + force_missing_configuration=False): """Install a set of packages marking progress.""" if self.allow_install is False: # Raise error if packages are not already installed. @@ -109,7 +111,8 @@ class Helper(object): 'step': 'install', 'transaction': transaction, } - transaction.install(skip_recommends, force_configuration) + transaction.install(skip_recommends, force_configuration, reinstall, + force_missing_configuration) def call(self, step, method, *args, **kwargs): """Call an arbitrary method during setup and note down its stage.""" @@ -408,9 +411,11 @@ class ForceUpgrader(): class TemporaryFailure(Exception): """Raised when upgrade fails but can be tried again immediately.""" + class PermanentFailure(Exception): """Raised when upgrade fails and there is nothing more we wish to do. """ + def __init__(self): """Initialize the force upgrader.""" if plinth.cfg.develop: