FreedomBox/actions/upgrades
Joseph Nuthalpati 9b46d1a661
matrix-synapse: Allow installation of version 1.2 from backports
Used version pinning instead of release pinning to avoid unexpected upgrades for
users running FreedomBox stable.

Explanation for backports:
This fixes incompatibility issues with newly created rooms on Matrix Synapse
versions 0.99.5 and above. Users on stable using 0.99.2 might not be able to
join those rooms, especially direct chats.

Fixes #1600

Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2019-08-20 20:14:36 -04:00

237 lines
7.5 KiB
Python
Executable File

#!/usr/bin/python3
#
# 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 <http://www.gnu.org/licenses/>.
#
"""
Configures or runs unattended-upgrades
"""
import argparse
import os
import re
import subprocess
import sys
from plinth import action_utils
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'
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-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 = action_utils.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."""
preferences_path = '/etc/apt/preferences.d'
old_preferences_files = map(
lambda pref_file: os.path.join(preferences_path, pref_file),
['50freedombox.pref', '50freedombox2.pref'])
for pref_file in old_preferences_files:
os.path.exists(pref_file) and os.remove(pref_file)
preferences_file = '/etc/apt/preferences.d/50freedombox3.pref'
if os.path.exists(preferences_file):
print('Preferences up-to-date. Skipping update')
return
warning = 'Explanation: This file is managed by FreedomBox, do not edit.'
freedombox_preference = '''
Explanation: Allow carefully selected updates to 'freedombox' from backports.
Package: freedombox
Pin: release a=buster-backports
Pin-Priority: 500
'''
matrix_synapse_preferences = '''
Explanation: Allow carefully selected updates to 'matrix-synapse' from backports.
Package: matrix-synapse
Pin: version 1.2*
Pin-Priority: 500
Explanation: Allow carefully selected updates to 'python3-service-identity' from backports.
Package: python3-service-identity
Pin: version 18.1*
Pin-Priority: 500
'''
preferences = "".join(
[warning, freedombox_preference, matrix_synapse_preferences])
print('Updating APT preferences.')
with open(preferences_file, 'w') as file_handle:
file_handle.write(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()
_add_apt_preferences()
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()