diff --git a/plinth/setup.py b/plinth/setup.py
index dddbe40bf..6d4ed71ab 100644
--- a/plinth/setup.py
+++ b/plinth/setup.py
@@ -29,12 +29,12 @@ _is_shutting_down = False
_force_upgrader = None
-def run_setup_on_app(app_id, allow_install=True):
+def run_setup_on_app(app_id, allow_install=True, rerun=False):
"""Execute the setup process in a thread."""
# App is already up-to-date
app = app_module.App.get(app_id)
current_version = app.get_setup_version()
- if current_version >= app.info.version:
+ if not rerun and current_version >= app.info.version:
return
if not current_version:
diff --git a/plinth/templates/toolbar.html b/plinth/templates/toolbar.html
index ffd03b7a1..26d19b314 100644
--- a/plinth/templates/toolbar.html
+++ b/plinth/templates/toolbar.html
@@ -45,6 +45,14 @@
{% trans "Restore" %}
{% endif %}
+ {% if show_rerun_setup %}
+
+ {% endif %}
{% if show_uninstall %}
[1-9a-z\-_]+)/$',
views.UninstallView.as_view(), name='uninstall'),
+ re_path(r'^rerun-setup/(?P[1-9a-z\-_]+)/$', views.rerun_setup_view,
+ name='rerun-setup'),
# captcha urls are public
re_path(r'^captcha/image/(?P\w+)/$', public(cviews.captcha_image),
diff --git a/plinth/views.py b/plinth/views.py
index 6d6e295de..8cb5d4c20 100644
--- a/plinth/views.py
+++ b/plinth/views.py
@@ -10,9 +10,11 @@ import urllib.parse
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect
+from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.translation import gettext as _
+from django.views.decorators.http import require_POST
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from stronghold.decorators import public
@@ -275,6 +277,7 @@ class AppView(FormView):
context['has_diagnostics'] = self.app.has_diagnostics()
context['port_forwarding_info'] = get_port_forwarding_info(self.app)
context['app_enable_disable_form'] = self.get_enable_disable_form()
+ context['show_rerun_setup'] = True
context['show_uninstall'] = not self.app.info.is_essential
context['refresh_page_sec'] = None
@@ -335,6 +338,7 @@ class SetupView(TemplateView):
setup_state = app.get_setup_state()
context['setup_state'] = setup_state
context['operations'] = operation.manager.filter(app.app_id)
+ context['show_rerun_setup'] = False
context['show_uninstall'] = (
not app.info.is_essential
and setup_state != app_module.App.SetupState.NEEDS_SETUP)
@@ -399,6 +403,24 @@ class SetupView(TemplateView):
if component.has_unavailable_packages())
+@require_POST
+def rerun_setup_view(request, app_id):
+ """Re-run setup on an app.
+
+ This should be safe to perform on an already setup/running app. This may be
+ useful in situations where the app is broken for unknown reason as notified
+ by the diagnostics tests.
+ """
+ # Start the application setup, and refresh the page every few seconds to
+ # keep displaying the status.
+ setup.run_setup_on_app(app_id, rerun=True)
+
+ # Give a moment for the setup process to start and show meaningful status.
+ time.sleep(1)
+
+ return redirect(reverse(f'{app_id}:index'))
+
+
class UninstallView(FormView):
"""View to uninstall apps."""