mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-06-17 11:10:23 +00:00
Closes: #2090 - Create a new page for distribution upgrade. - If distribution upgrade is running show its status here without any other UI. - Show various conditions for not allowing distribution upgrades. - Automatic updates disabled - Distribution updates disabled - Not enough free space. - Unknown or mixed distribution in sources.list. - If distribution upgrade was interrupted, show that information here and allow triggering distribution upgrade again. This is detected by noticing that codename in base-files is higher than one detected in sources.list. - If the user is not testing/unstable, show a message and don't allow triggering. - If next stable has not been released, don't auto-upgrade but allow manual upgrade. Show special warnings. - If next stable has been released but only recently, don't auto-upgrade but allow manual upgrade. - If next stable has been released and it has been 30 days, allow auto-upgrade and manual upgrade. - Seek confirmation before triggering manual upgrade. Provide appropriate advice. - Rely on hard-coded list of releases and their release dates instead of querying the server. Tests: - When automatic updates or distribution updates are disabled, an alert message is shown distribution upgrade page. If both are disabled, both messages show up in the alert. The start distribution upgrade button is disabled. Clicking on the button does not work. - Reducing the available free disk space will cause alert message to show up and start upgrade button to be disabled. - When the distribution in /etc/apt/sources.list is mixed or unknown, an alert message is shown. the start distribution upgrade button is disabled. - When the distribution in /etc/apt/sources.list is testing or unstable, an alert message is shown "You are on a rolling release distribution...". the start distribution upgrade button is disabled. The current distribution is 'None (testing)' or 'None (unstable)'. Next stable distribution is Unknown. - If get_current_release is hard-coded to return (None, 'trixie'). Then a message is show in the distribution update page 'A previous run of distribution update may have been interrupted. Please re-run the distribution update.' A 'Continue Distribution Update' button is shown in warning color. The button takes to confirm page where the confirm button is shown in blue and is enabled. - On a bookworm VM, visiting the page shows the message "You are on the latest stable distribution...". Upgrade button shows in red. Clicking it takes to confirmation page. The page shows a warning alert and red confirmation button. - Setting the clock to '2025-08-21' shows the message "A new stable distribution is available. Your FreedomBox will be update automatically in 4 weeks...". Upgrade button shows in blue. Clicking it takes to confirmation page. The page does show warning. The button is in blue. - Setting the clock to '2025-09-30' shows the message "A new status distribution is available. Your FreedomBox will be updated automatically soon...". Upgrade button shows in blue. Clicking it takes to confirmation page. The page does show warning. The button is in blue. - Clicking the confirmation button starts the distribution upgrade process. This distribution upgrade page is shown. The page shows spinner with a message and no other UI. Page is refreshed every 3 seconds. When the distribution upgrade process is completed, the page shows the current status. - Killing the apt-get process during distribution upgrade stop the page refresh. The page shows that process was interrupted and also continuation. Clicking on the confirmation button resumes the distribution upgrade process. - After distribution upgrade, the page shows the current distribution and next distribution properly. There is not release date for the next distribution. A message shows: "Next stable distribution is not available yet." Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
202 lines
7.1 KiB
Python
202 lines
7.1 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""FreedomBox app for upgrades."""
|
|
|
|
import subprocess
|
|
|
|
from apt.cache import Cache
|
|
from django.contrib import messages
|
|
from django.http import HttpResponseRedirect
|
|
from django.shortcuts import redirect
|
|
from django.urls import reverse_lazy
|
|
from django.utils.translation import gettext as _
|
|
from django.views.generic import TemplateView
|
|
from django.views.generic.edit import FormView
|
|
|
|
from plinth import __version__
|
|
from plinth.modules import first_boot, upgrades
|
|
from plinth.privileged import packages as packages_privileged
|
|
from plinth.views import AppView, messages_error
|
|
|
|
from . import distupgrade, privileged
|
|
from .forms import BackportsFirstbootForm, ConfigureForm
|
|
|
|
|
|
class UpgradesConfigurationView(AppView):
|
|
"""Serve configuration page."""
|
|
|
|
form_class = ConfigureForm
|
|
success_url = reverse_lazy('upgrades:index')
|
|
template_name = "upgrades_configure.html"
|
|
app_id = 'upgrades'
|
|
|
|
def get_initial(self):
|
|
"""Return the initial values for the form."""
|
|
return {
|
|
'auto_upgrades_enabled': privileged.check_auto(),
|
|
'dist_upgrade_enabled': upgrades.is_dist_upgrade_enabled()
|
|
}
|
|
|
|
def get_context_data(self, *args, **kwargs):
|
|
"""Add additional context data for template."""
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context['can_activate_backports'] = upgrades.can_activate_backports()
|
|
context['is_backports_requested'] = upgrades.is_backports_requested()
|
|
context['is_busy'] = (_is_updating()
|
|
or packages_privileged.is_package_manager_busy())
|
|
context['log'] = privileged.get_log()
|
|
context['refresh_page_sec'] = 3 if context['is_busy'] else None
|
|
context['version'] = __version__
|
|
context['new_version'] = is_newer_version_available()
|
|
context['os_release'] = get_os_release()
|
|
return context
|
|
|
|
def form_valid(self, form):
|
|
"""Apply the form changes."""
|
|
old_status = form.initial
|
|
new_status = form.cleaned_data
|
|
|
|
is_changed = False
|
|
|
|
if old_status['auto_upgrades_enabled'] \
|
|
!= new_status['auto_upgrades_enabled']:
|
|
|
|
try:
|
|
if new_status['auto_upgrades_enabled']:
|
|
privileged.enable_auto()
|
|
else:
|
|
privileged.disable_auto()
|
|
|
|
is_changed = True
|
|
except Exception as exception:
|
|
messages_error(self.request,
|
|
_('Error when configuring unattended-upgrades'),
|
|
exception)
|
|
|
|
if old_status['dist_upgrade_enabled'] \
|
|
!= new_status['dist_upgrade_enabled']:
|
|
upgrades.set_dist_upgrade_enabled(
|
|
new_status['dist_upgrade_enabled'])
|
|
is_changed = True
|
|
|
|
if is_changed:
|
|
messages.success(self.request, _('Configuration updated.'))
|
|
|
|
return super().form_valid(form)
|
|
|
|
|
|
class DistUpgradeView(TemplateView):
|
|
"""View to show status of distribution upgrade."""
|
|
template_name = 'upgrades-dist-upgrade.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Return additional context for rendering the template."""
|
|
context = super().get_context_data(**kwargs)
|
|
context['status'] = distupgrade.get_status()
|
|
context['refresh_page_sec'] = None
|
|
if context['status']['running']:
|
|
context['refresh_page_sec'] = 3
|
|
|
|
return context
|
|
|
|
|
|
class DistUpgradeConfirmView(TemplateView):
|
|
"""View to confirm and trigger trigger distribution upgrade."""
|
|
template_name = 'upgrades-dist-upgrade-confirm.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Return additional context for rendering the template."""
|
|
context = super().get_context_data(**kwargs)
|
|
context['status'] = distupgrade.get_status()
|
|
return context
|
|
|
|
def post(self, request):
|
|
"""Start the distribution upgrade process."""
|
|
privileged.start_dist_upgrade()
|
|
messages.success(request, _('Started distribution update.'))
|
|
return redirect(reverse_lazy('upgrades:dist-upgrade'))
|
|
|
|
|
|
def is_newer_version_available():
|
|
"""Return whether a newer Freedombox version is available."""
|
|
cache = Cache()
|
|
freedombox = cache['freedombox']
|
|
return not freedombox.candidate.is_installed
|
|
|
|
|
|
def get_os_release():
|
|
"""Return the Debian release number and name."""
|
|
output = 'Error: Cannot read PRETTY_NAME in /etc/os-release.'
|
|
with open('/etc/os-release', 'r', encoding='utf-8') as release_file:
|
|
for line in release_file:
|
|
if 'PRETTY_NAME=' in line:
|
|
line = line.replace('"', '').strip()
|
|
line = line.split('=')
|
|
output = line[1]
|
|
return output
|
|
|
|
|
|
def _is_updating():
|
|
"""Check if manually triggered update is running."""
|
|
command = ['systemctl', 'is-active', 'freedombox-manual-upgrade']
|
|
result = subprocess.run(command, capture_output=True, text=True,
|
|
check=False)
|
|
return str(result.stdout).startswith('activ') # 'active' or 'activating'
|
|
|
|
|
|
def upgrade(request):
|
|
"""Serve the upgrade page."""
|
|
if request.method == 'POST':
|
|
try:
|
|
privileged.run()
|
|
messages.success(request, _('Upgrade process started.'))
|
|
except Exception:
|
|
messages.error(request, _('Starting upgrade failed.'))
|
|
|
|
return redirect(reverse_lazy('upgrades:index'))
|
|
|
|
|
|
def activate_backports(request):
|
|
"""Activate backports."""
|
|
if request.method == 'POST':
|
|
upgrades.set_backports_requested(True)
|
|
upgrades.setup_repositories(None)
|
|
messages.success(request, _('Frequent feature updates activated.'))
|
|
|
|
return redirect(reverse_lazy('upgrades:index'))
|
|
|
|
|
|
class BackportsFirstbootView(FormView):
|
|
"""View to configure backports during first boot wizard."""
|
|
|
|
template_name = 'backports-firstboot.html'
|
|
form_class = BackportsFirstbootForm
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
"""Show backports configuration form only if it can be activated."""
|
|
if upgrades.is_backports_enabled():
|
|
# Backports is already enabled. Record this preference and
|
|
# skip first boot step.
|
|
upgrades.set_backports_requested(True)
|
|
first_boot.mark_step_done('backports_wizard')
|
|
return HttpResponseRedirect(reverse_lazy(first_boot.next_step()))
|
|
|
|
if not upgrades.can_activate_backports():
|
|
# Skip first boot step.
|
|
upgrades.set_backports_requested(False)
|
|
first_boot.mark_step_done('backports_wizard')
|
|
return HttpResponseRedirect(reverse_lazy(first_boot.next_step()))
|
|
|
|
return super().dispatch(request, *args, *kwargs)
|
|
|
|
def get_success_url(self):
|
|
"""Return next firstboot step."""
|
|
return reverse_lazy(first_boot.next_step())
|
|
|
|
def form_valid(self, form):
|
|
"""Mark the first wizard step as done, save value and redirect."""
|
|
enabled = form.cleaned_data['backports_enabled']
|
|
upgrades.set_backports_requested(enabled)
|
|
upgrades.setup_repositories(None)
|
|
first_boot.mark_step_done('backports_wizard')
|
|
return super().form_valid(form)
|