mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-21 07:55:00 +00:00
- 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>
230 lines
7.3 KiB
Python
Executable File
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()
|