diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py index ab16e2f95..82931f1c8 100644 --- a/plinth/modules/avahi/__init__.py +++ b/plinth/modules/avahi/__init__.py @@ -9,13 +9,14 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.config import get_hostname from plinth.modules.firewall.components import Firewall from plinth.modules.names.components import DomainType from plinth.signals import domain_added, domain_removed, post_hostname_change from plinth.utils import format_lazy -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest # pylint: disable=C0103 @@ -76,6 +77,10 @@ class AvahiApp(app_module.App): daemon = Daemon('daemon-avahi', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-avahi', + **manifest.backup) + self.add(backup_restore) + if self.is_enabled(): domain_added.send_robust(sender='avahi', domain_type='domain-type-local', diff --git a/plinth/modules/avahi/manifest.py b/plinth/modules/avahi/manifest.py index 448340c67..1a87951f7 100644 --- a/plinth/modules/avahi/manifest.py +++ b/plinth/modules/avahi/manifest.py @@ -3,10 +3,8 @@ Application manifest for avahi. """ -from plinth.modules.backups.api import validate as validate_backup - # Services that intend to make themselves discoverable will drop files into # /etc/avahi/services. Currently, we don't intend to make that customizable. # There is no necessity for backup and restore. This manifest will ensure that # avahi enable/disable setting is preserved. -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py index ea2fc511a..8ec31183e 100644 --- a/plinth/modules/backups/__init__.py +++ b/plinth/modules/backups/__init__.py @@ -75,10 +75,10 @@ def _backup_handler(packet, encryption_passphrase=None): get_valid_filename(packet.path) + '.json') manifests = { 'apps': [{ - 'name': app.name, - 'version': app.app.app.info.version, - 'backup': app.manifest - } for app in packet.apps] + 'name': component.app.app_id, + 'version': component.app.info.version, + 'backup': component.manifest + } for component in packet.components] } with open(manifest_path, 'w') as manifest_file: json.dump(manifests, manifest_file) @@ -124,9 +124,9 @@ def restore_archive_handler(packet, encryption_passphrase=None): actions.superuser_run('backups', arguments, input=locations_data.encode()) -def restore_from_upload(path, apps=None): +def restore_from_upload(path, app_ids=None): """Restore files from an uploaded .tar.gz backup file""" - api.restore_apps(_restore_exported_archive_handler, app_names=apps, + api.restore_apps(_restore_exported_archive_handler, app_ids=app_ids, create_subvolume=False, backup_file=path) diff --git a/plinth/modules/backups/api.py b/plinth/modules/backups/api.py index 59046fa15..a82035f0c 100644 --- a/plinth/modules/backups/api.py +++ b/plinth/modules/backups/api.py @@ -10,79 +10,38 @@ TODO: - Implement unit tests. """ +import importlib import logging -from plinth import actions, action_utils, module_loader, setup +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import setup + +from .components import BackupRestore logger = logging.getLogger(__name__) -def validate(backup): - """Validate the backup' information schema.""" - assert isinstance(backup, dict) - - if 'config' in backup: - assert isinstance(backup['config'], dict) - _validate_directories_and_files(backup['config']) - - if 'data' in backup: - assert isinstance(backup['data'], dict) - _validate_directories_and_files(backup['data']) - - if 'secrets' in backup: - assert isinstance(backup['secrets'], dict) - _validate_directories_and_files(backup['secrets']) - - if 'services' in backup: - assert isinstance(backup['services'], list) - for service in backup['services']: - assert isinstance(service, (str, dict)) - if isinstance(service, dict): - _validate_service(service) - - return backup - - -def _validate_directories_and_files(section): - """Validate directories and files keys in a section.""" - if 'directories' in section: - assert isinstance(section['directories'], list) - for directory in section['directories']: - assert isinstance(directory, str) - - if 'files' in section: - assert isinstance(section['files'], list) - for file_path in section['files']: - assert isinstance(file_path, str) - - -def _validate_service(service): - """Validate a service manifest provided as a dictionary.""" - assert isinstance(service['name'], str) - assert isinstance(service['type'], str) - assert service['type'] in ('apache', 'uwsgi', 'system') - if service['type'] == 'apache': - assert service['kind'] in ('config', 'site', 'module') - - class BackupError: """Represent an backup/restore operation error.""" - def __init__(self, error_type, app, hook=None): + + def __init__(self, error_type, component, hook=None): """Initialize the error object.""" self.error_type = error_type - self.app = app + self.component = component self.hook = hook def __eq__(self, other_error): """Compare to error objects.""" return (self.error_type == other_error.error_type - and self.app == other_error.app + and self.component == other_error.component and self.hook == other_error.hook) class Packet: """Information passed to a handlers for backup/restore operations.""" - def __init__(self, operation, scope, root, apps=None, path=None): + + def __init__(self, operation, scope, root, components=None, path=None): """Initialize the packet. operation is either 'backup' or 'restore. @@ -101,7 +60,7 @@ class Packet: self.operation = operation self.scope = scope self.root = root - self.apps = apps + self.components = components self.path = path self.errors = [] @@ -112,11 +71,11 @@ class Packet: def _process_manifests(self): """Look at manifests and fill up the list of directories/files.""" - for app in self.apps: + for component in self.components: for section in ['config', 'data', 'secrets']: - self.directories += app.manifest.get(section, {}).get( - 'directories', []) - self.files += app.manifest.get(section, {}).get('files', []) + section = getattr(component, section) + self.directories += section.get('directories', []) + self.files += section.get('files', []) def backup_full(backup_handler, path=None): @@ -146,25 +105,25 @@ def restore_full(restore_handler): _switch_to_subvolume(subvolume) -def backup_apps(backup_handler, path, app_names=None, +def backup_apps(backup_handler, path, app_ids=None, encryption_passphrase=None): """Backup data belonging to a set of applications.""" - if not app_names: - apps = get_all_apps_for_backup() + if not app_ids: + components = get_all_components_for_backup() else: - apps = get_apps_in_order(app_names) + components = get_components_in_order(app_ids) if _is_snapshot_available(): snapshot = _take_snapshot() backup_root = snapshot['mount_path'] snapshotted = True else: - _lockdown_apps(apps, lockdown=True) - original_state = _shutdown_services(apps) + _lockdown_apps(components, lockdown=True) + original_state = _shutdown_services(components) backup_root = '/' snapshotted = False - packet = Packet('backup', 'apps', backup_root, apps, path) + packet = Packet('backup', 'apps', backup_root, components, path) _run_operation(backup_handler, packet, encryption_passphrase=encryption_passphrase) @@ -172,29 +131,29 @@ def backup_apps(backup_handler, path, app_names=None, _delete_snapshot(snapshot) else: _restore_services(original_state) - _lockdown_apps(apps, lockdown=False) + _lockdown_apps(components, lockdown=False) -def restore_apps(restore_handler, app_names=None, create_subvolume=True, +def restore_apps(restore_handler, app_ids=None, create_subvolume=True, backup_file=None, encryption_passphrase=None): """Restore data belonging to a set of applications.""" - if not app_names: - apps = get_all_apps_for_backup() + if not app_ids: + components = get_all_components_for_backup() else: - apps = get_apps_in_order(app_names) + components = get_components_in_order(app_ids) - _install_apps_before_restore(apps) + _install_apps_before_restore(components) if _is_snapshot_available() and create_subvolume: subvolume = _create_subvolume(empty=False) restore_root = subvolume['mount_path'] else: - _lockdown_apps(apps, lockdown=True) - original_state = _shutdown_services(apps) + _lockdown_apps(components, lockdown=True) + original_state = _shutdown_services(components) restore_root = '/' subvolume = False - packet = Packet('restore', 'apps', restore_root, apps, backup_file) + packet = Packet('restore', 'apps', restore_root, components, backup_file) _run_operation(restore_handler, packet, encryption_passphrase=encryption_passphrase) @@ -202,10 +161,10 @@ def restore_apps(restore_handler, app_names=None, create_subvolume=True, _switch_to_subvolume(subvolume) else: _restore_services(original_state) - _lockdown_apps(apps, lockdown=False) + _lockdown_apps(components, lockdown=False) -def _install_apps_before_restore(apps): +def _install_apps_before_restore(components): """Install/upgrade apps needed before restoring a backup. Upgrading apps to latest version before backups reduces the chance of newer @@ -213,92 +172,57 @@ def _install_apps_before_restore(apps): """ modules_to_setup = [] - for backup_app in apps: - if backup_app.app.setup_helper.get_state() in ('needs-setup', - 'needs-update'): - modules_to_setup.append(backup_app.name) + for component in components: + module = importlib.import_module(component.app.__class__.__module__) + if module.setup_helper.get_state() in ('needs-setup', 'needs-update'): + modules_to_setup.append(component.app.app_id) setup.run_setup_on_modules(modules_to_setup) -class BackupApp: - """A application that can be backed up and its manifest.""" - def __init__(self, name, app): - """Initialize object and load manfiest.""" - self.name = name - self.app = app +def _get_backup_restore_component(app): + """Return the backup/restore component of the app.""" + for component in app.components.values(): + if isinstance(component, BackupRestore): + return component - # Has no backup related meta data + raise TypeError + + +def get_all_components_for_backup(): + """Return a list of all components that can be backed up.""" + components = [] + + for app_ in app_module.App.list(): try: - self.manifest = app.backup - except AttributeError: - raise TypeError - - self.has_data = bool(app.backup) - - def __eq__(self, other_app): - """Check if this app is same as another.""" - return self.name == other_app.name and \ - self.app == other_app.app and \ - self.manifest == other_app.manifest and \ - self.has_data == other_app.has_data - - def is_installed(self): - """Return whether app is installed. - - Return true even if the app needs update. - """ - return self.app.setup_helper.get_state() != 'needs-setup' - - def run_hook(self, hook, packet): - """Run a hook inside an application.""" - if not hasattr(self.app, hook): - return - - try: - getattr(self.app, hook)(packet) - except Exception as exception: - logger.exception( - 'Error running backup/restore hook for app %s: %s', self.name, - exception) - packet.errors.append(BackupError('hook', self.app, hook=hook)) - - -def get_all_apps_for_backup(): - """Return a list of all applications that can be backed up.""" - apps = [] - for module_name, module in module_loader.loaded_modules.items(): - try: - backup_app = BackupApp(module_name, module) - if backup_app.is_installed(): - apps.append(backup_app) + module = importlib.import_module(app_.__class__.__module__) + if module.setup_helper.get_state() != 'needs-setup': + components.append(_get_backup_restore_component(app_)) except TypeError: # Application not available for backup/restore pass - return apps + return components -def get_apps_in_order(app_names): - """Return a list of app modules in order of dependency.""" - apps = [] - for module_name, module in module_loader.loaded_modules.items(): - if module_name in app_names: - apps.append(BackupApp(module_name, module)) +def get_components_in_order(app_ids): + """Return a list of backup components in order of app dependencies.""" + components = [] + for app_ in app_module.App.list(): + if app_.app_id in app_ids: + components.append(_get_backup_restore_component(app_)) - return apps + return components -def _lockdown_apps(apps, lockdown): +def _lockdown_apps(components, lockdown): """Mark apps as in/out of lockdown mode and disable all user interaction. This is a flag in the app module. It will enforced by a middleware that will intercept all interaction and show a lockdown message. """ - for app in apps: - app.app.locked = lockdown - - # XXX: Lockdown the application UI by implementing a middleware + for component in components: + component.app.locked = lockdown def _is_snapshot_available(): @@ -347,6 +271,7 @@ def _switch_to_subvolume(subvolume): class ServiceHandler: """Abstraction to help with service shutdown/restart.""" + @staticmethod def create(backup_app, service): service_type = 'system' @@ -381,6 +306,7 @@ class ServiceHandler: class SystemServiceHandler(ServiceHandler): """Handle starting and stopping of system services for backup.""" + def __init__(self, backup_app, service): """Initialize the object.""" super().__init__(backup_app, service) @@ -400,6 +326,7 @@ class SystemServiceHandler(ServiceHandler): class ApacheServiceHandler(ServiceHandler): """Handle starting and stopping of Apache services for backup.""" + def __init__(self, backup_app, service): """Initialize the object.""" super().__init__(backup_app, service) @@ -424,18 +351,19 @@ class ApacheServiceHandler(ServiceHandler): ['enable', '--name', self.web_name, '--kind', self.kind]) -def _shutdown_services(apps): - """Shutdown all services specified by manifests. +def _shutdown_services(components): + """Shutdown all services specified by backup manifests. - - Services are shutdown in the reverse order of the apps listing. + - Services are shutdown in the reverse order of the components listing. Return the current state of the services so they can be restored accurately. + """ state = [] - for app in apps: - for service in app.manifest.get('services', []): - state.append(ServiceHandler.create(app, service)) + for component in components: + for service in component.services: + state.append(ServiceHandler.create(component, service)) for service in reversed(state): service.stop() @@ -480,8 +408,14 @@ def _run_hooks(hook, packet): """ logger.info('Running %s hooks', hook) - for app in packet.apps: - app.run_hook(hook, packet) + for component in packet.components: + try: + getattr(component, hook)(packet) + except Exception as exception: + logger.exception( + 'Error running backup/restore hook for app %s: %s', + component.app.app_id, exception) + packet.errors.append(BackupError('hook', component, hook=hook)) def _run_operation(handler, packet, encryption_passphrase=None): diff --git a/plinth/modules/backups/forms.py b/plinth/modules/backups/forms.py index 08e813097..39388a0b4 100644 --- a/plinth/modules/backups/forms.py +++ b/plinth/modules/backups/forms.py @@ -24,16 +24,16 @@ from .repository import get_repositories logger = logging.getLogger(__name__) -def _get_app_choices(apps): - """Return a list of check box multiple choices from list of apps.""" +def _get_app_choices(components): + """Return a list of check box multiple choices from list of components.""" choices = [] - for app in apps: - name = app.app.app.info.name - if not app.has_data: + for component in components: + name = component.app.info.name + if not component.has_data: name = ugettext('{app} (No data to backup)').format( - app=app.app.app.info.name) + app=component.app.info.name) - choices.append((app.name, name)) + choices.append((component.app_id, name)) return choices @@ -59,9 +59,12 @@ class CreateArchiveForm(forms.Form): def __init__(self, *args, **kwargs): """Initialize the form with selectable apps.""" super().__init__(*args, **kwargs) - apps = api.get_all_apps_for_backup() - self.fields['selected_apps'].choices = _get_app_choices(apps) - self.fields['selected_apps'].initial = [app.name for app in apps] + components = api.get_all_components_for_backup() + choices = _get_app_choices(components) + self.fields['selected_apps'].choices = choices + self.fields['selected_apps'].initial = [ + choice[0] for choice in choices + ] self.fields['repository'].choices = _get_repository_choices() @@ -72,10 +75,13 @@ class RestoreForm(forms.Form): def __init__(self, *args, **kwargs): """Initialize the form with selectable apps.""" - apps = kwargs.pop('apps') + components = kwargs.pop('components') super().__init__(*args, **kwargs) - self.fields['selected_apps'].choices = _get_app_choices(apps) - self.fields['selected_apps'].initial = [app.name for app in apps] + choices = _get_app_choices(components) + self.fields['selected_apps'].choices = choices + self.fields['selected_apps'].initial = [ + choice[0] for choice in choices + ] class UploadForm(forms.Form): diff --git a/plinth/modules/backups/manifest.py b/plinth/modules/backups/manifest.py index 1c7cc31eb..f78adbd55 100644 --- a/plinth/modules/backups/manifest.py +++ b/plinth/modules/backups/manifest.py @@ -3,9 +3,7 @@ Application manifest for backups. """ -from plinth.modules.backups.api import validate as validate_backup - # Currently, backup application does not have any settings. However, settings # such as scheduler settings, backup location, secrets to connect to remove # servers need to be backed up. -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/backups/repository.py b/plinth/modules/backups/repository.py index 94aafe0e1..7e9cc7ea2 100644 --- a/plinth/modules/backups/repository.py +++ b/plinth/modules/backups/repository.py @@ -158,6 +158,7 @@ class BaseBorgRepository(abc.ABC): def remove(self): """Remove a borg repository""" + def list_archives(self): """Return list of archives in this repository.""" output = self.run(['list-repo', '--path', self.borg_path]) @@ -165,12 +166,12 @@ class BaseBorgRepository(abc.ABC): return sorted(archives, key=lambda archive: archive['start'], reverse=True) - def create_archive(self, archive_name, app_names): + def create_archive(self, archive_name, app_ids): """Create a new archive in this repository with given name.""" archive_path = self._get_archive_path(archive_name) passphrase = self.credentials.get('encryption_passphrase', None) - api.backup_apps(_backup_handler, path=archive_path, - app_names=app_names, encryption_passphrase=passphrase) + api.backup_apps(_backup_handler, path=archive_path, app_ids=app_ids, + encryption_passphrase=passphrase) def delete_archive(self, archive_name): """Delete an archive with given name from this repository.""" @@ -222,6 +223,7 @@ class BaseBorgRepository(abc.ABC): def get_download_stream(self, archive_name): """Return an stream of .tar.gz binary data for a backup archive.""" + class BufferedReader(io.BufferedReader): """Improve performance of buffered binary streaming. @@ -235,6 +237,7 @@ class BaseBorgRepository(abc.ABC): binary data. """ + def __next__(self): """Override to call read() instead of readline().""" chunk = self.read(io.DEFAULT_BUFFER_SIZE) @@ -279,11 +282,11 @@ class BaseBorgRepository(abc.ABC): output = self.run(['get-archive-apps', '--path', archive_path]) return output.splitlines() - def restore_archive(self, archive_name, apps=None): + def restore_archive(self, archive_name, app_ids=None): """Restore an archive from this repository to the system.""" archive_path = self._get_archive_path(archive_name) passphrase = self.credentials.get('encryption_passphrase', None) - api.restore_apps(restore_archive_handler, app_names=apps, + api.restore_apps(restore_archive_handler, app_ids=app_ids, create_subvolume=False, backup_file=archive_path, encryption_passphrase=passphrase) diff --git a/plinth/modules/backups/tests/test_api.py b/plinth/modules/backups/tests/test_api.py index 9d11e38ea..8cf80b3ce 100644 --- a/plinth/modules/backups/tests/test_api.py +++ b/plinth/modules/backups/tests/test_api.py @@ -8,13 +8,18 @@ from unittest.mock import MagicMock, call, patch import pytest from django.core.files.uploadedfile import SimpleUploadedFile +from plinth.app import App + from .. import api, forms, repository +from ..components import BackupRestore # pylint: disable=protected-access +setup_helper = MagicMock() + def _get_test_manifest(name): - return api.validate({ + return { 'config': { 'directories': ['/etc/' + name + '/config.d/'], 'files': ['/etc/' + name + '/config'], @@ -32,50 +37,42 @@ def _get_test_manifest(name): 'name': name, 'kind': 'site' }] - }) + } -def _get_backup_app(name): - """Return a dummy BackupApp object.""" - return api.BackupApp(name, MagicMock(backup=_get_test_manifest(name))) +def _get_backup_component(name): + """Return a BackupRestore component.""" + return BackupRestore(name, **_get_test_manifest(name)) -class TestBackupApp: - """Test the BackupApp class.""" - @staticmethod - def test_run_hook(): - """Test running a hook on an application.""" - packet = api.Packet('backup', 'apps', '/', []) - hook = 'testhook_pre' - app = MagicMock() - backup_app = api.BackupApp('app_name', app) - backup_app.run_hook(hook, packet) +class AppTest(App): + """Sample App for testing.""" + app_id = 'test-app' - app.testhook_pre.assert_has_calls([call(packet)]) - assert not packet.errors - app.testhook_pre.reset_mock() - app.testhook_pre.side_effect = Exception() - backup_app.run_hook(hook, packet) - assert packet.errors == [api.BackupError('hook', app, hook=hook)] - - del app.testhook_pre - backup_app.run_hook(hook, packet) +def _get_test_app(name): + """Return an App.""" + app = AppTest() + app.app_id = name + app._all_apps[name] = app + app.add(_get_backup_component(name + '-component')) + return app @pytest.mark.usefixtures('load_cfg') class TestBackupProcesses: """Test cases for backup processes""" + @staticmethod - def test_packet_process_manifests(): + def test_packet_collected_files_directories(): """Test that directories/files are collected from manifests.""" - apps = [_get_backup_app('a'), _get_backup_app('b')] - packet = api.Packet('backup', 'apps', '/', apps) - for app in apps: + components = [_get_backup_component('a'), _get_backup_component('b')] + packet = api.Packet('backup', 'apps', '/', components) + for component in components: for section in ['config', 'data', 'secrets']: - for directory in app.manifest[section]['directories']: + for directory in getattr(component, section)['directories']: assert directory in packet.directories - for file_path in app.manifest[section]['files']: + for file_path in getattr(component, section)['files']: assert file_path in packet.files @staticmethod @@ -97,50 +94,56 @@ class TestBackupProcesses: restore_handler.assert_called_once() @staticmethod - @patch('plinth.module_loader.loaded_modules.items') - def test_get_all_apps_for_backup(modules): - """Test listing apps supporting backup and needing backup.""" - apps = [ - ('a', MagicMock(backup=_get_test_manifest('a'))), - ('b', MagicMock(backup=_get_test_manifest('b'))), - ('c', MagicMock(backup=None)), - ('d', MagicMock()), - ] - del apps[3][1].backup - modules.return_value = apps + @patch('importlib.import_module') + @patch('plinth.app.App.list') + def test_get_all_components_for_backup(apps_list, import_module): + """Test listing components supporting backup and needing backup.""" + modules = [MagicMock(), MagicMock(), MagicMock()] + import_module.side_effect = modules + apps = [_get_test_app('a'), _get_test_app('b'), _get_test_app('c')] + modules[1].setup_helper.get_state.side_effect = ['needs-setup'] + apps_list.return_value = apps - returned_apps = api.get_all_apps_for_backup() - expected_apps = [ - api.BackupApp('a', apps[0][1]), - api.BackupApp('b', apps[1][1]), - api.BackupApp('c', apps[2][1]) + returned_components = api.get_all_components_for_backup() + expected_components = [ + apps[0].components['a-component'], + apps[2].components['c-component'] ] - assert returned_apps == expected_apps + assert returned_components == expected_components @staticmethod - @patch('plinth.module_loader.loaded_modules.items') - def test_get_apps_in_order(modules): - """Test that apps are listed in correct dependency order.""" + @patch('plinth.app.App.list') + def test_get_components_in_order(apps_list): + """Test that components are listed in correct dependency order.""" apps = [ - ('names', MagicMock(backup=_get_test_manifest('names'))), - ('config', MagicMock(backup=_get_test_manifest('config'))), + _get_test_app('names'), + _get_test_app('other'), + _get_test_app('config') ] - modules.return_value = apps + apps_list.return_value = apps - app_names = ['config', 'names'] - apps = api.get_apps_in_order(app_names) - assert apps[0].name == 'names' - assert apps[1].name == 'config' + app_ids = ['config', 'names'] + components = api.get_components_in_order(app_ids) + assert len(components) == 2 + assert components[0].app_id == 'names' + assert components[1].app_id == 'config' @staticmethod def test__lockdown_apps(): """Test that locked flag is set for each app.""" - app_a = MagicMock(locked=False) - app_b = MagicMock(locked=None) - apps = [MagicMock(app=app_a), MagicMock(app=app_b)] - api._lockdown_apps(apps, True) - assert app_a.locked is True - assert app_b.locked is True + apps = [_get_test_app('test-app-1'), _get_test_app('test-app-2')] + components = [ + apps[0].components['test-app-1-component'], + apps[1].components['test-app-2-component'] + ] + + api._lockdown_apps(components, True) + assert apps[0].locked + assert apps[1].locked + + api._lockdown_apps(components, False) + assert not apps[0].locked + assert not apps[1].locked @staticmethod @patch('plinth.action_utils.webserver_is_enabled') @@ -148,19 +151,20 @@ class TestBackupProcesses: @patch('plinth.actions.superuser_run') def test__shutdown_services(run, service_is_running, webserver_is_enabled): """Test that services are stopped in correct order.""" - apps = [_get_backup_app('a'), _get_backup_app('b')] + components = [_get_backup_component('a'), _get_backup_component('b')] service_is_running.return_value = True webserver_is_enabled.return_value = True - state = api._shutdown_services(apps) + state = api._shutdown_services(components) expected_state = [ - api.ServiceHandler.create(apps[0], - apps[0].manifest['services'][0]), - api.ServiceHandler.create(apps[0], - apps[0].manifest['services'][1]), - api.ServiceHandler.create(apps[1], - apps[1].manifest['services'][0]), - api.ServiceHandler.create(apps[1], apps[1].manifest['services'][1]) + api.ServiceHandler.create(components[0], + components[0].services[0]), + api.ServiceHandler.create(components[0], + components[0].services[1]), + api.ServiceHandler.create(components[1], + components[1].services[0]), + api.ServiceHandler.create(components[1], + components[1].services[1]), ] assert state == expected_state @@ -207,21 +211,26 @@ class TestBackupProcesses: @staticmethod def test__run_operation(): """Test that operation runs handler and app hooks.""" - apps = [_get_backup_app('a'), _get_backup_app('b')] - packet = api.Packet('backup', 'apps', '/', apps) - packet.apps[0].run_hook = MagicMock() - packet.apps[1].run_hook = MagicMock() + components = [_get_backup_component('a'), _get_backup_component('b')] + packet = api.Packet('backup', 'apps', '/', components) + packet.components[0].backup_pre = MagicMock() + packet.components[0].backup_post = MagicMock() + packet.components[1].backup_pre = MagicMock() + packet.components[1].backup_post = MagicMock() handler = MagicMock() api._run_operation(handler, packet) handler.assert_has_calls([call(packet, encryption_passphrase=None)]) - calls = [call('backup_pre', packet), call('backup_post', packet)] - packet.apps[0].run_hook.assert_has_calls(calls) - packet.apps[1].run_hook.assert_has_calls(calls) + calls = [call(packet)] + packet.components[0].backup_pre.assert_has_calls(calls) + packet.components[0].backup_post.assert_has_calls(calls) + packet.components[1].backup_pre.assert_has_calls(calls) + packet.components[1].backup_post.assert_has_calls(calls) class TestBackupModule: """Tests of the backups django module, like views or forms.""" + @staticmethod def test_file_upload(): # posting a video should fail diff --git a/plinth/modules/backups/views.py b/plinth/modules/backups/views.py index 7e289449b..355eb4528 100644 --- a/plinth/modules/backups/views.py +++ b/plinth/modules/backups/views.py @@ -147,7 +147,7 @@ class BaseRestoreView(SuccessMessageMixin, FormView): """Pass additional keyword args for instantiating the form.""" kwargs = super().get_form_kwargs() included_apps = self._get_included_apps() - kwargs['apps'] = api.get_apps_in_order(included_apps) + kwargs['components'] = api.get_components_in_order(included_apps) return kwargs def get_context_data(self, **kwargs): diff --git a/plinth/modules/bepasty/__init__.py b/plinth/modules/bepasty/__init__.py index bf888deda..708587d50 100644 --- a/plinth/modules/bepasty/__init__.py +++ b/plinth/modules/bepasty/__init__.py @@ -11,9 +11,10 @@ from plinth import actions from plinth import app as app_module from plinth import frontpage, menu from plinth.modules.apache.components import Uwsgi, Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -64,7 +65,7 @@ class BepastyApp(app_module.App): icon_filename='bepasty', short_description=_('File & Snippet Sharing'), description=_description, manual_page='bepasty', - clients=clients) + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-bepasty', info.name, @@ -75,7 +76,7 @@ class BepastyApp(app_module.App): shortcut = frontpage.Shortcut('shortcut-bepasty', info.name, info.short_description, info.icon_filename, '/bepasty', - clients=clients) + clients=manifest.clients) self.add(shortcut) firewall = Firewall('firewall-bepasty', info.name, @@ -89,6 +90,10 @@ class BepastyApp(app_module.App): urls=['https://{host}/bepasty/']) self.add(webserver) + backup_restore = BackupRestore('backup-restore-bepasty', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/bepasty/manifest.py b/plinth/modules/bepasty/manifest.py index 1f267e1c6..f61f96a81 100644 --- a/plinth/modules/bepasty/manifest.py +++ b/plinth/modules/bepasty/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('bepasty'), @@ -13,7 +12,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'config': { 'files': ['/etc/bepasty-freedombox.conf'] }, @@ -21,4 +20,4 @@ backup = validate_backup({ 'directories': ['/var/lib/bepasty'] }, 'services': ['uwsgi'], -}) +} diff --git a/plinth/modules/bind/__init__.py b/plinth/modules/bind/__init__.py index bf5a8f097..981140158 100644 --- a/plinth/modules/bind/__init__.py +++ b/plinth/modules/bind/__init__.py @@ -14,10 +14,11 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.utils import format_lazy -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -96,6 +97,10 @@ class BindApp(app_module.App): alias=managed_services[1]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-bind', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/bind/manifest.py b/plinth/modules/bind/manifest.py index 546eb044b..616e6f641 100644 --- a/plinth/modules/bind/manifest.py +++ b/plinth/modules/bind/manifest.py @@ -3,11 +3,9 @@ Application manifest for bind. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'config': { 'files': ['/etc/bind/named.conf.options'] }, 'services': ['bind9'] -}) +} diff --git a/plinth/modules/calibre/__init__.py b/plinth/modules/calibre/__init__.py index 70b6d7136..b770531d5 100644 --- a/plinth/modules/calibre/__init__.py +++ b/plinth/modules/calibre/__init__.py @@ -13,11 +13,12 @@ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -60,7 +61,7 @@ class CalibreApp(app_module.App): name=_('calibre'), icon_filename='calibre', short_description=_('E-book Library'), description=_description, manual_page='Calibre', - clients=clients, + clients=manifest.clients, donation_url='https://calibre-ebook.com/donate') self.add(info) @@ -94,6 +95,10 @@ class CalibreApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-calibre', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/calibre/manifest.py b/plinth/modules/calibre/manifest.py index 2b6ce7317..dde545875 100644 --- a/plinth/modules/calibre/manifest.py +++ b/plinth/modules/calibre/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('calibre'), @@ -13,9 +12,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'data': { 'directories': ['/var/lib/private/calibre-server-freedombox/'] }, 'services': ['calibre-server-freedombox'] -}) +} diff --git a/plinth/modules/cockpit/__init__.py b/plinth/modules/cockpit/__init__.py index d470c5aa1..82edd004e 100644 --- a/plinth/modules/cockpit/__init__.py +++ b/plinth/modules/cockpit/__init__.py @@ -12,12 +12,12 @@ from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules import names from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.signals import domain_added, domain_removed from plinth.utils import format_lazy -from . import utils -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest, utils version = 1 @@ -65,7 +65,7 @@ class CockpitApp(app_module.App): icon='fa-wrench', icon_filename='cockpit', short_description=_('Server Administration'), description=_description, manual_page='Cockpit', - clients=clients) + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-cockpit', info.name, @@ -92,6 +92,10 @@ class CockpitApp(app_module.App): daemon = Daemon('daemon-cockpit', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-cockpit', + **manifest.backup) + self.add(backup_restore) + domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) diff --git a/plinth/modules/cockpit/manifest.py b/plinth/modules/cockpit/manifest.py index 1a15da82b..a0d873945 100644 --- a/plinth/modules/cockpit/manifest.py +++ b/plinth/modules/cockpit/manifest.py @@ -5,7 +5,6 @@ Application manifest for cockpit. from django.utils.translation import ugettext_lazy as _ -from plinth.modules.backups.api import validate as validate_backup from plinth.clients import validate clients = validate([{ @@ -20,4 +19,4 @@ clients = validate([{ # triggered on every Plinth domain change (and cockpit application install) and # will set the value of allowed domains correctly. This is the only key the is # customized in cockpit.conf. -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/coturn/__init__.py b/plinth/modules/coturn/__init__.py index 5e74ee7e0..73a180242 100644 --- a/plinth/modules/coturn/__init__.py +++ b/plinth/modules/coturn/__init__.py @@ -13,11 +13,12 @@ from plinth import app as app_module from plinth import menu from plinth.daemon import Daemon from plinth.modules import names +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.modules.users.components import UsersAndGroups -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -86,6 +87,10 @@ class CoturnApp(app_module.App): reserved_usernames=['turnserver']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-coturn', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/coturn/manifest.py b/plinth/modules/coturn/manifest.py index fc0748a4b..0e7047930 100644 --- a/plinth/modules/coturn/manifest.py +++ b/plinth/modules/coturn/manifest.py @@ -1,10 +1,3 @@ # SPDX-License-Identifier: AGPL-3.0-or-later -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ - 'secrets': { - 'directories': ['/etc/coturn'] - }, - 'services': ['coturn'] -}) +backup = {'secrets': {'directories': ['/etc/coturn']}, 'services': ['coturn']} diff --git a/plinth/modules/datetime/__init__.py b/plinth/modules/datetime/__init__.py index a45e410a2..197c626be 100644 --- a/plinth/modules/datetime/__init__.py +++ b/plinth/modules/datetime/__init__.py @@ -10,8 +10,9 @@ from django.utils.translation import ugettext_lazy as _ from plinth import app as app_module from plinth import menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -79,6 +80,10 @@ class DateTimeApp(app_module.App): daemon = Daemon('daemon-datetime', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-datetime', + **manifest.backup) + self.add(backup_restore) + def diagnose(self): """Run diagnostics and return the results.""" results = super().diagnose() diff --git a/plinth/modules/datetime/manifest.py b/plinth/modules/datetime/manifest.py index 627f79b76..59f2d45f9 100644 --- a/plinth/modules/datetime/manifest.py +++ b/plinth/modules/datetime/manifest.py @@ -3,6 +3,4 @@ Application manifest for datetime. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({'data': {'files': ['/etc/timezone']}}) +backup = {'data': {'files': ['/etc/timezone']}} diff --git a/plinth/modules/deluge/__init__.py b/plinth/modules/deluge/__init__.py index 71dfa3c28..15bc3962b 100644 --- a/plinth/modules/deluge/__init__.py +++ b/plinth/modules/deluge/__init__.py @@ -10,11 +10,12 @@ from plinth import app as app_module from plinth import frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users import add_user_to_share_group from plinth.modules.users.components import UsersAndGroups -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 6 @@ -50,7 +51,8 @@ class DelugeApp(app_module.App): app_id=self.app_id, version=version, name=_('Deluge'), icon_filename='deluge', short_description=_('BitTorrent Web Client'), - description=_description, manual_page='Deluge', clients=clients, + description=_description, manual_page='Deluge', + clients=manifest.clients, donation_url='https://www.patreon.com/deluge_cas') self.add(info) @@ -88,6 +90,10 @@ class DelugeApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-deluge', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/deluge/manifest.py b/plinth/modules/deluge/manifest.py index 4e8ed3cdb..da06256a4 100644 --- a/plinth/modules/deluge/manifest.py +++ b/plinth/modules/deluge/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('Deluge'), @@ -14,9 +13,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'config': { 'directories': ['/var/lib/deluged/.config'] }, 'services': ['deluged', 'deluge-web'] -}) +} diff --git a/plinth/modules/diagnostics/__init__.py b/plinth/modules/diagnostics/__init__.py index 1a3942897..232c5c6d7 100644 --- a/plinth/modules/diagnostics/__init__.py +++ b/plinth/modules/diagnostics/__init__.py @@ -16,8 +16,9 @@ from django.utils.translation import ugettext_noop from plinth import app as app_module from plinth import cfg, daemon, glib, menu from plinth.modules.apache.components import diagnose_url_on_all +from plinth.modules.backups.components import BackupRestore -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -57,6 +58,10 @@ class DiagnosticsApp(app_module.App): 'diagnostics:index', parent_url_name='system') self.add(menu_item) + backup_restore = BackupRestore('backup-restore-diagnostics', + **manifest.backup) + self.add(backup_restore) + # Check periodically for low RAM space interval = 180 if cfg.develop else 3600 glib.schedule(interval, _warn_about_low_ram_space) diff --git a/plinth/modules/diagnostics/manifest.py b/plinth/modules/diagnostics/manifest.py index 2eee842ca..5b39e5f2d 100644 --- a/plinth/modules/diagnostics/manifest.py +++ b/plinth/modules/diagnostics/manifest.py @@ -3,6 +3,4 @@ Application manifest for diagnostics. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/diaspora/__init__.py b/plinth/modules/diaspora/__init__.py index 2be66b871..aefc903bc 100644 --- a/plinth/modules/diaspora/__init__.py +++ b/plinth/modules/diaspora/__init__.py @@ -52,8 +52,6 @@ _description = [ ' federate with other diaspora* pods.') ] -from .manifest import clients # noqa pylint:disable=E402 isort:skip - app = None @@ -65,10 +63,12 @@ class DiasporaApp(app_module.App): def __init__(self): """Create components for the app.""" super().__init__() + from . import manifest info = app_module.Info(app_id=self.app_id, version=version, name=_('diaspora*'), icon_filename='diaspora', short_description=_('Federated Social Network'), - description=_description, clients=clients) + description=_description, + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-diaspora', info.name, diff --git a/plinth/modules/dynamicdns/__init__.py b/plinth/modules/dynamicdns/__init__.py index 8ca124593..67cc4d82b 100644 --- a/plinth/modules/dynamicdns/__init__.py +++ b/plinth/modules/dynamicdns/__init__.py @@ -8,12 +8,13 @@ from django.utils.translation import ugettext_lazy as _ from plinth import actions from plinth import app as app_module from plinth import cfg, menu +from plinth.modules.backups.components import BackupRestore from plinth.modules.names.components import DomainType from plinth.modules.users.components import UsersAndGroups from plinth.signals import domain_added from plinth.utils import format_lazy -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -70,6 +71,10 @@ class DynamicDNSApp(app_module.App): reserved_usernames=['ez-ipupd']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-dynamicdns', + **manifest.backup) + self.add(backup_restore) + current_status = get_status() if current_status['enabled']: domain_added.send_robust(sender='dynamicdns', diff --git a/plinth/modules/dynamicdns/manifest.py b/plinth/modules/dynamicdns/manifest.py index e9c1734e2..15151580d 100644 --- a/plinth/modules/dynamicdns/manifest.py +++ b/plinth/modules/dynamicdns/manifest.py @@ -1,5 +1,3 @@ # SPDX-License-Identifier: AGPL-3.0-or-later -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({'config': {'directories': ['/etc/ez-ipupdate/']}}) +backup = {'config': {'directories': ['/etc/ez-ipupdate/']}} diff --git a/plinth/modules/ejabberd/__init__.py b/plinth/modules/ejabberd/__init__.py index 18b66174f..99f2f7daf 100644 --- a/plinth/modules/ejabberd/__init__.py +++ b/plinth/modules/ejabberd/__init__.py @@ -16,6 +16,7 @@ from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules import config from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.modules.users.components import UsersAndGroups @@ -23,7 +24,7 @@ from plinth.signals import (domain_added, post_hostname_change, pre_hostname_change) from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 3 @@ -63,7 +64,8 @@ class EjabberdApp(app_module.App): name=_('ejabberd'), icon_filename='ejabberd', short_description=_('Chat Server'), description=_description, - manual_page='ejabberd', clients=clients) + manual_page='ejabberd', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-ejabberd', info.name, @@ -107,6 +109,10 @@ class EjabberdApp(app_module.App): reserved_usernames=['ejabberd']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-ejabberd', + **manifest.backup) + self.add(backup_restore) + pre_hostname_change.connect(on_pre_hostname_change) post_hostname_change.connect(on_post_hostname_change) domain_added.connect(on_domain_added) diff --git a/plinth/modules/ejabberd/manifest.py b/plinth/modules/ejabberd/manifest.py index 7279829bb..eff22e9b7 100644 --- a/plinth/modules/ejabberd/manifest.py +++ b/plinth/modules/ejabberd/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup from plinth.modules.jsxc import manifest as jsxc_manifest _clients = validate([{ @@ -106,7 +105,7 @@ _clients.extend(jsxc_manifest.clients) clients = _clients -backup = validate_backup({ +backup = { 'config': { 'files': ['/etc/ejabberd/ejabberd.yml'] }, @@ -118,4 +117,4 @@ backup = validate_backup({ 'directories': ['/etc/ejabberd/letsencrypt/'] }, 'services': ['ejabberd'] -}) +} diff --git a/plinth/modules/firewall/__init__.py b/plinth/modules/firewall/__init__.py index 178ed409c..0a35c9537 100644 --- a/plinth/modules/firewall/__init__.py +++ b/plinth/modules/firewall/__init__.py @@ -12,9 +12,10 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.utils import Version, format_lazy, import_from_gi -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest gio = import_from_gi('Gio', '2.0') glib = import_from_gi('GLib', '2.0') @@ -74,6 +75,10 @@ class FirewallApp(app_module.App): daemon = Daemon('daemon-firewall', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-firewall', + **manifest.backup) + self.add(backup_restore) + def _run_setup(): """Run firewalld setup.""" diff --git a/plinth/modules/firewall/manifest.py b/plinth/modules/firewall/manifest.py index 7a8d88136..f8bd7bbfc 100644 --- a/plinth/modules/firewall/manifest.py +++ b/plinth/modules/firewall/manifest.py @@ -3,6 +3,4 @@ Application manifest for firewall. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/gitweb/__init__.py b/plinth/modules/gitweb/__init__.py index 4254ab9d7..702bf8f76 100644 --- a/plinth/modules/gitweb/__init__.py +++ b/plinth/modules/gitweb/__init__.py @@ -13,12 +13,13 @@ from plinth import app as app_module from plinth import frontpage, menu from plinth.errors import ActionError from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups +from . import manifest from .forms import is_repo_url -from .manifest import ( # noqa, pylint: disable=unused-import - GIT_REPO_PATH, backup, clients) +from .manifest import GIT_REPO_PATH version = 1 @@ -56,7 +57,7 @@ class GitwebApp(app_module.App): name=_('Gitweb'), icon_filename='gitweb', short_description=_('Simple Git Hosting'), description=_description, manual_page='GitWeb', - clients=clients) + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-gitweb', info.name, info.short_description, @@ -88,6 +89,10 @@ class GitwebApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = GitwebBackupRestore('backup-restore-gitweb', + **manifest.backup) + self.add(backup_restore) + setup_helper = globals()['setup_helper'] if setup_helper.get_state() != 'needs-setup': self.update_service_access() @@ -140,6 +145,14 @@ class GitwebWebserverAuth(Webserver): super().enable() +class GitwebBackupRestore(BackupRestore): + """Component to handle backup/restore for Gitweb.""" + + def restore_post(self, packet): + """Update access after restoration of backups.""" + app.update_service_access() + + def setup(helper, old_version=None): """Install and configure the module.""" helper.install(managed_packages) @@ -147,11 +160,6 @@ def setup(helper, old_version=None): helper.call('post', app.enable) -def restore_post(packet): - """Update access after restoration of backups.""" - app.update_service_access() - - def repo_exists(name): """Check whether a remote repository exists.""" try: diff --git a/plinth/modules/gitweb/manifest.py b/plinth/modules/gitweb/manifest.py index c755363d4..08dc4e6d9 100644 --- a/plinth/modules/gitweb/manifest.py +++ b/plinth/modules/gitweb/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup CONFIG_FILE = '/etc/gitweb-freedombox.conf' GIT_REPO_PATH = '/var/lib/git' @@ -35,11 +34,11 @@ clients = validate([ }, ]) -backup = validate_backup({ +backup = { 'config': { 'files': [CONFIG_FILE] }, 'data': { 'directories': [GIT_REPO_PATH] } -}) +} diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index 3746fa307..146196619 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -10,11 +10,12 @@ from plinth import app as app_module from plinth import frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.i2p.resources import FAVORITES from plinth.modules.users.components import UsersAndGroups -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -58,7 +59,8 @@ class I2PApp(app_module.App): info = app_module.Info( app_id=self.app_id, version=version, name=_('I2P'), icon_filename='i2p', short_description=_('Anonymity Network'), - description=_description, manual_page='I2P', clients=clients, + description=_description, manual_page='I2P', + clients=manifest.clients, donation_url='https://geti2p.net/en/get-involved/donate') self.add(info) @@ -96,6 +98,9 @@ class I2PApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-i2p', **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/i2p/manifest.py b/plinth/modules/i2p/manifest.py index f9577b323..e631bf8af 100644 --- a/plinth/modules/i2p/manifest.py +++ b/plinth/modules/i2p/manifest.py @@ -6,7 +6,6 @@ Application manifest for I2P. from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup _package_id = 'net.geti2p.i2p' _download_url = 'https://geti2p.net/download' @@ -36,9 +35,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'secrets': { 'directories': ['/var/lib/i2p/i2p-config'] }, 'services': ['i2p'] -}) +} diff --git a/plinth/modules/ikiwiki/__init__.py b/plinth/modules/ikiwiki/__init__.py index f0986b428..e2fd6cf4d 100644 --- a/plinth/modules/ikiwiki/__init__.py +++ b/plinth/modules/ikiwiki/__init__.py @@ -10,11 +10,12 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -52,7 +53,7 @@ class IkiwikiApp(app_module.App): name=_('ikiwiki'), icon_filename='ikiwiki', short_description=_('Wiki and Blog'), description=_description, manual_page='Ikiwiki', - clients=clients, + clients=manifest.clients, donation_url='https://ikiwiki.info/tipjar/') self.add(info) @@ -76,6 +77,10 @@ class IkiwikiApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-ikiwiki', + **manifest.backup) + self.add(backup_restore) + def add_shortcut(self, site, title): """Add an ikiwiki shortcut to frontpage.""" shortcut = frontpage.Shortcut('shortcut-ikiwiki-' + site, title, diff --git a/plinth/modules/ikiwiki/manifest.py b/plinth/modules/ikiwiki/manifest.py index 9af176e50..69773651b 100644 --- a/plinth/modules/ikiwiki/manifest.py +++ b/plinth/modules/ikiwiki/manifest.py @@ -2,7 +2,6 @@ from django.utils.translation import ugettext_lazy as _ -from plinth.modules.backups.api import validate as validate_backup from plinth.clients import validate clients = validate([{ @@ -13,7 +12,4 @@ clients = validate([{ }] }]) -backup = validate_backup( - {'data': { - 'directories': ['/var/lib/ikiwiki/', '/var/www/ikiwiki/'] - }}) +backup = {'data': {'directories': ['/var/lib/ikiwiki/', '/var/www/ikiwiki/']}} diff --git a/plinth/modules/infinoted/__init__.py b/plinth/modules/infinoted/__init__.py index 3ded41f35..5cda4cf2e 100644 --- a/plinth/modules/infinoted/__init__.py +++ b/plinth/modules/infinoted/__init__.py @@ -10,10 +10,11 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 3 @@ -45,7 +46,8 @@ class InfinotedApp(app_module.App): name=_('infinoted'), icon_filename='infinoted', short_description=_('Gobby Server'), description=_description, - manual_page='Infinoted', clients=clients) + manual_page='Infinoted', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-infinoted', info.name, @@ -69,6 +71,10 @@ class InfinotedApp(app_module.App): listen_ports=[(6523, 'tcp4'), (6523, 'tcp6')]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-infinoted', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/infinoted/manifest.py b/plinth/modules/infinoted/manifest.py index 38c098148..daf1d4b15 100644 --- a/plinth/modules/infinoted/manifest.py +++ b/plinth/modules/infinoted/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth import cfg -from plinth.modules.backups.api import validate as validate_backup from plinth.clients import validate from plinth.utils import format_lazy @@ -32,7 +31,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'data': { 'directories': ['/var/lib/infinoted/'] }, @@ -43,4 +42,4 @@ backup = validate_backup({ ], }, 'services': ['infinoted'] -}) +} diff --git a/plinth/modules/jsxc/__init__.py b/plinth/modules/jsxc/__init__.py index 80b96a9fb..7f58d1acc 100644 --- a/plinth/modules/jsxc/__init__.py +++ b/plinth/modules/jsxc/__init__.py @@ -10,10 +10,11 @@ from django.utils.translation import ugettext_lazy as _ from plinth import app as app_module from plinth import frontpage, menu +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.web_server import StaticFiles -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -43,7 +44,7 @@ class JSXCApp(app_module.App): name=_('JSXC'), icon_filename='jsxc', short_description=_('Chat Client'), description=_description, manual_page='JSXC', - clients=clients) + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-jsxc', info.name, info.short_description, @@ -72,6 +73,10 @@ class JSXCApp(app_module.App): directory_map=directory_map) self.add(static_files) + backup_restore = BackupRestore('backup-restore-jsxc', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/jsxc/manifest.py b/plinth/modules/jsxc/manifest.py index 6cd065c29..2bb8c46b6 100644 --- a/plinth/modules/jsxc/manifest.py +++ b/plinth/modules/jsxc/manifest.py @@ -4,7 +4,6 @@ from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('JSXC'), @@ -14,4 +13,4 @@ clients = validate([{ }] }]) -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 5f0507339..fc66f3fe8 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -15,12 +15,12 @@ from plinth import cfg, menu from plinth.errors import ActionError from plinth.modules import names from plinth.modules.apache.components import diagnose_url +from plinth.modules.backups.components import BackupRestore from plinth.modules.names.components import DomainType from plinth.signals import domain_added, domain_removed, post_module_loading from plinth.utils import format_lazy -from . import components -from .manifest import backup # noqa, pylint: disable=unused-import +from . import components, manifest version = 3 @@ -74,6 +74,10 @@ class LetsEncryptApp(app_module.App): 'letsencrypt:index', parent_url_name='system') self.add(menu_item) + backup_restore = BackupRestore('backup-restore-letsencrypt', + **manifest.backup) + self.add(backup_restore) + domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) diff --git a/plinth/modules/letsencrypt/manifest.py b/plinth/modules/letsencrypt/manifest.py index 9a340eeec..4055b1717 100644 --- a/plinth/modules/letsencrypt/manifest.py +++ b/plinth/modules/letsencrypt/manifest.py @@ -3,7 +3,5 @@ Application manfiest for letsencrypt. """ -from plinth.modules.backups.api import validate as validate_backup - # XXX: Backup and restore the Apache site configuration. -backup = validate_backup({'secrets': {'directories': ['/etc/letsencrypt/']}}) +backup = {'secrets': {'directories': ['/etc/letsencrypt/']}} diff --git a/plinth/modules/matrixsynapse/__init__.py b/plinth/modules/matrixsynapse/__init__.py index 97cf017a1..3445ee0b6 100644 --- a/plinth/modules/matrixsynapse/__init__.py +++ b/plinth/modules/matrixsynapse/__init__.py @@ -16,10 +16,11 @@ from plinth import app as app_module from plinth import frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 6 @@ -63,12 +64,11 @@ class MatrixSynapseApp(app_module.App): def __init__(self): """Create components for the app.""" super().__init__() - info = app_module.Info(app_id=self.app_id, version=version, - name=_('Matrix Synapse'), - icon_filename='matrixsynapse', - short_description=_('Chat Server'), - description=_description, - manual_page='MatrixSynapse', clients=clients) + info = app_module.Info( + app_id=self.app_id, version=version, name=_('Matrix Synapse'), + icon_filename='matrixsynapse', short_description=_('Chat Server'), + description=_description, manual_page='MatrixSynapse', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-matrixsynapse', info.name, @@ -106,6 +106,10 @@ class MatrixSynapseApp(app_module.App): listen_ports=[(8008, 'tcp4'), (8448, 'tcp4')]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-matrixsynapse', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/matrixsynapse/manifest.py b/plinth/modules/matrixsynapse/manifest.py index 654be70e1..dba665640 100644 --- a/plinth/modules/matrixsynapse/manifest.py +++ b/plinth/modules/matrixsynapse/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup _android_package_id = 'im.vector.app' _element_desktop_download_url = 'https://element.io/get-started' @@ -44,7 +43,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'config': { 'directories': ['/etc/matrix-synapse/conf.d/'], 'files': [ @@ -68,4 +67,4 @@ backup = validate_backup({ ] }, 'services': ['matrix-synapse'] -}) +} diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py index 95925da93..6bbb4e4fa 100644 --- a/plinth/modules/mediawiki/__init__.py +++ b/plinth/modules/mediawiki/__init__.py @@ -13,9 +13,10 @@ from plinth import app as app_module from plinth import frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 9 @@ -58,7 +59,8 @@ class MediaWikiApp(app_module.App): name=_('MediaWiki'), icon_filename='mediawiki', short_description=_('Wiki'), description=_description, - manual_page='MediaWiki', clients=clients) + manual_page='MediaWiki', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-mediawiki', info.name, @@ -87,6 +89,10 @@ class MediaWikiApp(app_module.App): daemon = Daemon('daemon-mediawiki', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-mediawiki', + **manifest.backup) + self.add(backup_restore) + class Shortcut(frontpage.Shortcut): """Frontpage shortcut for only logged users when in private mode.""" diff --git a/plinth/modules/mediawiki/manifest.py b/plinth/modules/mediawiki/manifest.py index 79d3c266d..a457ca908 100644 --- a/plinth/modules/mediawiki/manifest.py +++ b/plinth/modules/mediawiki/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('MediaWiki'), @@ -13,7 +12,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'config': { 'files': ['/etc/mediawiki/FreedomBoxSettings.php'] }, @@ -21,4 +20,4 @@ backup = validate_backup({ 'directories': ['/var/lib/mediawiki-db/'] }, 'services': ['mediawiki-jobrunner'] -}) +} diff --git a/plinth/modules/minetest/__init__.py b/plinth/modules/minetest/__init__.py index bada85c28..eb49512d3 100644 --- a/plinth/modules/minetest/__init__.py +++ b/plinth/modules/minetest/__init__.py @@ -10,11 +10,12 @@ from django.utils.translation import ugettext_lazy as _ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -59,7 +60,8 @@ class MinetestApp(app_module.App): info = app_module.Info( app_id=self.app_id, version=version, name=_('Minetest'), icon_filename='minetest', short_description=_('Block Sandbox'), - description=_description, manual_page='Minetest', clients=clients, + description=_description, manual_page='Minetest', + clients=manifest.clients, donation_url='https://www.minetest.net/get-involved/#donate') self.add(info) @@ -89,6 +91,10 @@ class MinetestApp(app_module.App): reserved_usernames=['Debian-minetest']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-minetest', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/minetest/manifest.py b/plinth/modules/minetest/manifest.py index d73a1a8b8..039bc9478 100644 --- a/plinth/modules/minetest/manifest.py +++ b/plinth/modules/minetest/manifest.py @@ -2,7 +2,6 @@ from django.utils.translation import ugettext_lazy as _ -from plinth.modules.backups.api import validate as validate_backup from plinth.clients import store_url, validate clients = validate([{ @@ -37,7 +36,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'config': { 'files': ['/etc/minetest/minetest.conf'] }, @@ -45,4 +44,4 @@ backup = validate_backup({ 'directories': ['/var/games/minetest-server/'] }, 'services': ['minetest-server'] -}) +} diff --git a/plinth/modules/minidlna/__init__.py b/plinth/modules/minidlna/__init__.py index 5cfb6bcb0..65dff2f09 100644 --- a/plinth/modules/minidlna/__init__.py +++ b/plinth/modules/minidlna/__init__.py @@ -8,10 +8,11 @@ import plinth.app as app_module from plinth import actions, frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups -from .manifest import backup, clients # noqa +from . import manifest version = 2 @@ -47,7 +48,8 @@ class MiniDLNAApp(app_module.App): name=_('MiniDLNA'), icon_filename='minidlna', short_description=_('Simple Media Server'), description=_description, - manual_page='MiniDLNA', clients=clients) + manual_page='MiniDLNA', + clients=manifest.clients) self.add(info) menu_item = menu.Menu( @@ -70,6 +72,10 @@ class MiniDLNAApp(app_module.App): allowed_groups=list(groups)) daemon = Daemon('daemon-minidlna', managed_services[0]) + backup_restore = BackupRestore('backup-restore-minidlna', + **manifest.backup) + self.add(backup_restore) + self.add(menu_item) self.add(webserver) self.add(firewall) diff --git a/plinth/modules/minidlna/manifest.py b/plinth/modules/minidlna/manifest.py index 716b72e99..55c233df3 100644 --- a/plinth/modules/minidlna/manifest.py +++ b/plinth/modules/minidlna/manifest.py @@ -2,124 +2,108 @@ from django.utils.translation import ugettext_lazy as _ -from plinth.modules.backups.api import validate as validate_backup -from plinth.clients import validate, store_url +from plinth.clients import store_url, validate clients = validate([ { - 'name': _('vlc'), - 'platforms': [ - { - 'type': 'package', - 'os': 'gnu-linux', - 'format': 'deb', - 'name': 'vlc', - }, - { - 'type': 'package', - 'os': 'gnu-linux', - 'format': 'rpm', - 'name': 'vlc', - }, - { - 'type': 'download', - 'os': 'windows', - 'url': 'https://www.videolan.org/vlc/download-windows.html', - }, - { - 'type': 'download', - 'os': 'macos', - 'url': 'https://www.videolan.org/vlc/download-macosx.html', - }, - { - 'type': 'store', - 'os': 'android', - 'store_name': 'google-play', - 'url': store_url('google-play', 'org.videolan.vlc') - }, - { - 'type': 'store', - 'os': 'android', - 'store_name': 'f-droid', - 'url': store_url('f-droid', 'org.videolan.vlc') - }, - ] + 'name': + _('vlc'), + 'platforms': [{ + 'type': 'package', + 'os': 'gnu-linux', + 'format': 'deb', + 'name': 'vlc', + }, { + 'type': 'package', + 'os': 'gnu-linux', + 'format': 'rpm', + 'name': 'vlc', + }, { + 'type': 'download', + 'os': 'windows', + 'url': 'https://www.videolan.org/vlc/download-windows.html', + }, { + 'type': 'download', + 'os': 'macos', + 'url': 'https://www.videolan.org/vlc/download-macosx.html', + }, { + 'type': 'store', + 'os': 'android', + 'store_name': 'google-play', + 'url': store_url('google-play', 'org.videolan.vlc') + }, { + 'type': 'store', + 'os': 'android', + 'store_name': 'f-droid', + 'url': store_url('f-droid', 'org.videolan.vlc') + }] }, { - 'name': _('kodi'), - 'platforms': [ - { - 'type': 'package', - 'os': 'gnu-linux', - 'format': 'deb', - 'name': 'kodi', - }, - { - 'type': 'package', - 'os': 'gnu-linux', - 'format': 'rpm', - 'name': 'kodi', - }, - { - 'type': 'download', - 'os': 'windows', - 'url': 'http://kodi.tv/download/', - }, - { - 'type': 'download', - 'os': 'macos', - 'url': 'http://kodi.tv/download/', - }, - { - 'type': 'store', - 'os': 'android', - 'store_name': 'google-play', - 'url': store_url('google-play', 'org.xbmc.kodi') - }, - { - 'type': 'store', - 'os': 'android', - 'store_name': 'f-droid', - 'url': store_url('f-droid', 'org.xbmc.kodi') - }, - ] + 'name': + _('kodi'), + 'platforms': [{ + 'type': 'package', + 'os': 'gnu-linux', + 'format': 'deb', + 'name': 'kodi', + }, { + 'type': 'package', + 'os': 'gnu-linux', + 'format': 'rpm', + 'name': 'kodi', + }, { + 'type': 'download', + 'os': 'windows', + 'url': 'http://kodi.tv/download/', + }, { + 'type': 'download', + 'os': 'macos', + 'url': 'http://kodi.tv/download/', + }, { + 'type': 'store', + 'os': 'android', + 'store_name': 'google-play', + 'url': store_url('google-play', 'org.xbmc.kodi') + }, { + 'type': 'store', + 'os': 'android', + 'store_name': 'f-droid', + 'url': store_url('f-droid', 'org.xbmc.kodi') + }] }, { - 'name': _('yaacc'), - 'platforms': [ - { - 'type': 'store', - 'os': 'android', - 'store_name': 'f-droid', - 'url': store_url('f-droid', 'de.yaacc') - }, - ] + 'name': + _('yaacc'), + 'platforms': [{ + 'type': 'store', + 'os': 'android', + 'store_name': 'f-droid', + 'url': store_url('f-droid', 'de.yaacc') + }] }, { - 'name': _('totem'), - 'platforms': [ - { - 'type': 'package', - 'os': 'gnu-linux', - 'format': 'deb', - 'name': 'totem', - }, - { - 'type': 'package', - 'os': 'gnu-linux', - 'format': 'rpm', - 'name': 'totem', - }, - ] + 'name': + _('totem'), + 'platforms': [{ + 'type': 'package', + 'os': 'gnu-linux', + 'format': 'deb', + 'name': 'totem', + }, { + 'type': 'package', + 'os': 'gnu-linux', + 'format': 'rpm', + 'name': 'totem', + }] }, ]) # TODO: get all media directories from config file # for now hard code default media folder. -backup = validate_backup({ +backup = { 'data': { 'files': ['/etc/minidlna.conf'], 'directories': ['/var/lib/minidlna'] }, 'services': ['minidlna'] -}) +} diff --git a/plinth/modules/mldonkey/__init__.py b/plinth/modules/mldonkey/__init__.py index d282a03c0..147edf899 100644 --- a/plinth/modules/mldonkey/__init__.py +++ b/plinth/modules/mldonkey/__init__.py @@ -10,12 +10,13 @@ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users import add_user_to_share_group from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -56,7 +57,8 @@ class MLDonkeyApp(app_module.App): app_id=self.app_id, version=version, name=_('MLDonkey'), icon_filename='mldonkey', short_description=_('Peer-to-peer File Sharing'), - description=_description, manual_page='MLDonkey', clients=clients) + description=_description, manual_page='MLDonkey', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-mldonkey', info.name, @@ -88,6 +90,10 @@ class MLDonkeyApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-mldonkey', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/mldonkey/manifest.py b/plinth/modules/mldonkey/manifest.py index 8db4380f2..2240ae774 100644 --- a/plinth/modules/mldonkey/manifest.py +++ b/plinth/modules/mldonkey/manifest.py @@ -6,7 +6,6 @@ Application manifest for mldonkey. from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('MLDonkey'), @@ -37,7 +36,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'config': { 'files': [ '/var/lib/mldonkey/bittorrent.ini', '/var/lib/mldonkey/bt_dht.ini', @@ -55,4 +54,4 @@ backup = validate_backup({ ] }, 'services': ['mldonkey-server'] -}) +} diff --git a/plinth/modules/monkeysphere/__init__.py b/plinth/modules/monkeysphere/__init__.py index b125a56b6..d4ffff00a 100644 --- a/plinth/modules/monkeysphere/__init__.py +++ b/plinth/modules/monkeysphere/__init__.py @@ -7,9 +7,10 @@ from django.utils.translation import ugettext_lazy as _ from plinth import app as app_module from plinth import menu +from plinth.modules.backups.components import BackupRestore from plinth.modules.users.components import UsersAndGroups -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -60,6 +61,10 @@ class MonkeysphereApp(app_module.App): reserved_usernames=['monkeysphere']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-monkeysphere', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/monkeysphere/manifest.py b/plinth/modules/monkeysphere/manifest.py index 3a0ac8aec..0f5708de6 100644 --- a/plinth/modules/monkeysphere/manifest.py +++ b/plinth/modules/monkeysphere/manifest.py @@ -3,13 +3,11 @@ Application manfiest for monkeysphere. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'config': { 'directories': ['/etc/monkeysphere/'] }, 'secrets': { 'directories': ['/var/lib/monkeysphere/'] } -}) +} diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index aa73ae105..b39889bd7 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -13,12 +13,13 @@ from plinth import app as app_module from plinth import frontpage, menu from plinth.daemon import Daemon from plinth.modules import names +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.modules.users.components import UsersAndGroups from plinth.utils import Version -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -50,7 +51,8 @@ class MumbleApp(app_module.App): info = app_module.Info( app_id=self.app_id, version=version, name=_('Mumble'), icon_filename='mumble', short_description=_('Voice Chat'), - description=_description, manual_page='Mumble', clients=clients, + description=_description, manual_page='Mumble', + clients=manifest.clients, donation_url='https://wiki.mumble.info/wiki/Donate') self.add(info) @@ -88,6 +90,10 @@ class MumbleApp(app_module.App): reserved_usernames=['mumble-server']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-mumble', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/mumble/manifest.py b/plinth/modules/mumble/manifest.py index 1befe4e75..79a702b7d 100644 --- a/plinth/modules/mumble/manifest.py +++ b/plinth/modules/mumble/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': @@ -55,9 +54,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'data': { 'directories': ['/var/lib/mumble-server'] }, 'services': ['mumble-server'] -}) +} diff --git a/plinth/modules/names/__init__.py b/plinth/modules/names/__init__.py index b06316fbc..45b47934c 100644 --- a/plinth/modules/names/__init__.py +++ b/plinth/modules/names/__init__.py @@ -9,11 +9,11 @@ from django.utils.translation import ugettext_lazy as _ from plinth import app as app_module from plinth import cfg, menu +from plinth.modules.backups.components import BackupRestore from plinth.signals import domain_added, domain_removed from plinth.utils import format_lazy -from . import components -from .manifest import backup # noqa, pylint: disable=unused-import +from . import components, manifest version = 1 @@ -52,6 +52,10 @@ class NamesApp(app_module.App): 'names:index', parent_url_name='system') self.add(menu_item) + backup_restore = BackupRestore('backup-restore-names', + **manifest.backup) + self.add(backup_restore) + domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) diff --git a/plinth/modules/names/manifest.py b/plinth/modules/names/manifest.py index 68d0ed7fb..2d5b9ab3f 100644 --- a/plinth/modules/names/manifest.py +++ b/plinth/modules/names/manifest.py @@ -3,6 +3,4 @@ Application manifest for names. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index a2d6c7be9..4eebfd66a 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -12,11 +12,12 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 4 @@ -60,7 +61,7 @@ class OpenVPNApp(app_module.App): name=_('OpenVPN'), icon_filename='openvpn', short_description=_('Virtual Private Network'), description=_description, manual_page='OpenVPN', - clients=clients) + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-openvpn', info.name, @@ -92,6 +93,10 @@ class OpenVPNApp(app_module.App): groups=self.groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-openvpn', + **manifest.backup) + self.add(backup_restore) + def is_enabled(self): """Return whether all the leader components are enabled. diff --git a/plinth/modules/openvpn/manifest.py b/plinth/modules/openvpn/manifest.py index ffd3c17c4..88d3c0b74 100644 --- a/plinth/modules/openvpn/manifest.py +++ b/plinth/modules/openvpn/manifest.py @@ -6,12 +6,11 @@ Application manifest for OpenVPN. from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup _package_id = 'de.blinkt.openvpn' _download_url = 'https://openvpn.net/community-downloads' -backup = validate_backup({'secrets': {'directories': ['/etc/openvpn/']}}) +backup = {'secrets': {'directories': ['/etc/openvpn/']}} clients = validate([{ 'name': diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index 98f22e733..02891e86a 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -9,11 +9,11 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.names.components import DomainType from plinth.utils import format_lazy -from . import utils -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest, utils version = 2 @@ -80,6 +80,10 @@ class PagekiteApp(app_module.App): daemon = Daemon('daemon-pagekite', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-pagekite', + **manifest.backup) + self.add(backup_restore) + # Register kite name with Name Services module. setup_helper = globals()['setup_helper'] if setup_helper.get_state() != 'needs-setup' and self.is_enabled(): diff --git a/plinth/modules/pagekite/manifest.py b/plinth/modules/pagekite/manifest.py index bd6e2a206..448d62fd5 100644 --- a/plinth/modules/pagekite/manifest.py +++ b/plinth/modules/pagekite/manifest.py @@ -3,11 +3,9 @@ Application manifest for pagekite. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'config': { 'directories': ['/etc/pagekite.d/'] }, 'services': ['pagekite'] -}) +} diff --git a/plinth/modules/performance/__init__.py b/plinth/modules/performance/__init__.py index 0c67a98bb..4591f451d 100644 --- a/plinth/modules/performance/__init__.py +++ b/plinth/modules/performance/__init__.py @@ -9,7 +9,7 @@ from plinth import app as app_module from plinth import menu from plinth.daemon import Daemon -from .manifest import clients +from . import manifest version = 1 @@ -45,7 +45,8 @@ class PerformanceApp(app_module.App): name=_('Performance'), icon='fa-bar-chart', short_description=_('System Monitoring'), description=_description, - manual_page='Performance', clients=clients) + manual_page='Performance', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-performance', info.name, diff --git a/plinth/modules/power/__init__.py b/plinth/modules/power/__init__.py index 936e86fac..75e4ce599 100644 --- a/plinth/modules/power/__init__.py +++ b/plinth/modules/power/__init__.py @@ -6,8 +6,9 @@ FreedomBox app for power controls. from django.utils.translation import ugettext_lazy as _ from plinth import app as app_module +from plinth.modules.backups.components import BackupRestore -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -32,4 +33,8 @@ class PowerApp(app_module.App): description=_description, manual_page='Power') self.add(info) + backup_restore = BackupRestore('backup-restore-power', + **manifest.backup) + self.add(backup_restore) + # not in menu, see issue #834 diff --git a/plinth/modules/power/manifest.py b/plinth/modules/power/manifest.py index 61ad25ded..d958ecbac 100644 --- a/plinth/modules/power/manifest.py +++ b/plinth/modules/power/manifest.py @@ -3,6 +3,4 @@ Application manifest for power. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py index 8114a711b..40255cec3 100644 --- a/plinth/modules/privoxy/__init__.py +++ b/plinth/modules/privoxy/__init__.py @@ -11,11 +11,12 @@ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import diagnose_url +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from plinth.utils import format_lazy from plinth.modules.users.components import UsersAndGroups +from plinth.utils import format_lazy -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -82,6 +83,10 @@ class PrivoxyApp(app_module.App): reserved_usernames=['privoxy']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-privoxy', + **manifest.backup) + self.add(backup_restore) + def diagnose(self): """Run diagnostics and return the results.""" results = super().diagnose() diff --git a/plinth/modules/privoxy/manifest.py b/plinth/modules/privoxy/manifest.py index 336b83e47..87747ba2b 100644 --- a/plinth/modules/privoxy/manifest.py +++ b/plinth/modules/privoxy/manifest.py @@ -3,6 +3,4 @@ Application manifest for privoxy. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index 43555e679..f45e700e9 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -13,12 +13,13 @@ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules import names +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -59,7 +60,7 @@ class QuasselApp(app_module.App): name=_('Quassel'), icon_filename='quassel', short_description=_('IRC Client'), description=_description, manual_page='Quassel', - clients=clients) + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-quassel', info.name, @@ -96,6 +97,10 @@ class QuasselApp(app_module.App): reserved_usernames=['quasselcore']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-quassel', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/quassel/manifest.py b/plinth/modules/quassel/manifest.py index f4e4b4b5a..8cab21ab4 100644 --- a/plinth/modules/quassel/manifest.py +++ b/plinth/modules/quassel/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': @@ -45,9 +44,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'secrets': { 'directories': ['/var/lib/quassel/'] }, 'services': ['quasselcore'], -}) +} diff --git a/plinth/modules/radicale/__init__.py b/plinth/modules/radicale/__init__.py index 9d803deb7..abaf90eb8 100644 --- a/plinth/modules/radicale/__init__.py +++ b/plinth/modules/radicale/__init__.py @@ -12,11 +12,12 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.modules.apache.components import Uwsgi, Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups from plinth.utils import Version, format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -54,7 +55,8 @@ class RadicaleApp(app_module.App): name=_('Radicale'), icon_filename='radicale', short_description=_('Calendar and Addressbook'), description=_description, - manual_page='Radicale', clients=clients) + manual_page='Radicale', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-radicale', info.name, @@ -84,6 +86,10 @@ class RadicaleApp(app_module.App): reserved_usernames=['radicale']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-radicale', + **manifest.backup) + self.add(backup_restore) + def enable(self): """Fix missing directories before enabling radicale.""" actions.superuser_run('radicale', ['fix-paths']) diff --git a/plinth/modules/radicale/manifest.py b/plinth/modules/radicale/manifest.py index f17f220ec..a341863ab 100644 --- a/plinth/modules/radicale/manifest.py +++ b/plinth/modules/radicale/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': @@ -79,9 +78,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'data': { 'directories': ['/var/lib/radicale/'] }, 'services': ['uwsgi'] -}) +} diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py index 9452140b2..5b663f2af 100644 --- a/plinth/modules/roundcube/__init__.py +++ b/plinth/modules/roundcube/__init__.py @@ -9,9 +9,10 @@ from plinth import actions from plinth import app as app_module from plinth import frontpage, menu from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -53,7 +54,8 @@ class RoundcubeApp(app_module.App): name=_('Roundcube'), icon_filename='roundcube', short_description=_('Email Client'), description=_description, - manual_page='Roundcube', clients=clients) + manual_page='Roundcube', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-roundcube', info.name, @@ -76,6 +78,10 @@ class RoundcubeApp(app_module.App): urls=['https://{host}/roundcube']) self.add(webserver) + backup_restore = BackupRestore('backup-restore-roundcube', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/roundcube/manifest.py b/plinth/modules/roundcube/manifest.py index e71c0eae6..30756b043 100644 --- a/plinth/modules/roundcube/manifest.py +++ b/plinth/modules/roundcube/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('Roundcube'), @@ -13,4 +12,4 @@ clients = validate([{ }] }]) -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/samba/__init__.py b/plinth/modules/samba/__init__.py index 118aec07f..e13452a52 100644 --- a/plinth/modules/samba/__init__.py +++ b/plinth/modules/samba/__init__.py @@ -15,11 +15,12 @@ from plinth import actions from plinth import app as app_module from plinth import frontpage, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 2 @@ -60,7 +61,8 @@ class SambaApp(app_module.App): info = app_module.Info( app_id=self.app_id, version=version, name=_('Samba'), icon_filename='samba', short_description=_('Network File Storage'), - manual_page='Samba', description=_description, clients=clients, + manual_page='Samba', description=_description, + clients=manifest.clients, donation_url='https://www.samba.org/samba/donations.html') self.add(info) @@ -96,6 +98,23 @@ class SambaApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = SambaBackupRestore('backup-restore-samba', + **manifest.backup) + self.add(backup_restore) + + +class SambaBackupRestore(BackupRestore): + """Component to backup/restore Samba.""" + + def backup_pre(self, packet): + """Save registry share configuration.""" + actions.superuser_run('samba', ['dump-shares']) + + def restore_post(self, packet): + """Restore configuration.""" + actions.superuser_run('samba', ['setup']) + actions.superuser_run('samba', ['restore-shares']) + def setup(helper, old_version=None): """Install and configure the module.""" @@ -148,14 +167,3 @@ def get_shares(): output = actions.superuser_run('samba', ['get-shares']) return json.loads(output) - - -def backup_pre(packet): - """Save registry share configuration.""" - actions.superuser_run('samba', ['dump-shares']) - - -def restore_post(packet): - """Restore configuration.""" - actions.superuser_run('samba', ['setup']) - actions.superuser_run('samba', ['restore-shares']) diff --git a/plinth/modules/samba/manifest.py b/plinth/modules/samba/manifest.py index 60169f435..41869533c 100644 --- a/plinth/modules/samba/manifest.py +++ b/plinth/modules/samba/manifest.py @@ -6,7 +6,6 @@ Application manifest for Samba. from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup SHARES_CONF_BACKUP_FILE = '/var/lib/plinth/backups-data/samba-shares-dump.conf' @@ -77,9 +76,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'data': { 'files': [SHARES_CONF_BACKUP_FILE] }, 'services': ['smbd', 'nmbd'] -}) +} diff --git a/plinth/modules/searx/__init__.py b/plinth/modules/searx/__init__.py index f1189e73e..be661a77c 100644 --- a/plinth/modules/searx/__init__.py +++ b/plinth/modules/searx/__init__.py @@ -11,11 +11,11 @@ from plinth import actions from plinth import app as app_module from plinth import frontpage, menu from plinth.modules.apache.components import Uwsgi, Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups -from .manifest import backup # noqa, pylint: disable=unused-import -from .manifest import (PUBLIC_ACCESS_SETTING_FILE, clients) +from . import manifest version = 4 @@ -47,7 +47,8 @@ class SearxApp(app_module.App): info = app_module.Info( app_id=self.app_id, version=version, name=_('Searx'), icon_filename='searx', short_description=_('Web Search'), - description=_description, manual_page='Searx', clients=clients, + description=_description, manual_page='Searx', + clients=manifest.clients, donation_url='https://searx.me/static/donate.html') self.add(info) @@ -83,6 +84,10 @@ class SearxApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-searx', + **manifest.backup) + self.add(backup_restore) + def set_shortcut_login_required(self, login_required): """Change the login_required property of shortcut.""" shortcut = self.remove('shortcut-searx') @@ -122,7 +127,7 @@ def get_safe_search_setting(): def is_public_access_enabled(): """Check whether public access is enabled for Searx.""" - return os.path.exists(PUBLIC_ACCESS_SETTING_FILE) + return os.path.exists(manifest.PUBLIC_ACCESS_SETTING_FILE) def enable_public_access(): diff --git a/plinth/modules/searx/manifest.py b/plinth/modules/searx/manifest.py index 2a741634e..8b260ce46 100644 --- a/plinth/modules/searx/manifest.py +++ b/plinth/modules/searx/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('Searx'), @@ -15,4 +14,4 @@ clients = validate([{ PUBLIC_ACCESS_SETTING_FILE = '/etc/searx/allow_public_access' -backup = validate_backup({'config': {'files': [PUBLIC_ACCESS_SETTING_FILE]}}) +backup = {'config': {'files': [PUBLIC_ACCESS_SETTING_FILE]}} diff --git a/plinth/modules/security/__init__.py b/plinth/modules/security/__init__.py index 4d78bbd9b..0f98425f2 100644 --- a/plinth/modules/security/__init__.py +++ b/plinth/modules/security/__init__.py @@ -13,8 +13,9 @@ from django.utils.translation import ugettext_lazy as _ from plinth import actions from plinth import app as app_module from plinth import menu, module_loader +from plinth.modules.backups.components import BackupRestore -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 6 @@ -50,6 +51,10 @@ class SecurityApp(app_module.App): 'security:index', parent_url_name='system') self.add(menu_item) + backup_restore = BackupRestore('backup-restore-security', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install the required packages""" diff --git a/plinth/modules/security/manifest.py b/plinth/modules/security/manifest.py index c4747d162..ca4afcaf9 100644 --- a/plinth/modules/security/manifest.py +++ b/plinth/modules/security/manifest.py @@ -3,9 +3,4 @@ Application manifest for security. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup( - {'config': { - 'files': ['/etc/security/access.d/50freedombox.conf'] - }}) +backup = {'config': {'files': ['/etc/security/access.d/50freedombox.conf']}} diff --git a/plinth/modules/shaarli/__init__.py b/plinth/modules/shaarli/__init__.py index 3433f10d1..4ec8707b9 100644 --- a/plinth/modules/shaarli/__init__.py +++ b/plinth/modules/shaarli/__init__.py @@ -10,7 +10,7 @@ from plinth import frontpage, menu from plinth.modules.apache.components import Webserver from plinth.modules.firewall.components import Firewall -from .manifest import clients +from . import manifest version = 1 @@ -37,7 +37,7 @@ class ShaarliApp(app_module.App): name=_('Shaarli'), icon_filename='shaarli', short_description=_('Bookmarks'), description=_description, manual_page='Shaarli', - clients=clients) + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-shaarli', info.name, diff --git a/plinth/modules/shadowsocks/__init__.py b/plinth/modules/shadowsocks/__init__.py index 66ffa53a4..2a9934ef1 100644 --- a/plinth/modules/shadowsocks/__init__.py +++ b/plinth/modules/shadowsocks/__init__.py @@ -10,10 +10,11 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.utils import format_lazy -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 3 @@ -76,6 +77,10 @@ class ShadowsocksApp(app_module.App): listen_ports=[(1080, 'tcp4'), (1080, 'tcp6')]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-shadowsocks', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/shadowsocks/manifest.py b/plinth/modules/shadowsocks/manifest.py index 38a0412cb..33cac4e4f 100644 --- a/plinth/modules/shadowsocks/manifest.py +++ b/plinth/modules/shadowsocks/manifest.py @@ -3,13 +3,11 @@ Application manifest for shadowsocks. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'secrets': { 'files': [ '/var/lib/private/shadowsocks-libev/freedombox/freedombox.json' ] }, 'services': ['shadowsocks-libev-local@freedombox'] -}) +} diff --git a/plinth/modules/sharing/__init__.py b/plinth/modules/sharing/__init__.py index 10dad5a43..0640994d2 100644 --- a/plinth/modules/sharing/__init__.py +++ b/plinth/modules/sharing/__init__.py @@ -10,9 +10,10 @@ from django.utils.translation import ugettext_lazy as _ from plinth import actions from plinth import app as app_module from plinth import cfg, menu +from plinth.modules.backups.components import BackupRestore from plinth.utils import format_lazy -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -44,6 +45,10 @@ class SharingApp(app_module.App): parent_url_name='apps') self.add(menu_item) + backup_restore = BackupRestore('backup-restore-sharing', + **manifest.backup) + self.add(backup_restore) + def list_shares(): """Return a list of shares.""" diff --git a/plinth/modules/sharing/manifest.py b/plinth/modules/sharing/manifest.py index 0dedccb20..2da78201d 100644 --- a/plinth/modules/sharing/manifest.py +++ b/plinth/modules/sharing/manifest.py @@ -3,9 +3,7 @@ Application manifest for sharing. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'config': { 'files': ['/etc/apache2/conf-available/sharing-freedombox.conf'] }, @@ -14,4 +12,4 @@ backup = validate_backup({ 'kind': 'config', 'name': 'sharing-freedombox' }] -}) +} diff --git a/plinth/modules/snapshot/__init__.py b/plinth/modules/snapshot/__init__.py index 3e0bf44b6..52ca422d2 100644 --- a/plinth/modules/snapshot/__init__.py +++ b/plinth/modules/snapshot/__init__.py @@ -13,8 +13,9 @@ from plinth import actions from plinth import app as app_module from plinth import menu from plinth.modules import storage +from plinth.modules.backups.components import BackupRestore -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 4 @@ -61,6 +62,18 @@ class SnapshotApp(app_module.App): 'snapshot:index', parent_url_name='system') self.add(menu_item) + backup_restore = SnapshotBackupRestore('backup-restore-snapshot', + **manifest.backup) + self.add(backup_restore) + + +class SnapshotBackupRestore(BackupRestore): + """Component to backup/restore snapshot module.""" + + def restore_post(self, packet): + """Run after restore.""" + actions.superuser_run('snapshot', ['kill-daemon']) + def is_supported(): """Return whether snapshots are support on current setup.""" @@ -129,8 +142,3 @@ def get_configuration(): 'free_space': output['FREE_LIMIT'], } - - -def restore_post(packet): - """Run after restore.""" - actions.superuser_run('snapshot', ['kill-daemon']) diff --git a/plinth/modules/snapshot/manifest.py b/plinth/modules/snapshot/manifest.py index fca79a2be..a1660dfc1 100644 --- a/plinth/modules/snapshot/manifest.py +++ b/plinth/modules/snapshot/manifest.py @@ -3,10 +3,8 @@ Application manifest for snapshot. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'config': { 'files': ['/etc/snapper/configs/root', '/etc/default/snapper'] } -}) +} diff --git a/plinth/modules/ssh/__init__.py b/plinth/modules/ssh/__init__.py index 3657fd849..1281bfeb5 100644 --- a/plinth/modules/ssh/__init__.py +++ b/plinth/modules/ssh/__init__.py @@ -13,9 +13,10 @@ from plinth import actions from plinth import app as app_module from plinth import menu from plinth.daemon import Daemon +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 1 @@ -60,6 +61,9 @@ class SSHApp(app_module.App): daemon = Daemon('daemon-ssh', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-ssh', **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Configure the module.""" diff --git a/plinth/modules/ssh/manifest.py b/plinth/modules/ssh/manifest.py index 6abceee4d..9ab3b80fa 100644 --- a/plinth/modules/ssh/manifest.py +++ b/plinth/modules/ssh/manifest.py @@ -3,9 +3,7 @@ Application manifest for ssh. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'secrets': { 'files': [ '/etc/ssh/ssh_host_ecdsa_key', '/etc/ssh/ssh_host_ecdsa_key.pub', @@ -14,4 +12,4 @@ backup = validate_backup({ '/etc/ssh/ssh_host_rsa_key.pub' ] } -}) +} diff --git a/plinth/modules/storage/__init__.py b/plinth/modules/storage/__init__.py index b0aec5ba9..2f06563ce 100644 --- a/plinth/modules/storage/__init__.py +++ b/plinth/modules/storage/__init__.py @@ -15,10 +15,10 @@ from plinth import actions from plinth import app as app_module from plinth import cfg, glib, menu from plinth.errors import ActionError, PlinthError +from plinth.modules.backups.components import BackupRestore from plinth.utils import format_lazy -from . import udisks2 -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest, udisks2 version = 4 @@ -59,6 +59,10 @@ class StorageApp(app_module.App): 'storage:index', parent_url_name='system') self.add(menu_item) + backup_restore = BackupRestore('backup-restore-storage', + **manifest.backup) + self.add(backup_restore) + # Check every hour for low disk space, every 3 minutes in debug mode interval = 180 if cfg.develop else 3600 glib.schedule(interval, warn_about_low_disk_space) diff --git a/plinth/modules/storage/manifest.py b/plinth/modules/storage/manifest.py index e2909a820..f27f6313e 100644 --- a/plinth/modules/storage/manifest.py +++ b/plinth/modules/storage/manifest.py @@ -3,6 +3,4 @@ Application manifest for storage. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({}) +backup = {} diff --git a/plinth/modules/syncthing/__init__.py b/plinth/modules/syncthing/__init__.py index 2ce8f4c67..ffa2fae30 100644 --- a/plinth/modules/syncthing/__init__.py +++ b/plinth/modules/syncthing/__init__.py @@ -10,12 +10,13 @@ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall -from plinth.modules.users.components import UsersAndGroups from plinth.modules.users import add_user_to_share_group +from plinth.modules.users.components import UsersAndGroups from plinth.utils import format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 3 @@ -59,7 +60,8 @@ class SyncthingApp(app_module.App): name=_('Syncthing'), icon_filename='syncthing', short_description=_('File Synchronization'), description=_description, - manual_page='Syncthing', clients=clients, + manual_page='Syncthing', + clients=manifest.clients, donation_url='https://syncthing.net/donations/') self.add(info) @@ -95,6 +97,10 @@ class SyncthingApp(app_module.App): [SYSTEM_USER], self.groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-syncthing', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/syncthing/manifest.py b/plinth/modules/syncthing/manifest.py index ac0efd4e6..02b7d0293 100644 --- a/plinth/modules/syncthing/manifest.py +++ b/plinth/modules/syncthing/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup _package_id = 'com.nutomic.syncthingandroid' _download_url = 'https://syncthing.net/' @@ -47,9 +46,9 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'secrets': { 'directories': ['/var/lib/syncthing/.config'] }, 'services': ['syncthing@syncthing'] -}) +} diff --git a/plinth/modules/tahoe/__init__.py b/plinth/modules/tahoe/__init__.py index 057bd54a1..50b5f7680 100644 --- a/plinth/modules/tahoe/__init__.py +++ b/plinth/modules/tahoe/__init__.py @@ -14,11 +14,12 @@ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver, diagnose_url +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.utils import format_lazy +from . import manifest from .errors import TahoeConfigurationError -from .manifest import backup # noqa, pylint: disable=unused-import version = 1 @@ -87,6 +88,10 @@ class TahoeApp(app_module.App): daemon = Daemon('daemon-tahoe', managed_services[0]) self.add(daemon) + backup_restore = BackupRestore('backup-restore-tahoe', + **manifest.backup) + self.add(backup_restore) + def is_enabled(self): """Return whether all the leader components are enabled. diff --git a/plinth/modules/tahoe/manifest.py b/plinth/modules/tahoe/manifest.py index e83d5d996..22fb0dbeb 100644 --- a/plinth/modules/tahoe/manifest.py +++ b/plinth/modules/tahoe/manifest.py @@ -3,11 +3,9 @@ Application manfiest for tahoe-lafs. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup({ +backup = { 'secrets': { 'directories': ['/var/lib/tahoe-lafs/'] }, 'services': ['tahoe-lafs'] -}) +} diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py index 2fbbdc7d1..80909be19 100644 --- a/plinth/modules/tor/__init__.py +++ b/plinth/modules/tor/__init__.py @@ -13,13 +13,13 @@ from plinth import menu from plinth.daemon import (Daemon, app_is_running, diagnose_netcat, diagnose_port_listening) from plinth.modules.apache.components import diagnose_url +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.names.components import DomainType from plinth.modules.users.components import UsersAndGroups from plinth.signals import domain_added, domain_removed -from . import utils -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest, utils version = 5 @@ -55,7 +55,7 @@ class TorApp(app_module.App): name=_('Tor'), icon_filename='tor', short_description=_('Anonymity Network'), description=_description, manual_page='Tor', - clients=clients, + clients=manifest.clients, donation_url='https://donate.torproject.org/') self.add(info) @@ -87,6 +87,9 @@ class TorApp(app_module.App): reserved_usernames=['debian-tor']) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-tor', **manifest.backup) + self.add(backup_restore) + # Register hidden service name with Name Services module. setup_helper = globals()['setup_helper'] if setup_helper.get_state() != 'needs-setup' and \ diff --git a/plinth/modules/tor/manifest.py b/plinth/modules/tor/manifest.py index a15799ad1..3a00a257d 100644 --- a/plinth/modules/tor/manifest.py +++ b/plinth/modules/tor/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import store_url, validate -from plinth.modules.backups.api import validate as validate_backup _orbot_package_id = 'org.torproject.android' _tor_browser_download_url = \ @@ -41,7 +40,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'config': { 'directories': ['/etc/tor/'] }, @@ -49,4 +48,4 @@ backup = validate_backup({ 'directories': ['/var/lib/tor/', '/var/lib/tor-instances/'] }, 'services': ['tor@plinth'] -}) +} diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py index fb6c1385e..0639e3489 100644 --- a/plinth/modules/transmission/__init__.py +++ b/plinth/modules/transmission/__init__.py @@ -12,11 +12,12 @@ from plinth import app as app_module from plinth import frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users import add_user_to_share_group from plinth.modules.users.components import UsersAndGroups -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 3 @@ -52,7 +53,8 @@ class TransmissionApp(app_module.App): icon_filename='transmission', short_description=_('BitTorrent Web Client'), description=_description, manual_page='Transmission', - clients=clients, donation_url='https://transmissionbt.com/donate/') + clients=manifest.clients, + donation_url='https://transmissionbt.com/donate/') self.add(info) menu_item = menu.Menu('menu-transmission', info.name, @@ -84,6 +86,10 @@ class TransmissionApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = BackupRestore('backup-restore-transmission', + **manifest.backup) + self.add(backup_restore) + def setup(helper, old_version=None): """Install and configure the module.""" diff --git a/plinth/modules/transmission/manifest.py b/plinth/modules/transmission/manifest.py index f858b29d9..dd6dccad5 100644 --- a/plinth/modules/transmission/manifest.py +++ b/plinth/modules/transmission/manifest.py @@ -3,7 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from plinth.clients import validate -from plinth.modules.backups.api import validate as validate_backup clients = validate([{ 'name': _('Transmission'), @@ -13,7 +12,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'data': { 'directories': ['/var/lib/transmission-daemon/.config'] }, @@ -21,4 +20,4 @@ backup = validate_backup({ 'files': ['/etc/transmission-daemon/settings.json'] }, 'services': ['transmission-daemon'] -}) +} diff --git a/plinth/modules/ttrss/__init__.py b/plinth/modules/ttrss/__init__.py index 69f070de5..756a3ac16 100644 --- a/plinth/modules/ttrss/__init__.py +++ b/plinth/modules/ttrss/__init__.py @@ -11,11 +11,12 @@ from plinth import app as app_module from plinth import cfg, frontpage, menu from plinth.daemon import Daemon from plinth.modules.apache.components import Webserver +from plinth.modules.backups.components import BackupRestore from plinth.modules.firewall.components import Firewall from plinth.modules.users.components import UsersAndGroups from plinth.utils import Version, format_lazy -from .manifest import backup, clients # noqa, pylint: disable=unused-import +from . import manifest version = 3 @@ -56,7 +57,8 @@ class TTRSSApp(app_module.App): name=_('Tiny Tiny RSS'), icon_filename='ttrss', short_description=_('News Feed Reader'), description=_description, - manual_page='TinyTinyRSS', clients=clients) + manual_page='TinyTinyRSS', + clients=manifest.clients) self.add(info) menu_item = menu.Menu('menu-ttrss', info.name, info.short_description, @@ -87,12 +89,28 @@ class TTRSSApp(app_module.App): groups=groups) self.add(users_and_groups) + backup_restore = TTRSSBackupRestore('backup-restore-ttrss', + **manifest.backup) + self.add(backup_restore) + def enable(self): """Enable components and API access.""" super().enable() actions.superuser_run('ttrss', ['enable-api-access']) +class TTRSSBackupRestore(BackupRestore): + """Component to backup/restore TT-RSS""" + + def backup_pre(self, packet): + """Save database contents.""" + actions.superuser_run('ttrss', ['dump-database']) + + def restore_post(self, packet): + """Restore database contents.""" + actions.superuser_run('ttrss', ['restore-database']) + + def setup(helper, old_version=None): """Install and configure the module.""" helper.call('pre', actions.superuser_run, 'ttrss', ['pre-setup']) @@ -114,13 +132,3 @@ def force_upgrade(helper, packages): helper.install(['tt-rss'], force_configuration='new') actions.superuser_run('ttrss', ['setup']) return True - - -def backup_pre(packet): - """Save database contents.""" - actions.superuser_run('ttrss', ['dump-database']) - - -def restore_post(packet): - """Restore database contents.""" - actions.superuser_run('ttrss', ['restore-database']) diff --git a/plinth/modules/ttrss/manifest.py b/plinth/modules/ttrss/manifest.py index 1d9b7bb14..3c44b8e7e 100644 --- a/plinth/modules/ttrss/manifest.py +++ b/plinth/modules/ttrss/manifest.py @@ -2,7 +2,6 @@ from django.utils.translation import ugettext_lazy as _ -from plinth.modules.backups.api import validate as validate_backup from plinth.clients import store_url, validate clients = validate([{ @@ -28,7 +27,7 @@ clients = validate([{ }] }]) -backup = validate_backup({ +backup = { 'data': { 'files': ['/var/lib/plinth/backups-data/ttrss-database.sql'] }, @@ -36,4 +35,4 @@ backup = validate_backup({ 'files': ['/etc/tt-rss/database.php'] }, 'services': ['tt-rss'] -}) +} diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py index 67c8dec6c..1f7c599be 100644 --- a/plinth/modules/upgrades/__init__.py +++ b/plinth/modules/upgrades/__init__.py @@ -15,8 +15,9 @@ import plinth from plinth import actions from plinth import app as app_module from plinth import cfg, glib, kvstore, menu +from plinth.modules.backups.components import BackupRestore -from .manifest import backup # noqa, pylint: disable=unused-import +from . import manifest version = 8 @@ -79,6 +80,10 @@ class UpgradesApp(app_module.App): 'upgrades:index', parent_url_name='system') self.add(menu_item) + backup_restore = BackupRestore('backup-restore-upgrades', + **manifest.backup) + self.add(backup_restore) + self._show_new_release_notification() # Check every day (every 3 minutes in debug mode): diff --git a/plinth/modules/upgrades/manifest.py b/plinth/modules/upgrades/manifest.py index b6efa1644..6bdd5f9dd 100644 --- a/plinth/modules/upgrades/manifest.py +++ b/plinth/modules/upgrades/manifest.py @@ -3,9 +3,4 @@ Application manifest for upgrades. """ -from plinth.modules.backups.api import validate as validate_backup - -backup = validate_backup( - {'config': { - 'files': ['/etc/apt/apt.conf.d/20auto-upgrades'] - }}) +backup = {'config': {'files': ['/etc/apt/apt.conf.d/20auto-upgrades']}} diff --git a/plinth/modules/wireguard/__init__.py b/plinth/modules/wireguard/__init__.py index 214703ff5..c3c130edd 100644 --- a/plinth/modules/wireguard/__init__.py +++ b/plinth/modules/wireguard/__init__.py @@ -11,8 +11,7 @@ from plinth import cfg, frontpage, menu from plinth.modules.firewall.components import Firewall from plinth.utils import format_lazy, import_from_gi -from . import utils -from .manifest import clients # noqa, pylint: disable=unused-import +from . import manifest, utils nm = import_from_gi('NM', '1.0') @@ -50,7 +49,8 @@ class WireguardApp(app_module.App): app_id=self.app_id, version=version, name=_('WireGuard'), icon_filename='wireguard', short_description=_('Virtual Private Network'), - description=_description, manual_page='WireGuard', clients=clients, + description=_description, manual_page='WireGuard', + clients=manifest.clients, donation_url='https://www.wireguard.com/donations/') self.add(info)