diff --git a/actions/backports b/actions/backports
new file mode 100755
index 000000000..353b65313
--- /dev/null
+++ b/actions/backports
@@ -0,0 +1,116 @@
+#!/usr/bin/python3
+# -*- mode: python -*-
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+"""
+Script to check for availability of buster-backports and create an
+apt sources list for backports if available.
+"""
+
+import argparse
+import os
+import requests
+
+from plinth import action_utils
+
+BUSTER_BACKPORTS_RELEASE_FILE_URL = "http://cdn-fastly.deb.debian.org/debian/dists/buster-backports/Release"
+
+
+def parse_arguments():
+ """Return parsed command line arguments as dictionary."""
+ parser = argparse.ArgumentParser()
+ subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
+
+ subparsers.add_parser('enable', help='Enable buster backports check')
+ subparsers.add_parser('disable', help='Disable buster backports check')
+ subparsers.add_parser('check-backports',
+ help='Check whether buster backports are available')
+
+ subparsers.required = True
+ return parser.parse_args()
+
+
+def add_buster_backports_sources():
+ backports_list = '/etc/apt/sources.list.d/freedombox-backports.list'
+
+ conf = \
+"""deb http://deb.debian.org/debian buster-backports main
+deb-src http://deb.debian.org/debian buster-backports main
+"""
+
+ components = {"main"}
+
+ # Use tor+http if download over tor is enabled in sources.list
+ with open('/etc/apt/sources.list', 'r') as sources_list:
+ sources = sources_list.readlines()
+ for line in sources:
+ if not line.startswith("#") and "tor+http" in line:
+ conf = conf.replace("http", "tor+http")
+ # Check for contrib and nonfree components
+ if "contrib" in line:
+ components.add("contrib")
+ if "nonfree" in line:
+ components.add("nonfree")
+ break
+
+ conf = conf.replace("main", " ".join(components))
+
+ if not os.path.exists(backports_list):
+ try:
+ with open(backports_list, 'w') as file_handle:
+ file_handle.write(conf)
+ except PermissionError:
+ print(("Failed adding sources list for buster-backports."
+ "Try running as a superuser."))
+
+
+def subcommand_check_backports(_):
+ """Check whether buster backports is available.
+ Add a sources file if it's available.
+ """
+ response = requests.get(BUSTER_BACKPORTS_RELEASE_FILE_URL)
+
+ if response.status_code == 404:
+ print("Release file for Buster backports is not available yet.")
+ else:
+ print("Buster backports is now available. Adding to sources...")
+ add_buster_backports_sources()
+
+
+def subcommand_enable(_):
+ """Enable systemd service for the daily job buster-backports-check."""
+ action_utils.service_enable('buster-backports-check.timer')
+ action_utils.service_start('buster-backports-check.timer')
+
+
+def subcommand_disable(_):
+ """Disable systemd service for the daily job buster-backports-check."""
+ action_utils.service_stop('buster-backports-check.timer')
+ action_utils.service_disable('buster-backports-check.timer')
+
+
+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/apt/preferences.d/freedombox.pref b/data/etc/apt/preferences.d/freedombox.pref
new file mode 100644
index 000000000..f298825a3
--- /dev/null
+++ b/data/etc/apt/preferences.d/freedombox.pref
@@ -0,0 +1,3 @@
+Package: freedombox
+Pin: release a=buster-backports
+Pin-Priority: 800
diff --git a/data/etc/plinth/modules-enabled/backports b/data/etc/plinth/modules-enabled/backports
new file mode 100644
index 000000000..0b329653d
--- /dev/null
+++ b/data/etc/plinth/modules-enabled/backports
@@ -0,0 +1,2 @@
+plinth.modules.backports
+
diff --git a/data/lib/systemd/system/buster-backports-check.service b/data/lib/systemd/system/buster-backports-check.service
new file mode 100644
index 000000000..31cb98928
--- /dev/null
+++ b/data/lib/systemd/system/buster-backports-check.service
@@ -0,0 +1,24 @@
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+
+[Unit]
+Description=Service to check for buster backports and enable them
+ConditionPathExists=!/etc/apt/sources.list.d/freedombox-backports.list
+
+[Service]
+ExecStart=/usr/share/plinth/actions/backports check-backports
+
diff --git a/data/lib/systemd/system/buster-backports-check.timer b/data/lib/systemd/system/buster-backports-check.timer
new file mode 100644
index 000000000..e06b71748
--- /dev/null
+++ b/data/lib/systemd/system/buster-backports-check.timer
@@ -0,0 +1,27 @@
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+
+[Unit]
+Description=Daily check of whether buster backports are available yet
+
+[Timer]
+OnCalendar=daily
+AccuracySec=12h
+Persistent=true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/postrm b/debian/postrm
index 01686de51..fb7db5b7b 100755
--- a/debian/postrm
+++ b/debian/postrm
@@ -9,6 +9,10 @@ purge)
# Remove legacy directory too
rm -rf /var/log/plinth
+
+ if [ -e '/etc/apt/sources.list.d/freedombox-backports.list' ]; then
+ rm /etc/apt/sources.list.d/freedombox-backports.list
+ fi
;;
esac
diff --git a/plinth/modules/backports/__init__.py b/plinth/modules/backports/__init__.py
new file mode 100644
index 000000000..06357928e
--- /dev/null
+++ b/plinth/modules/backports/__init__.py
@@ -0,0 +1,30 @@
+#
+# This file is part of FreedomBox.
+#
+# 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 .
+#
+"""
+FreedomBox app to manage Debian backports.
+"""
+
+from plinth import actions
+
+is_essential = True
+
+version = 1
+
+
+def setup(helper, old_version=None):
+ """Configure the module."""
+ helper.call('post', actions.superuser_run, 'backports', ['enable'])
diff --git a/plinth/modules/backports/urls.py b/plinth/modules/backports/urls.py
new file mode 100644
index 000000000..21fc6a4ac
--- /dev/null
+++ b/plinth/modules/backports/urls.py
@@ -0,0 +1,21 @@
+#
+# This file is part of FreedomBox.
+#
+# 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 Backports module.
+"""
+
+urlpatterns = []
diff --git a/plinth/package.py b/plinth/package.py
index f6d454734..0731346b8 100644
--- a/plinth/package.py
+++ b/plinth/package.py
@@ -84,7 +84,7 @@ class Transaction(object):
self.package_names)
else:
self._run_apt_command(['install', self.module_name] +
- self.package_names)
+ self.package_names)
except subprocess.CalledProcessError as exception:
logger.exception('Error installing package: %s', exception)
raise
diff --git a/setup.py b/setup.py
index 05c840b8d..920c0913c 100755
--- a/setup.py
+++ b/setup.py
@@ -238,6 +238,8 @@ setuptools.setup(
glob.glob('data/etc/apache2/includes/*.conf')),
('/etc/apt/apt.conf.d',
glob.glob('data/etc/apt/apt.conf.d/60unattended-upgrades')),
+ ('/etc/apt/preferences.d',
+ glob.glob('data/etc/apt/preferences.d/freedombox.pref')),
('/etc/avahi/services/',
glob.glob('data/etc/avahi/services/*.service')),
('/etc/ikiwiki', glob.glob('data/etc/ikiwiki/*.setup')),
@@ -247,6 +249,7 @@ setuptools.setup(
'data/etc/sudoers.d/plinth'
]), ('/lib/systemd/system',
glob.glob('data/lib/systemd/system/*.service')),
+ ('/lib/systemd/system', glob.glob('data/lib/systemd/system/*.timer')),
('/etc/mediawiki',
glob.glob('data/etc/mediawiki/*.php')), ('/etc/update-motd.d/', [
'data/etc/update-motd.d/50-freedombox'