backups: Use the backup component in all apps

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Veiko Aasa <veiko17@disroot.org>
This commit is contained in:
Sunil Mohan Adapa 2020-09-28 16:01:49 -07:00 committed by Veiko Aasa
parent f630fb6059
commit fb1898befc
No known key found for this signature in database
GPG Key ID: 478539CAE680674E
105 changed files with 748 additions and 644 deletions

View File

@ -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',

View File

@ -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 = {}

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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 = {}

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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."""

View File

@ -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'],
})
}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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)

View File

@ -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 = {}

View File

@ -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."""

View File

@ -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']}

View File

@ -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()

View File

@ -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']}}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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)

View File

@ -3,6 +3,4 @@
Application manifest for diagnostics.
"""
from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({})
backup = {}

View File

@ -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,

View File

@ -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',

View File

@ -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/']}}

View File

@ -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)

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -3,6 +3,4 @@
Application manifest for firewall.
"""
from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({})
backup = {}

View File

@ -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:

View File

@ -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]
}
})
}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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,

View File

@ -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/']}}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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 = {}

View File

@ -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)

View File

@ -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/']}}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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)

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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/']
}
})
}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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)

View File

@ -3,6 +3,4 @@
Application manifest for names.
"""
from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({})
backup = {}

View File

@ -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.

View File

@ -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':

View File

@ -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():

View File

@ -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']
})
}

View File

@ -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,

View File

@ -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

View File

@ -3,6 +3,4 @@
Application manifest for power.
"""
from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({})
backup = {}

View File

@ -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()

View File

@ -3,6 +3,4 @@
Application manifest for privoxy.
"""
from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({})
backup = {}

View File

@ -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."""

View File

@ -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'],
})
}

View File

@ -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'])

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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 = {}

View File

@ -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'])

View File

@ -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']
})
}

View File

@ -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():

View File

@ -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]}}

View File

@ -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"""

View File

@ -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']}}

View File

@ -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,

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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'
}]
})
}

View File

@ -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'])

View File

@ -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']
}
})
}

View File

@ -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."""

View File

@ -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'
]
}
})
}

View File

@ -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)

View File

@ -3,6 +3,4 @@
Application manifest for storage.
"""
from plinth.modules.backups.api import validate as validate_backup
backup = validate_backup({})
backup = {}

View File

@ -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."""

View File

@ -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']
})
}

View File

@ -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.

View File

@ -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']
})
}

View File

@ -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 \

View File

@ -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']
})
}

View File

@ -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."""

View File

@ -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']
})
}

Some files were not shown because too many files have changed in this diff Show More