diff --git a/actions/upgrades b/actions/upgrades index 2d130660e..561edcc8c 100755 --- a/actions/upgrades +++ b/actions/upgrades @@ -61,7 +61,11 @@ def subcommand_run(_): sys.exit(1) try: - subprocess.check_call(['unattended-upgrades', '-v']) + subprocess.Popen( + ['unattended-upgrades', '-v'], + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, close_fds=True, + start_new_session=True) except FileNotFoundError: print('Error: unattended-upgrades is not available.', file=sys.stderr) sys.exit(2) diff --git a/plinth/modules/upgrades/templates/upgrades.html b/plinth/modules/upgrades/templates/upgrades.html index 6bc6f0391..15b0167b2 100644 --- a/plinth/modules/upgrades/templates/upgrades.html +++ b/plinth/modules/upgrades/templates/upgrades.html @@ -22,7 +22,7 @@ {% block page_head %} - {% if running %} + {% if is_busy %} {% endif %} @@ -33,47 +33,25 @@

{{ title }}

- {% if result %} +

+ {% blocktrans trimmed %} + Upgrades install the latest software and security updates. When automatic + upgrades are enabled, upgrades are automatically run every night. You + don't normally need to start the upgrade process. + {% endblocktrans %} +

- {% if result.return_code %} -

- {% trans "There was an error while upgrading." %} -

+

+ {% blocktrans trimmed %} + Depending on the number of packages to install, this may take a long time + to complete. While upgrades are in progress, you will not be able to + install other packages. During the upgrade, this web interface may be + temporarily unavailable and show an error. Refresh the page to continue. + {% endblocktrans %} +

-
{% trans "Output from unattended-upgrades:" %}
-
{{ result.error }}
- {% if result.output %} -
{{ result.output }}
- {% endif %} - {% else %} -

- {% trans "The operating system is up to date now.  " %} - -

- -
-
{% trans "Output from unattended-upgrades:" %}
-
{{ result.output }}
-
- {% endif %} - - {% endif %} - - - {% if not result and not running %} -

- {% blocktrans trimmed %} - This will run unattended-upgrades, which will attempt to upgrade - your system with the latest Debian packages. It may take a few - minutes to complete. - {% endblocktrans %} -

- -
+ {% if not is_busy %} + {% csrf_token %} {% endif %} - {% if running %} + {% if is_busy %}

- {% trans "System is being upgraded." %} + {% trans "A package manager is running." %}

{% endif %} + {% if log %} +
{% trans "Recent log from upgrades:" %}
+ +
{{ log }}
+ {% endif %} + {% endblock %} {% block page_js %} diff --git a/plinth/modules/upgrades/urls.py b/plinth/modules/upgrades/urls.py index 5ff9f8788..9457c17dc 100644 --- a/plinth/modules/upgrades/urls.py +++ b/plinth/modules/upgrades/urls.py @@ -27,5 +27,4 @@ from . import views urlpatterns = [ url(r'^sys/upgrades/$', views.index, name='index'), url(r'^sys/upgrades/upgrade/$', views.upgrade, name='upgrade'), - url(r'^sys/upgrades/upgrade/run/$', views.run, name='run'), ] diff --git a/plinth/modules/upgrades/views.py b/plinth/modules/upgrades/views.py index 6e83faf8c..d08d6f291 100644 --- a/plinth/modules/upgrades/views.py +++ b/plinth/modules/upgrades/views.py @@ -25,6 +25,7 @@ from django.shortcuts import redirect from django.template.response import TemplateResponse from django.utils.translation import ugettext as _, ugettext_lazy from django.views.decorators.http import require_POST +import subprocess from .forms import ConfigureForm from plinth import actions @@ -36,7 +37,8 @@ subsubmenu = [{'url': reverse_lazy('upgrades:index'), {'url': reverse_lazy('upgrades:upgrade'), 'text': ugettext_lazy('Upgrade Packages')}] -upgrade_process = None +LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades.log' +LOCK_FILE = '/var/log/dpkg/lock' def on_install(): @@ -65,29 +67,42 @@ def index(request): 'form': form, 'subsubmenu': subsubmenu}) +def is_package_manager_busy(): + """Return whether a package manager is running.""" + try: + subprocess.check_output(['lsof', '/var/lib/dpkg/lock']) + return True + except subprocess.CalledProcessError: + return False + + +def get_log(): + """Return the current log for unattended upgrades.""" + try: + with open(LOG_FILE, 'r') as file_handle: + return file_handle.read() + except IOError: + return None + @package.required(['unattended-upgrades'], on_install=on_install) def upgrade(request): """Serve the upgrade page.""" - result = _collect_upgrade_result(request) + is_busy = is_package_manager_busy() + + if request.method == 'POST': + try: + actions.superuser_run('upgrades', ['run']) + messages.success(request, _('Upgrade process started.')) + is_busy = True + except ActionError: + messages.error(request, _('Starting upgrade failed.')) return TemplateResponse(request, 'upgrades.html', {'title': _('Package Upgrades'), 'subsubmenu': subsubmenu, - 'running': bool(upgrade_process), - 'result': result}) - - -@require_POST -@package.required(['unattended-upgrades'], on_install=on_install) -def run(_): - """Start the upgrade process.""" - global upgrade_process - if not upgrade_process: - upgrade_process = actions.superuser_run( - 'upgrades', ['run'], async=True) - - return redirect('upgrades:upgrade') + 'is_busy': is_busy, + 'log': get_log()}) def get_status(): @@ -121,30 +136,3 @@ def _apply_changes(request, old_status, new_status): messages.success(request, _('Automatic upgrades enabled')) else: messages.success(request, _('Automatic upgrades disabled')) - - -def _collect_upgrade_result(request): - """Handle upgrade process completion.""" - global upgrade_process - if not upgrade_process: - return - - return_code = upgrade_process.poll() - - # Upgrade process is not complete yet - if return_code is None: - return - - output, error = upgrade_process.communicate() - output, error = output.decode(), error.decode() - - if not return_code: - messages.success(request, _('Upgrade completed.')) - else: - messages.error(request, _('Upgrade failed.')) - - upgrade_process = None - - return {'return_code': return_code, - 'output': output, - 'error': error}