mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-27 10:44:33 +00:00
diagnostics: Add option for automatic repair
- Not enabled by default currently. This can be changed after further testing. - Re-use existing operation from diagnostics run. However, this requires changing the app_id of the operation for each app. Tests: - Enable automatic repair, and run diagnostics. See that repairs are run. - Enable automatic repair, and wait for daily diagnostics run. See that repairs are run. Closes: #2399. Signed-off-by: James Valleroy <jvalleroy@mailbox.org> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
parent
e7e9642a11
commit
d87685b95a
@ -21,6 +21,7 @@ from plinth.diagnostic_check import (CheckJSONDecoder, CheckJSONEncoder,
|
|||||||
DiagnosticCheck, Result)
|
DiagnosticCheck, Result)
|
||||||
from plinth.modules.apache.components import diagnose_url_on_all
|
from plinth.modules.apache.components import diagnose_url_on_all
|
||||||
from plinth.modules.backups.components import BackupRestore
|
from plinth.modules.backups.components import BackupRestore
|
||||||
|
from plinth.setup import run_repair_on_app
|
||||||
|
|
||||||
from . import manifest
|
from . import manifest
|
||||||
|
|
||||||
@ -256,7 +257,7 @@ def _daily_diagnostics_run(data: None = None):
|
|||||||
logger.info('Skipping daily diagnostics run (disabled)')
|
logger.info('Skipping daily diagnostics run (disabled)')
|
||||||
|
|
||||||
|
|
||||||
def start_diagnostics(data: None = None):
|
def start_diagnostics():
|
||||||
"""Start full diagnostics as a background operation."""
|
"""Start full diagnostics as a background operation."""
|
||||||
logger.info('Running full diagnostics')
|
logger.info('Running full diagnostics')
|
||||||
try:
|
try:
|
||||||
@ -275,6 +276,7 @@ def _run_diagnostics():
|
|||||||
from plinth.notification import Notification
|
from plinth.notification import Notification
|
||||||
|
|
||||||
_run_on_all_enabled_modules()
|
_run_on_all_enabled_modules()
|
||||||
|
apps_with_issues = set()
|
||||||
with results_lock:
|
with results_lock:
|
||||||
results = current_results['results']
|
results = current_results['results']
|
||||||
# Store the most recent results in the database.
|
# Store the most recent results in the database.
|
||||||
@ -283,13 +285,14 @@ def _run_diagnostics():
|
|||||||
|
|
||||||
issue_count = 0
|
issue_count = 0
|
||||||
severity = 'warning'
|
severity = 'warning'
|
||||||
for _app_id, app_data in results.items():
|
for app_id, app_data in results.items():
|
||||||
if app_data['exception']:
|
if app_data['exception']:
|
||||||
issue_count += 1
|
issue_count += 1
|
||||||
severity = 'error'
|
severity = 'error'
|
||||||
else:
|
else:
|
||||||
for check in app_data['diagnosis']:
|
for check in app_data['diagnosis']:
|
||||||
if check.result != Result.PASSED:
|
if check.result != Result.PASSED:
|
||||||
|
apps_with_issues.add(app_id)
|
||||||
issue_count += 1
|
issue_count += 1
|
||||||
if check.result != Result.WARNING:
|
if check.result != Result.WARNING:
|
||||||
severity = 'error'
|
severity = 'error'
|
||||||
@ -323,6 +326,14 @@ def _run_diagnostics():
|
|||||||
data=data, group='admin')
|
data=data, group='admin')
|
||||||
note.dismiss(False)
|
note.dismiss(False)
|
||||||
|
|
||||||
|
# If enabled, run automatic repair for apps with failed diagnostics.
|
||||||
|
if is_automatic_repair_enabled():
|
||||||
|
logger.info('Starting automatic repair...')
|
||||||
|
for app_id in apps_with_issues:
|
||||||
|
run_repair_on_app(app_id, False)
|
||||||
|
else:
|
||||||
|
logger.info('Skipping automatic repair, disabled.')
|
||||||
|
|
||||||
|
|
||||||
def are_results_available():
|
def are_results_available():
|
||||||
"""Return whether diagnostic results are available."""
|
"""Return whether diagnostic results are available."""
|
||||||
@ -374,3 +385,17 @@ def is_daily_run_enabled() -> bool:
|
|||||||
def set_daily_run_enabled(enabled: bool):
|
def set_daily_run_enabled(enabled: bool):
|
||||||
"""Enable or disable daily run."""
|
"""Enable or disable daily run."""
|
||||||
kvstore.set('diagnostics_daily_run_enabled', enabled)
|
kvstore.set('diagnostics_daily_run_enabled', enabled)
|
||||||
|
|
||||||
|
|
||||||
|
def is_automatic_repair_enabled() -> bool:
|
||||||
|
"""Return whether automatic repair is enabled.
|
||||||
|
|
||||||
|
In case it is not set, assume it is not enabled. This default could be
|
||||||
|
changed later.
|
||||||
|
"""
|
||||||
|
return kvstore.get_default('diagnostics_automatic_repair_enabled', False)
|
||||||
|
|
||||||
|
|
||||||
|
def set_automatic_repair_enabled(enabled: bool):
|
||||||
|
"""Enable or disable automatic repair."""
|
||||||
|
kvstore.set('diagnostics_automatic_repair_enabled', enabled)
|
||||||
|
|||||||
@ -10,3 +10,7 @@ class ConfigureForm(forms.Form):
|
|||||||
daily_run_enabled = forms.BooleanField(
|
daily_run_enabled = forms.BooleanField(
|
||||||
label=_('Enable daily run'), required=False,
|
label=_('Enable daily run'), required=False,
|
||||||
help_text=_('When enabled, diagnostic checks will run once a day.'))
|
help_text=_('When enabled, diagnostic checks will run once a day.'))
|
||||||
|
|
||||||
|
automatic_repair = forms.BooleanField(
|
||||||
|
label=_('Enable automatic repair'), required=False,
|
||||||
|
help_text=_('If issues are found, try to repair them automatically.'))
|
||||||
|
|||||||
@ -44,14 +44,25 @@ class DiagnosticsView(AppView):
|
|||||||
"""Return the initial values for the form."""
|
"""Return the initial values for the form."""
|
||||||
status = super().get_initial()
|
status = super().get_initial()
|
||||||
status['daily_run_enabled'] = diagnostics.is_daily_run_enabled()
|
status['daily_run_enabled'] = diagnostics.is_daily_run_enabled()
|
||||||
|
status['automatic_repair'] = diagnostics.is_automatic_repair_enabled()
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Apply the form changes."""
|
"""Apply the form changes."""
|
||||||
old_status = form.initial
|
old_status = form.initial
|
||||||
new_status = form.cleaned_data
|
new_status = form.cleaned_data
|
||||||
|
updated = False
|
||||||
|
|
||||||
if old_status['daily_run_enabled'] != new_status['daily_run_enabled']:
|
if old_status['daily_run_enabled'] != new_status['daily_run_enabled']:
|
||||||
diagnostics.set_daily_run_enabled(new_status['daily_run_enabled'])
|
diagnostics.set_daily_run_enabled(new_status['daily_run_enabled'])
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
if old_status['automatic_repair'] != new_status['automatic_repair']:
|
||||||
|
diagnostics.set_automatic_repair_enabled(
|
||||||
|
new_status['automatic_repair'])
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
if updated:
|
||||||
messages.success(self.request, _('Configuration updated.'))
|
messages.success(self.request, _('Configuration updated.'))
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|||||||
@ -40,7 +40,8 @@ def install(app_id: str, packages: list[str], skip_recommends: bool = False,
|
|||||||
try:
|
try:
|
||||||
_assert_managed_packages(app_id, packages)
|
_assert_managed_packages(app_id, packages)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise PermissionError(f'Packages are not managed: {packages}')
|
raise PermissionError(
|
||||||
|
f'Packages are not managed by {app_id}: {packages}')
|
||||||
|
|
||||||
extra_arguments = []
|
extra_arguments = []
|
||||||
if skip_recommends:
|
if skip_recommends:
|
||||||
|
|||||||
@ -97,20 +97,36 @@ def _run_setup_on_app(app, current_version, repair: bool = False):
|
|||||||
operation.on_update(message, exception_to_update)
|
operation.on_update(message, exception_to_update)
|
||||||
|
|
||||||
|
|
||||||
def run_repair_on_app(app_id):
|
def run_repair_on_app(app_id, create_operation=True):
|
||||||
"""Execute the repair process in a thread."""
|
"""Execute the repair process in a thread.
|
||||||
|
|
||||||
|
In case this is called from within another operation, creating a new
|
||||||
|
operation can be skipped.
|
||||||
|
"""
|
||||||
app = app_module.App.get(app_id)
|
app = app_module.App.get(app_id)
|
||||||
current_version = app.get_setup_version()
|
current_version = app.get_setup_version()
|
||||||
if not current_version:
|
if not current_version:
|
||||||
logger.warning('App %s is not installed, cannot repair', app_id)
|
logger.warning('App %s is not installed, cannot repair', app_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug('Creating operation to repair app: %s', app_id)
|
if create_operation:
|
||||||
return operation_module.manager.new(f'{app_id}-repair', app_id,
|
logger.debug('Creating operation to repair app: %s', app_id)
|
||||||
gettext_noop('Repairing app'),
|
return operation_module.manager.new(f'{app_id}-repair', app_id,
|
||||||
_run_repair_on_app, [app],
|
gettext_noop('Repairing app'),
|
||||||
show_message=True,
|
_run_repair_on_app, [app],
|
||||||
show_notification=True)
|
show_message=True,
|
||||||
|
show_notification=True)
|
||||||
|
|
||||||
|
# Re-use existing operation.
|
||||||
|
try:
|
||||||
|
operation = operation_module.Operation.get_operation()
|
||||||
|
except AttributeError:
|
||||||
|
raise RuntimeError(
|
||||||
|
'run_repair_on_app: Expected an existing operation.')
|
||||||
|
|
||||||
|
# XXX: Ugly hack to re-use operation from another app.
|
||||||
|
operation.app_id = app_id
|
||||||
|
_run_repair_on_app(app)
|
||||||
|
|
||||||
|
|
||||||
def _run_repair_on_app(app: app_module.App):
|
def _run_repair_on_app(app: app_module.App):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user