diff --git a/actions/upgrades b/actions/upgrades new file mode 100755 index 000000000..d72064b4f --- /dev/null +++ b/actions/upgrades @@ -0,0 +1,111 @@ +#!/usr/bin/python3 +# -*- mode: python -*- +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Configures or runs unattended-upgrades +""" + +import argparse +import os +import subprocess + +CONF_FILE = '/etc/apt/apt.conf.d/50unattended-upgrades' +AUTO_CONF_FILE = '/etc/apt/apt.conf.d/20auto-upgrades' + + +def parse_arguments(): + """REturn parsed command line arguments as dictionary""" + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') + + # Run unattended-upgrades + subparsers.add_parser('run', help='Upgrade packages on the system') + + # Enable automatic upgrades + subparsers.add_parser('enable-auto', help='Enable automatic upgrades') + + # Disable automatic upgrades + subparsers.add_parser('disable-auto', help='Disable automatic upgrades.') + + return parser.parse_args() + + +def subcommand_run(_): + """Run unattended-upgrades""" + try: + setup() + except FileNotFoundError: + print('Error: Could not configure unattended-upgrades.') + return + + try: + output = subprocess.check_output(['unattended-upgrades', '-v']) + except subprocess.CalledProcessError as error: + print('Error: %s', error) + except FileNotFoundError: + print('Error: unattended-upgrades is not available.') + else: + print('%s', output.decode()) + + +def subcommand_enable_auto(_): + """Enable automatic upgrades""" + try: + setup() + except FileNotFoundError: + print('Error: Could not configure unattended-upgrades.') + return + + with open(AUTO_CONF_FILE, 'w') as conffile: + conffile.write('APT::Periodic::Update-Package-Lists "1";\n') + conffile.write('APT::Periodic::Unattended-Upgrade "1";\n') + + +def subcommand_disable_auto(_): + """Disable automatic upgrades""" + os.remove(AUTO_CONF_FILE) + + +def setup(): + """Sets unattended-upgrades config to upgrade any package from Debian.""" + with open(CONF_FILE, 'r') as conffile: + lines = conffile.readlines() + + for line in lines: + if '"origin=Debian";' in line: + return # already configured + + with open(CONF_FILE, 'w') as conffile: + for line in lines: + conffile.write(line) + if 'Unattended-Upgrade::Origins-Pattern {' in line: + conffile.write(' "origin=Debian";\n') + + +def main(): + """Parse arguments and perform all duties""" + arguments = parse_arguments() + + subcommand = arguments.subcommand.replace('-', '_') + subcommand_method = globals()['subcommand_' + subcommand] + subcommand_method(arguments) + + +if __name__ == '__main__': + main() diff --git a/data/etc/plinth/modules-enabled/upgrades b/data/etc/plinth/modules-enabled/upgrades new file mode 100644 index 000000000..259ea059e --- /dev/null +++ b/data/etc/plinth/modules-enabled/upgrades @@ -0,0 +1 @@ +plinth.modules.upgrades diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py new file mode 100644 index 000000000..24066ee4a --- /dev/null +++ b/plinth/modules/upgrades/__init__.py @@ -0,0 +1,27 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Plinth module for upgrades +""" + +from . import upgrades +from .upgrades import init + +__all__ = ['upgrades', 'init'] + +depends = ['plinth.modules.system'] diff --git a/plinth/modules/upgrades/templates/upgrades.html b/plinth/modules/upgrades/templates/upgrades.html new file mode 100644 index 000000000..29c6d7cd3 --- /dev/null +++ b/plinth/modules/upgrades/templates/upgrades.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% comment %} +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +{% endcomment %} + +{% block content %} + +

{{ title }}

+ +

This will run unattended-upgrades, which will attempt to upgrade your system +with the latest Debian packages. It may take a minute to complete.

+ +

+ Upgrade now » +

+ +{% endblock %} diff --git a/plinth/modules/upgrades/templates/upgrades_run.html b/plinth/modules/upgrades/templates/upgrades_run.html new file mode 100644 index 000000000..de860a570 --- /dev/null +++ b/plinth/modules/upgrades/templates/upgrades_run.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} +{% comment %} +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +{% endcomment %} + +{% block content %} + +

{{title}}

+ +{% if upgrades_error %} +

There was an error while upgrading:

+

{{ upgrades_error }}
+{% endif %} + +{% if upgrades_output %} +

Output from unattended-upgrades:

+
{{ upgrades_output }}
+{% endif %} + +{% endblock %} diff --git a/plinth/modules/upgrades/upgrades.py b/plinth/modules/upgrades/upgrades.py new file mode 100644 index 000000000..d6557af43 --- /dev/null +++ b/plinth/modules/upgrades/upgrades.py @@ -0,0 +1,63 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Plinth module for upgrades +""" + +from django.contrib.auth.decorators import login_required +from django.template.response import TemplateResponse +from gettext import gettext as _ + +from plinth import actions +from plinth import cfg +from plinth import package +from plinth.errors import ActionError + + +def init(): + """Initialize the module""" + menu = cfg.main_menu.get('system:index') + menu.add_urlname("Upgrades", "glyphicon-refresh", + "upgrades:index", 21) + + +@login_required +@package.required('unattended-upgrades') +def index(request): + """Serve the index page""" + return TemplateResponse(request, 'upgrades.html', + {'title': _('Package Upgrades')}) + + +@login_required +@package.required('unattended-upgrades') +def run(request): + """Run upgrades and show the output page""" + output = '' + error = '' + try: + output = actions.superuser_run('upgrades', ['run']) + except ActionError as exception: + output, error = exception.args[1:] + except Exception as exception: + error = str(exception) + + return TemplateResponse(request, 'upgrades_run.html', + {'title': _('Package Upgrades'), + 'upgrades_output': output, + 'upgrades__error': error}) diff --git a/plinth/modules/upgrades/urls.py b/plinth/modules/upgrades/urls.py new file mode 100644 index 000000000..25c50fa85 --- /dev/null +++ b/plinth/modules/upgrades/urls.py @@ -0,0 +1,29 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +URLs for the upgrades module +""" + +from django.conf.urls import patterns, url + + +urlpatterns = patterns( + 'plinth.modules.upgrades.upgrades', + url(r'^sys/upgrades/$', 'index', name='index'), + url(r'^sys/upgrades/run/$', 'run', name='run'), + )