diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 13563015f..8d50836dc 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -437,9 +437,31 @@ def is_disk_image(): return os.path.exists('/var/lib/freedombox/is-freedombox-disk-image') -def run_apt_command(arguments, enable_triggers: bool = False): +def run_apt_command(arguments, enable_triggers: bool = False, + allow_freedombox_restart=False): """Run apt-get with provided arguments.""" - command = ['apt-get', '--assume-yes', '--quiet=2'] + arguments + command = [] + if not allow_freedombox_restart: + # Don't restart the freedombox web service. This configuration is only + # used when apt command is invoked from freedombox web service itself + # (such as during an app's installation/uninstallation). + # + # If this is not done, a freedombox web service restart is attempted. + # needsrestart will wait until the restart is completed. apt command + # will wait until needsrestart is completed. The restart mechanism in + # service will wait until all currently running threads are completed. + # One thread that has invoked this apt command will not finish as it + # waits for apt command to finish. This results in a deadlock. Avoid + # this by not attempting to restart freedombox web service when apt + # command is invoked from freedombox web service. + mount_path = '/etc/needrestart/conf.d/freedombox-self.conf' + orig_path = f'/usr/share/freedombox{mount_path}' + command = [ + 'systemd-run', '--pipe', + f'--property=BindReadOnlyPaths={orig_path}:{mount_path}' + ] + + command += ['apt-get', '--assume-yes', '--quiet=2'] + arguments env = os.environ.copy() env['DEBIAN_FRONTEND'] = 'noninteractive' diff --git a/plinth/modules/upgrades/data/usr/share/freedombox/etc/needrestart/conf.d/freedombox-self.conf b/plinth/modules/upgrades/data/usr/share/freedombox/etc/needrestart/conf.d/freedombox-self.conf new file mode 100644 index 000000000..a3e008f3c --- /dev/null +++ b/plinth/modules/upgrades/data/usr/share/freedombox/etc/needrestart/conf.d/freedombox-self.conf @@ -0,0 +1,13 @@ +# Don't restart the freedombox web service. This configuration is only used when +# apt command is invoked from freedombox web service itself (such as during an +# app's installation/uninstallation). +# +# If this is not done, a freedombox web service restart is attempted. +# needsrestart will wait until the restart is completed. apt command will wait +# until needsrestart is completed. The restart mechanism in service will wait +# until all currently running threads are completed. One thread that has invoked +# this apt command will not finish as it waits for apt command to finish. This +# results in a deadlock. Avoid this by not attempting to restart freedombox web +# service when apt command is invoked from freedombox web service. +$nrconf{override_rc}->{qr(^freedombox\.service$)} = 0; +$nrconf{override_rc}->{qr(^plinth\.service$)} = 0; diff --git a/plinth/modules/upgrades/distupgrade.py b/plinth/modules/upgrades/distupgrade.py index e9024727e..fbc689e4a 100644 --- a/plinth/modules/upgrades/distupgrade.py +++ b/plinth/modules/upgrades/distupgrade.py @@ -72,7 +72,8 @@ distribution_info: dict = { def _apt_run(arguments: list[str]): """Run an apt command and ensure that output is written to stdout.""" - returncode = action_utils.run_apt_command(arguments) + returncode = action_utils.run_apt_command(arguments, + allow_freedombox_restart=True) if returncode: raise RuntimeError( f'Apt command failed with return code: {returncode}')