FreedomBox/actions/upgrades
Sunil Mohan Adapa 6179d98a07
upgrades: Don't ship apt backport preferences file
- Don't ship the file preferences file as this is a violation of the Debian
policy. Lintian throws a hard error that can't be overridden. Remove the lintian
override. Remove this file using maintainer scripts when upgrading from all
version below 20.5.

- The preferences file is now renamed to 50freedombox4.pref.

- Instead write the file when the app is getting setup (on each new version).

- Don't run the setup code on daily timer, instead run the code when the app
upgrades. This ensures that as soon as freedombox package is upgraded and run,
the new preferences file is created instead of waiting for the daily timer to
run.

- From now on when the preferences change, we will increment the version number
of the upgrades app. Change the setup() for the app so that it does not
re-enable automatic upgrades every time setup() is run.

Closes: #1673.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2020-03-20 13:59:58 -04:00

230 lines
7.3 KiB
Python
Executable File

#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configures or runs unattended-upgrades
"""
import argparse
import os
import pathlib
import re
import subprocess
import sys
from plinth.modules.apache.components import check_url
AUTO_CONF_FILE = '/etc/apt/apt.conf.d/20auto-upgrades'
LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades.log'
BUSTER_BACKPORTS_RELEASE_FILE_URL = \
'https://deb.debian.org/debian/dists/buster-backports/Release'
# Whenever these preferences needs to change, increment the version number
# upgrades app. This ensures that setup is run again and the new contents are
# overwritten on the old file.
APT_PREFERENCES = '''Explanation: This file is managed by FreedomBox, do not edit.
Explanation: Allow carefully selected updates to 'freedombox' from backports.
Package: freedombox
Pin: release a=buster-backports
Pin-Priority: 500
Explanation: matrix-synapse 0.99.5 introduces room version 4. Older version
Explanation: 0.99.2 in buster won't be able join newly created rooms.
Package: matrix-synapse
Pin: release a=buster-backports
Pin-Priority: 500
Explanation: matrix-synapse >= 1.2 requires python3-service-identity >= 18.1
Package: python3-service-identity
Pin: release a=buster-backports
Pin-Priority: 500
Explanation: matrix-synapse >= 1.5 requires python3-typing-extensions >= 3.7.4
Package: python3-typing-extensions
Pin: release a=buster-backports
Pin-Priority: 500
Explanation: matrix-synapse >= 1.11 requires python3-signedjson >= 1.1.0
Package: python3-signedjson
Pin: release a=buster-backports
Pin-Priority: 500
'''
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('run', help='Upgrade packages on the system')
subparsers.add_parser('check-auto',
help='Check if automatic upgrades are enabled')
subparsers.add_parser('enable-auto', help='Enable automatic upgrades')
subparsers.add_parser('disable-auto', help='Disable automatic upgrades.')
subparsers.add_parser('get-log', help='Print the automatic upgrades log')
subparsers.add_parser('setup', help='Setup apt preferences')
subparsers.add_parser('setup-repositories',
help='Setup software repositories for FreedomBox')
subparsers.required = True
return parser.parse_args()
def subcommand_run(_):
"""Run unattended-upgrades"""
try:
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)
except Exception as error:
print('Error: {0}'.format(error), file=sys.stderr)
sys.exit(3)
def subcommand_check_auto(_):
"""Check if automatic upgrades are enabled"""
arguments = [
'apt-config', 'shell', 'UpdateInterval',
'APT::Periodic::Update-Package-Lists'
]
try:
output = subprocess.check_output(arguments).decode()
except subprocess.CalledProcessError as error:
print('Error: {0}'.format(error), file=sys.stderr)
sys.exit(1)
update_interval = 0
match = re.match(r"UpdateInterval='(.*)'", output)
if match:
update_interval = int(match.group(1))
print(bool(update_interval))
def subcommand_enable_auto(_):
"""Enable automatic upgrades"""
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"""
with open(AUTO_CONF_FILE, 'w') as conffile:
conffile.write('APT::Periodic::Update-Package-Lists "0";\n')
conffile.write('APT::Periodic::Unattended-Upgrade "0";\n')
def subcommand_get_log(_):
"""Print the automatic upgrades log."""
try:
with open(LOG_FILE, 'r') as file_handle:
print(file_handle.read())
except IOError:
pass
def _get_protocol():
"""Return the protocol to use for newly added repository sources."""
try:
from plinth.modules.tor import utils
if utils.is_apt_transport_tor_enabled():
return 'tor+http'
except Exception:
pass
return 'http'
def _is_release_file_available(protocol):
"""Return whether the release for backports is available."""
wrapper = None
if protocol == 'tor+http':
wrapper = 'torsocks'
result = check_url(BUSTER_BACKPORTS_RELEASE_FILE_URL, wrapper=wrapper)
return result == 'passed'
def _add_buster_backports_sources(sources_list, protocol):
"""Add buster backports sources to freedombox repositories list."""
sources = '''# This file is managed by FreedomBox, do not edit.
# Allow carefully selected updates to 'freedombox' from backports.
deb {protocol}://deb.debian.org/debian buster-backports main
deb-src {protocol}://deb.debian.org/debian buster-backports main
'''
sources = sources.format(protocol=protocol)
with open(sources_list, 'w') as file_handle:
file_handle.write(sources)
def _check_and_backports_sources():
"""Add buster backports sources after checking if it is available."""
old_sources_list = '/etc/apt/sources.list.d/freedombox.list'
if os.path.exists(old_sources_list):
os.remove(old_sources_list)
sources_list = '/etc/apt/sources.list.d/freedombox2.list'
if os.path.exists(sources_list):
print('Repositories list up-to-date. Skipping update.')
return
protocol = _get_protocol()
if protocol == 'tor+http':
print('Package download over Tor is enabled.')
if not _is_release_file_available(protocol):
print('Release file for Buster backports is not available yet.')
return
print('Buster backports is now available. Adding to sources.')
_add_buster_backports_sources(sources_list, protocol)
def _add_apt_preferences():
"""Setup APT preferences to upgrade selected packages from backports."""
base_path = pathlib.Path('/etc/apt/preferences.d')
for file_name in ['50freedombox.pref', '50freedombox2.pref']:
full_path = base_path / file_name
if full_path.exists():
full_path.unlink()
# Don't try to remove 50freedombox3.pref as this file is shipped with the
# Debian package and is removed using maintainer scripts.
with open(base_path / '50freedombox4.pref', 'w') as file_handle:
file_handle.write(APT_PREFERENCES)
def subcommand_setup(_):
"""Setup apt preferences."""
_add_apt_preferences()
def subcommand_setup_repositories(_):
"""Setup software repositories needed for FreedomBox.
Repositories list for now only contains the backports. If the file exists,
assume that it contains backports.
"""
_check_and_backports_sources()
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()