upgrades: Use privileged decorator for actions

Tests:

- DONE: Functional tests work
- DONE: Initial setup works
  - DONE: Automatic upgrades are enable by default
  - DONE: apt preferences have been updated
- DONE: Enabling backports works
  - DONE: Configuration file is created
  - DONE: Correct status is shown in the app page
- DONE: Enabling/disabling automatic upgrades works
  - DONE: Configuration file is updated
  - DONE: Correct status is shown in the app page
- DONE: Manual triggering of updates work
  - DONE: Log is shown properly in the app page
- DONE: Checking for distribution upgrade works
- DONE: Distribution upgrade from stable to testing works
  - DONE: When running on btrfs distribution, snapshot is created before.
  - DONE: Snapshots will be disable before upgrade and re-enabled later.
  - DONE: When searx is enabled before upgrade, it's uwsgi will be disabled and
    re-enabled later.
  - Failures due to freedombox package not being the latest version (with the
    changes).
- DONE: Development Vagrant box
  - DONE: Automatic updates are disabled during development setup
- DONE: Development Container
  - DONE: Automatic updates are disabled during development setup
  - DONE: On stable, backports are enabled when running tests

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2022-09-03 07:31:31 -07:00 committed by James Valleroy
parent 11a27d8efc
commit 66c1ddc404
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
5 changed files with 110 additions and 181 deletions

2
Vagrantfile vendored
View File

@ -16,7 +16,7 @@ Vagrant.configure(2) do |config|
end end
config.vm.provision "shell", run: 'always', inline: <<-SHELL config.vm.provision "shell", run: 'always', inline: <<-SHELL
# Disable automatic upgrades # Disable automatic upgrades
/vagrant/actions/upgrades disable-auto echo -e 'APT::Periodic::Update-Package-Lists "0";\nAPT::Periodic::Unattended-Upgrade "0";' > //etc/apt/apt.conf.d/20auto-upgrades
# Do not run system plinth # Do not run system plinth
systemctl stop plinth systemctl stop plinth
systemctl disable plinth systemctl disable plinth

View File

@ -193,7 +193,7 @@ mount -o remount /freedombox
if [[ "{distribution}" == "stable" && ! -e $BACKPORTS_SOURCES_LIST ]] if [[ "{distribution}" == "stable" && ! -e $BACKPORTS_SOURCES_LIST ]]
then then
echo "> In container: Enable backports" echo "> In container: Enable backports"
/freedombox/actions/upgrades activate-backports /freedombox/actions/actions upgrades activate_backports --no-args
fi fi
echo "> In container: Upgrade packages" echo "> In container: Upgrade packages"
@ -698,7 +698,10 @@ def _setup(image_file, distribution):
return return
logger.info('In container: Disabling automatic updates temporarily') logger.info('In container: Disabling automatic updates temporarily')
_runc(image_file, ['/usr/share/plinth/actions/upgrades', 'disable-auto']) contents = 'APT::Periodic::Update-Package-Lists "0";\n' \
'APT::Periodic::Unattended-Upgrade "0";\n'
_runc(image_file, ['tee', '/etc/apt/apt.conf.d/20auto-upgrades'],
input=contents.encode())
logger.info('In container: Disabling FreedomBox service') logger.info('In container: Disabling FreedomBox service')
_runc(image_file, ['systemctl', 'disable', 'plinth'], _runc(image_file, ['systemctl', 'disable', 'plinth'],

View File

@ -1,9 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """FreedomBox app for upgrades."""
FreedomBox app for upgrades.
"""
import json
import logging import logging
import os import os
import subprocess import subprocess
@ -13,14 +10,13 @@ from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop from django.utils.translation import gettext_noop
import plinth import plinth
from plinth import actions
from plinth import app as app_module from plinth import app as app_module
from plinth import cfg, glib, kvstore, menu from plinth import cfg, glib, kvstore, menu
from plinth.daemon import RelatedDaemon from plinth.daemon import RelatedDaemon
from plinth.modules.backups.components import BackupRestore from plinth.modules.backups.components import BackupRestore
from plinth.package import Packages from plinth.package import Packages
from . import manifest from . import manifest, privileged
first_boot_steps = [ first_boot_steps = [
{ {
@ -48,10 +44,6 @@ BACKPORTS_REQUESTED_KEY = 'upgrades_backports_requested'
DIST_UPGRADE_ENABLED_KEY = 'upgrades_dist_upgrade_enabled' DIST_UPGRADE_ENABLED_KEY = 'upgrades_dist_upgrade_enabled'
SOURCES_LIST = '/etc/apt/sources.list'
BACKPORTS_SOURCES_LIST = '/etc/apt/sources.list.d/freedombox2.list'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -140,11 +132,11 @@ class UpgradesApp(app_module.App):
# Enable automatic upgrades but only on first install # Enable automatic upgrades but only on first install
if not old_version and not cfg.develop: if not old_version and not cfg.develop:
actions.superuser_run('upgrades', ['enable-auto']) privileged.enable_auto()
# Update apt preferences whenever on first install and on version # Update apt preferences whenever on first install and on version
# increment. # increment.
actions.superuser_run('upgrades', ['setup']) privileged.setup()
# When upgrading from a version without first boot wizard for # When upgrading from a version without first boot wizard for
# backports, assume backports have been requested. # backports, assume backports have been requested.
@ -161,30 +153,10 @@ class UpgradesApp(app_module.App):
setup_repositories(None) setup_repositories(None)
def is_enabled():
"""Return whether the module is enabled."""
output = actions.run('upgrades', ['check-auto'])
return 'True' in output.split()
def enable():
"""Enable the module."""
actions.superuser_run('upgrades', ['enable-auto'])
def disable():
"""Disable the module."""
actions.superuser_run('upgrades', ['disable-auto'])
def setup_repositories(_): def setup_repositories(_):
"""Setup apt repositories for backports.""" """Setup apt repositories for backports."""
if is_backports_requested(): if is_backports_requested():
command = ['activate-backports'] privileged.activate_backports(cfg.develop)
if cfg.develop:
command.append('--develop')
actions.superuser_run('upgrades', command)
def check_dist_upgrade(_): def check_dist_upgrade(_):
@ -196,12 +168,8 @@ def check_dist_upgrade(_):
def try_start_dist_upgrade(test=False): def try_start_dist_upgrade(test=False):
"""Try to start dist upgrade.""" """Try to start dist upgrade."""
from plinth.notification import Notification from plinth.notification import Notification
command = ['start-dist-upgrade']
if test:
command.append('--test')
output = actions.superuser_run('upgrades', command) result = privileged.start_dist_upgrade(test)
result = json.loads(output)
dist_upgrade_started = result['dist_upgrade_started'] dist_upgrade_started = result['dist_upgrade_started']
reason = result['reason'] reason = result['reason']
if 'found-previous' in reason: if 'found-previous' in reason:
@ -270,7 +238,7 @@ def set_dist_upgrade_enabled(enabled=True):
def is_backports_enabled(): def is_backports_enabled():
"""Return whether backports are enabled in the system configuration.""" """Return whether backports are enabled in the system configuration."""
return os.path.exists(BACKPORTS_SOURCES_LIST) return os.path.exists(privileged.BACKPORTS_SOURCES_LIST)
def get_current_release(): def get_current_release():

View File

@ -1,30 +1,26 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """Configure or run unattended-upgrades."""
Configures or runs unattended-upgrades
"""
import argparse
import json
import logging import logging
import os import os
import pathlib import pathlib
import re import re
import subprocess import subprocess
import sys
import time import time
from typing import List, Tuple from typing import List, Tuple, Union
from plinth.action_utils import (apt_hold, apt_hold_flag, apt_hold_freedombox, from plinth.action_utils import (apt_hold, apt_hold_flag, apt_hold_freedombox,
apt_unhold_freedombox, debconf_set_selections, apt_unhold_freedombox, debconf_set_selections,
is_package_manager_busy, run_apt_command, is_package_manager_busy, run_apt_command,
service_daemon_reload, service_restart) service_daemon_reload, service_restart)
from plinth.actions import privileged
from plinth.modules.apache.components import check_url from plinth.modules.apache.components import check_url
from plinth.modules.snapshot import is_apt_snapshots_enabled from plinth.modules.snapshot import is_apt_snapshots_enabled
from plinth.modules.snapshot import is_supported as snapshot_is_supported from plinth.modules.snapshot import is_supported as snapshot_is_supported
from plinth.modules.snapshot import load_augeas as snapshot_load_augeas from plinth.modules.snapshot import load_augeas as snapshot_load_augeas
from plinth.modules.upgrades import (BACKPORTS_SOURCES_LIST, SOURCES_LIST,
get_current_release, is_backports_current) SOURCES_LIST = '/etc/apt/sources.list'
BACKPORTS_SOURCES_LIST = '/etc/apt/sources.list.d/freedombox2.list'
AUTO_CONF_FILE = '/etc/apt/apt.conf.d/20auto-upgrades' AUTO_CONF_FILE = '/etc/apt/apt.conf.d/20auto-upgrades'
LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades.log' LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades.log'
@ -32,7 +28,8 @@ DPKG_LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades-dpkg.log'
RELEASE_FILE_URL = \ RELEASE_FILE_URL = \
'https://deb.debian.org/debian/dists/{}/Release' 'https://deb.debian.org/debian/dists/{}/Release'
APT_PREFERENCES_FREEDOMBOX = '''Explanation: This file is managed by FreedomBox, do not edit. APT_PREFERENCES_FREEDOMBOX = \
'''Explanation: This file is managed by FreedomBox, do not edit.
Explanation: Allow carefully selected updates to 'freedombox' from backports. Explanation: Allow carefully selected updates to 'freedombox' from backports.
Package: src:freedombox Package: src:freedombox
Pin: release a={}-backports Pin: release a={}-backports
@ -42,7 +39,8 @@ Pin-Priority: 500
# Whenever these preferences needs to change, increment the version number # Whenever these preferences needs to change, increment the version number
# upgrades app. This ensures that setup is run again and the new contents are # upgrades app. This ensures that setup is run again and the new contents are
# overwritten on the old file. # overwritten on the old file.
APT_PREFERENCES_APPS = '''Explanation: This file is managed by FreedomBox, do not edit. APT_PREFERENCES_APPS = \
'''Explanation: This file is managed by FreedomBox, do not edit.
Explanation: matrix-synapse shall not be available in Debian stable but Explanation: matrix-synapse shall not be available in Debian stable but
Explanation: only in backports. Upgrade priority of packages that have needed Explanation: only in backports. Upgrade priority of packages that have needed
Explanation: versions only in backports. Explanation: versions only in backports.
@ -104,7 +102,7 @@ Description=Upgrade to new stable Debian release
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/usr/share/plinth/actions/upgrades dist-upgrade ExecStart=/usr/share/plinth/actions/actions upgrades dist_upgrade --no-args
KillMode=process KillMode=process
TimeoutSec=12hr TimeoutSec=12hr
''' '''
@ -116,46 +114,18 @@ dist_upgrade_flag = pathlib.Path(
'/var/lib/freedombox/dist-upgrade-in-progress') '/var/lib/freedombox/dist-upgrade-in-progress')
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')
activate_backports = subparsers.add_parser(
'activate-backports', help='Activate backports if possible')
activate_backports.add_argument('--develop', required=False, default=False,
action='store_true',
help='Development mode')
start_dist_upgrade = subparsers.add_parser(
'start-dist-upgrade', help='Check and start dist upgrade process')
start_dist_upgrade.add_argument(
'--test', required=False, default=False, action='store_true',
help='Test dist-upgrade from stable to testing')
subparsers.add_parser('dist-upgrade', help='Perform dist upgrade')
subparsers.required = True
return parser.parse_args()
def _release_held_freedombox(): def _release_held_freedombox():
"""In case freedombox package was left in held state by an interrupted """If freedombox package was left in held state, release it.
process, release it."""
This can happen due to an interrupted process.
"""
if apt_hold_flag.exists() and not is_package_manager_busy(): if apt_hold_flag.exists() and not is_package_manager_busy():
apt_unhold_freedombox() apt_unhold_freedombox()
def _run(): @privileged
"""Run unattended-upgrades""" def run():
"""Run unattended-upgrades."""
subprocess.run(['dpkg', '--configure', '-a'], check=False) subprocess.run(['dpkg', '--configure', '-a'], check=False)
run_apt_command(['--fix-broken', 'install']) run_apt_command(['--fix-broken', 'install'])
_release_held_freedombox() _release_held_freedombox()
@ -166,18 +136,6 @@ def _run():
start_new_session=True) start_new_session=True)
def subcommand_run(_):
"""Run unattended-upgrades"""
try:
_run()
except FileNotFoundError:
print('Error: systemctl 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 _check_auto() -> bool: def _check_auto() -> bool:
"""Check if automatic upgrades are enabled.""" """Check if automatic upgrades are enabled."""
arguments = [ arguments = [
@ -193,45 +151,48 @@ def _check_auto() -> bool:
return bool(update_interval) return bool(update_interval)
def subcommand_check_auto(_): @privileged
"""Check if automatic upgrades are enabled""" def check_auto() -> bool:
try: """Check if automatic upgrades are enabled."""
print(_check_auto()) return _check_auto()
except subprocess.CalledProcessError as error:
print('Error: {0}'.format(error), file=sys.stderr)
sys.exit(1)
def subcommand_enable_auto(_): @privileged
"""Enable automatic upgrades""" def enable_auto():
"""Enable automatic upgrades."""
with open(AUTO_CONF_FILE, 'w', encoding='utf-8') as conffile: with open(AUTO_CONF_FILE, 'w', encoding='utf-8') as conffile:
conffile.write('APT::Periodic::Update-Package-Lists "1";\n') conffile.write('APT::Periodic::Update-Package-Lists "1";\n')
conffile.write('APT::Periodic::Unattended-Upgrade "1";\n') conffile.write('APT::Periodic::Unattended-Upgrade "1";\n')
def subcommand_disable_auto(_): @privileged
"""Disable automatic upgrades""" def disable_auto():
"""Disable automatic upgrades."""
with open(AUTO_CONF_FILE, 'w', encoding='utf-8') as conffile: with open(AUTO_CONF_FILE, 'w', encoding='utf-8') as conffile:
conffile.write('APT::Periodic::Update-Package-Lists "0";\n') conffile.write('APT::Periodic::Update-Package-Lists "0";\n')
conffile.write('APT::Periodic::Unattended-Upgrade "0";\n') conffile.write('APT::Periodic::Unattended-Upgrade "0";\n')
def subcommand_get_log(_): @privileged
"""Print the automatic upgrades log.""" def get_log() -> str:
"""Return the automatic upgrades log."""
log_lines = []
try: try:
print('==> ' + os.path.basename(LOG_FILE)) log_lines.append('==> ' + os.path.basename(LOG_FILE))
with open(LOG_FILE, 'r', encoding='utf-8') as file_handle: with open(LOG_FILE, 'r', encoding='utf-8') as file_handle:
print(file_handle.read()) log_lines.append(file_handle.read())
except IOError: except IOError:
pass pass
try: try:
print('==> ' + os.path.basename(DPKG_LOG_FILE)) log_lines.append('==> ' + os.path.basename(DPKG_LOG_FILE))
with open(DPKG_LOG_FILE, 'r', encoding='utf-8') as file_handle: with open(DPKG_LOG_FILE, 'r', encoding='utf-8') as file_handle:
print(file_handle.read()) log_lines.append(file_handle.read())
except IOError: except IOError:
pass pass
return '\n'.join(log_lines)
def _get_protocol() -> str: def _get_protocol() -> str:
"""Return the protocol to use for newly added repository sources.""" """Return the protocol to use for newly added repository sources."""
@ -278,8 +239,10 @@ def _check_and_backports_sources(develop=False):
if os.path.exists(old_sources_list): if os.path.exists(old_sources_list):
os.remove(old_sources_list) os.remove(old_sources_list)
from plinth.modules.upgrades import (get_current_release,
is_backports_current)
if is_backports_current(): if is_backports_current():
print('Repositories list up-to-date. Skipping update.') logging.info('Repositories list up-to-date. Skipping update.')
return return
try: try:
@ -290,25 +253,26 @@ def _check_and_backports_sources(develop=False):
for line in default_origin.readlines() for line in default_origin.readlines()
] ]
except FileNotFoundError: except FileNotFoundError:
print('Could not open /etc/dpkg/origins/default') logging.info('Could not open /etc/dpkg/origins/default')
return return
if not any(matches): if not any(matches):
print('System is running a derivative of Debian. Skip enabling ' logging.info('System is running a derivative of Debian. Skip enabling '
'backports.') 'backports.')
return return
release, dist = get_current_release() release, dist = get_current_release()
if release == 'unstable' or (release == 'testing' and not develop): if release == 'unstable' or (release == 'testing' and not develop):
print(f'System release is {release}. Skip enabling backports.') logging.info(f'System release is {release}. Skip enabling backports.')
return return
protocol = _get_protocol() protocol = _get_protocol()
if protocol == 'tor+http': if protocol == 'tor+http':
print('Package download over Tor is enabled.') logging.info('Package download over Tor is enabled.')
if not _is_release_file_available(protocol, dist, backports=True): if not _is_release_file_available(protocol, dist, backports=True):
print(f'Release file for {dist}-backports is not available yet.') logging.info(
f'Release file for {dist}-backports is not available yet.')
return return
print(f'{dist}-backports is now available. Adding to sources.') print(f'{dist}-backports is now available. Adding to sources.')
@ -328,12 +292,14 @@ def _add_apt_preferences():
# Don't try to remove 50freedombox3.pref as this file is shipped with the # Don't try to remove 50freedombox3.pref as this file is shipped with the
# Debian package and is removed using maintainer scripts. # Debian package and is removed using maintainer scripts.
from plinth.modules.upgrades import get_current_release
_, dist = get_current_release() _, dist = get_current_release()
if dist == 'sid': if dist == 'sid':
print(f'System distribution is {dist}. Skip setting apt preferences ' logging.info(
'for backports.') f'System distribution is {dist}. Skip setting apt preferences '
'for backports.')
else: else:
print(f'Setting apt preferences for {dist}-backports.') logging.info(f'Setting apt preferences for {dist}-backports.')
with open(base_path / '50freedombox4.pref', 'w', with open(base_path / '50freedombox4.pref', 'w',
encoding='utf-8') as file_handle: encoding='utf-8') as file_handle:
file_handle.write(APT_PREFERENCES_FREEDOMBOX.format(dist)) file_handle.write(APT_PREFERENCES_FREEDOMBOX.format(dist))
@ -350,17 +316,20 @@ def _is_sufficient_free_space() -> bool:
def _check_dist_upgrade(test_upgrade=False) -> Tuple[bool, str]: def _check_dist_upgrade(test_upgrade=False) -> Tuple[bool, str]:
"""Check for new stable release, if updates are enabled, and if there is """Check if a distribution upgrade be performed.
Check for new stable release, if updates are enabled, and if there is
enough free space for the dist upgrade. enough free space for the dist upgrade.
If test_upgrade is True, also check for upgrade to testing. If test_upgrade is True, also check for upgrade to testing.
Returns (boolean, string) indicating if the upgrade is ready, and a reason Return (boolean, string) indicating if the upgrade is ready, and a reason
if not. if not.
""" """
if dist_upgrade_flag.exists(): if dist_upgrade_flag.exists():
return (True, 'found-previous') return (True, 'found-previous')
from plinth.modules.upgrades import get_current_release
release, dist = get_current_release() release, dist = get_current_release()
if release in ['unstable', 'testing']: if release in ['unstable', 'testing']:
return (False, f'already-{release}') return (False, f'already-{release}')
@ -429,7 +398,8 @@ def _check_dist_upgrade(test_upgrade=False) -> Tuple[bool, str]:
def _take_snapshot_and_disable() -> bool: def _take_snapshot_and_disable() -> bool:
"""Take a snapshot if supported and enabled, then disable snapshots. """Take a snapshot if supported and enabled, then disable snapshots.
Return whether snapshots shall be re-enabled at the end.""" Return whether snapshots shall be re-enabled at the end.
"""
if snapshot_is_supported(): if snapshot_is_supported():
print('Taking a snapshot before dist upgrade...', flush=True) print('Taking a snapshot before dist upgrade...', flush=True)
subprocess.run([ subprocess.run([
@ -467,7 +437,8 @@ def _restore_snapshots_config(reenable=False):
def _disable_searx() -> bool: def _disable_searx() -> bool:
"""If searx is enabled, disable it until we can upgrade it properly. """If searx is enabled, disable it until we can upgrade it properly.
Return whether searx was originally enabled.""" Return whether searx was originally enabled.
"""
searx_is_enabled = pathlib.Path( searx_is_enabled = pathlib.Path(
'/etc/uwsgi/apps-enabled/searx.ini').exists() '/etc/uwsgi/apps-enabled/searx.ini').exists()
if searx_is_enabled: if searx_is_enabled:
@ -481,7 +452,9 @@ def _disable_searx() -> bool:
def _update_searx(reenable=False): def _update_searx(reenable=False):
"""If searx is installed, update search engines list. """If searx is installed, update search engines list.
Re-enable if previously enabled."""
Re-enable if previously enabled.
"""
if pathlib.Path('/etc/searx/settings.yml').exists(): if pathlib.Path('/etc/searx/settings.yml').exists():
print('Updating searx search engines list...', flush=True) print('Updating searx search engines list...', flush=True)
subprocess.run([ subprocess.run([
@ -567,19 +540,20 @@ def _perform_dist_upgrade():
dist_upgrade_flag.unlink() dist_upgrade_flag.unlink()
def subcommand_setup(_): @privileged
def setup():
"""Setup apt preferences.""" """Setup apt preferences."""
_add_apt_preferences() _add_apt_preferences()
def subcommand_activate_backports(arguments): @privileged
def activate_backports(develop: bool = False):
"""Setup software repositories needed for FreedomBox. """Setup software repositories needed for FreedomBox.
Repositories list for now only contains the backports. If the file exists, Repositories list for now only contains the backports. If the file exists,
assume that it contains backports. assume that it contains backports.
""" """
_check_and_backports_sources(arguments.develop) _check_and_backports_sources(develop)
def _start_dist_upgrade_service(): def _start_dist_upgrade_service():
@ -595,7 +569,8 @@ def _start_dist_upgrade_service():
start_new_session=True) start_new_session=True)
def subcommand_start_dist_upgrade(arguments): @privileged
def start_dist_upgrade(test: bool = False) -> dict[str, Union[str, bool]]:
"""Start dist upgrade process. """Start dist upgrade process.
Check if a new stable release is available, and start dist-upgrade process Check if a new stable release is available, and start dist-upgrade process
@ -603,31 +578,14 @@ def subcommand_start_dist_upgrade(arguments):
""" """
_release_held_freedombox() _release_held_freedombox()
upgrade_ready, reason = _check_dist_upgrade(arguments.test) upgrade_ready, reason = _check_dist_upgrade(test)
if upgrade_ready: if upgrade_ready:
_start_dist_upgrade_service() _start_dist_upgrade_service()
print( return {'dist_upgrade_started': upgrade_ready, 'reason': reason}
json.dumps({
'dist_upgrade_started': upgrade_ready,
'reason': reason,
}))
def subcommand_dist_upgrade(_): @privileged
"""Perform major distribution upgrade. def dist_upgrade():
""" """Perform major distribution upgrade."""
_perform_dist_upgrade() _perform_dist_upgrade()
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()

View File

@ -1,7 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """FreedomBox app for upgrades."""
FreedomBox app for upgrades.
"""
import subprocess import subprocess
from apt.cache import Cache from apt.cache import Cache
@ -13,34 +12,37 @@ from django.utils.translation import gettext as _
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from plinth import __version__, actions, package from plinth import __version__, package
from plinth.errors import ActionError
from plinth.modules import first_boot, upgrades from plinth.modules import first_boot, upgrades
from plinth.views import AppView from plinth.views import AppView
from . import privileged
from .forms import BackportsFirstbootForm, ConfigureForm, UpdateFirstbootForm from .forms import BackportsFirstbootForm, ConfigureForm, UpdateFirstbootForm
class UpgradesConfigurationView(AppView): class UpgradesConfigurationView(AppView):
"""Serve configuration page.""" """Serve configuration page."""
form_class = ConfigureForm form_class = ConfigureForm
success_url = reverse_lazy('upgrades:index') success_url = reverse_lazy('upgrades:index')
template_name = "upgrades_configure.html" template_name = "upgrades_configure.html"
app_id = 'upgrades' app_id = 'upgrades'
def get_initial(self): def get_initial(self):
"""Return the initial values for the form."""
return { return {
'auto_upgrades_enabled': upgrades.is_enabled(), 'auto_upgrades_enabled': privileged.check_auto(),
'dist_upgrade_enabled': upgrades.is_dist_upgrade_enabled() 'dist_upgrade_enabled': upgrades.is_dist_upgrade_enabled()
} }
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
"""Add additional context data for template."""
context = super().get_context_data(*args, **kwargs) context = super().get_context_data(*args, **kwargs)
context['can_activate_backports'] = upgrades.can_activate_backports() context['can_activate_backports'] = upgrades.can_activate_backports()
context['is_backports_requested'] = upgrades.is_backports_requested() context['is_backports_requested'] = upgrades.is_backports_requested()
context['is_busy'] = (_is_updating() context['is_busy'] = (_is_updating()
or package.is_package_manager_busy()) or package.is_package_manager_busy())
context['log'] = get_log() context['log'] = privileged.get_log()
context['refresh_page_sec'] = 3 if context['is_busy'] else None context['refresh_page_sec'] = 3 if context['is_busy'] else None
context['version'] = __version__ context['version'] = __version__
context['new_version'] = is_newer_version_available() context['new_version'] = is_newer_version_available()
@ -58,10 +60,10 @@ class UpgradesConfigurationView(AppView):
try: try:
if new_status['auto_upgrades_enabled']: if new_status['auto_upgrades_enabled']:
upgrades.enable() privileged.enable_auto()
else: else:
upgrades.disable() privileged.disable_auto()
except ActionError as exception: except Exception as exception:
error = exception.args[2] error = exception.args[2]
messages.error( messages.error(
self.request, self.request,
@ -89,14 +91,14 @@ class UpgradesConfigurationView(AppView):
def is_newer_version_available(): def is_newer_version_available():
"""Returns whether a newer Freedombox version is available.""" """Return whether a newer Freedombox version is available."""
cache = Cache() cache = Cache()
freedombox = cache['freedombox'] freedombox = cache['freedombox']
return not freedombox.candidate.is_installed return not freedombox.candidate.is_installed
def get_os_release(): def get_os_release():
"""Returns the Debian release number and name.""" """Return the Debian release number and name."""
output = 'Error: Cannot read PRETTY_NAME in /etc/os-release.' output = 'Error: Cannot read PRETTY_NAME in /etc/os-release.'
with open('/etc/os-release', 'r', encoding='utf-8') as release_file: with open('/etc/os-release', 'r', encoding='utf-8') as release_file:
for line in release_file: for line in release_file:
@ -107,11 +109,6 @@ def get_os_release():
return output return output
def get_log():
"""Return the current log for unattended upgrades."""
return actions.superuser_run('upgrades', ['get-log'])
def _is_updating(): def _is_updating():
"""Check if manually triggered update is running.""" """Check if manually triggered update is running."""
command = ['systemctl', 'is-active', 'freedombox-manual-upgrade'] command = ['systemctl', 'is-active', 'freedombox-manual-upgrade']
@ -124,9 +121,9 @@ def upgrade(request):
"""Serve the upgrade page.""" """Serve the upgrade page."""
if request.method == 'POST': if request.method == 'POST':
try: try:
actions.superuser_run('upgrades', ['run']) privileged.run()
messages.success(request, _('Upgrade process started.')) messages.success(request, _('Upgrade process started.'))
except ActionError: except Exception:
messages.error(request, _('Starting upgrade failed.')) messages.error(request, _('Starting upgrade failed.'))
return redirect(reverse_lazy('upgrades:index')) return redirect(reverse_lazy('upgrades:index'))
@ -144,6 +141,7 @@ def activate_backports(request):
class BackportsFirstbootView(FormView): class BackportsFirstbootView(FormView):
"""View to configure backports during first boot wizard.""" """View to configure backports during first boot wizard."""
template_name = 'backports-firstboot.html' template_name = 'backports-firstboot.html'
form_class = BackportsFirstbootForm form_class = BackportsFirstbootForm
@ -179,6 +177,7 @@ class BackportsFirstbootView(FormView):
class UpdateFirstbootView(FormView): class UpdateFirstbootView(FormView):
"""View to run initial update during first boot wizard.""" """View to run initial update during first boot wizard."""
template_name = 'update-firstboot.html' template_name = 'update-firstboot.html'
form_class = UpdateFirstbootForm form_class = UpdateFirstbootForm
@ -197,7 +196,7 @@ class UpdateFirstbootView(FormView):
"""Run update if selected, and mark step as done.""" """Run update if selected, and mark step as done."""
self.update = form.cleaned_data['update_now'] self.update = form.cleaned_data['update_now']
if self.update: if self.update:
actions.superuser_run('upgrades', ['run']) privileged.run()
first_boot.mark_step_done('initial_update') first_boot.mark_step_done('initial_update')
return super().form_valid(form) return super().form_valid(form)
@ -205,6 +204,7 @@ class UpdateFirstbootView(FormView):
class UpdateFirstbootProgressView(TemplateView): class UpdateFirstbootProgressView(TemplateView):
"""View to show initial update progress.""" """View to show initial update progress."""
template_name = 'update-firstboot-progress.html' template_name = 'update-firstboot-progress.html'
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):