mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
backups: Save new backup location to plinth database
Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net> Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
7c7ad6d56a
commit
de4a019063
@ -81,6 +81,7 @@ KNOWN_ERRORS = [{
|
|||||||
class BaseBorgRepository(ABC):
|
class BaseBorgRepository(ABC):
|
||||||
"""Base class for all kinds of Borg repositories."""
|
"""Base class for all kinds of Borg repositories."""
|
||||||
uuid = None
|
uuid = None
|
||||||
|
is_mounted = True
|
||||||
|
|
||||||
def __init__(self, uuid=None, path=None, credentials=None, **kwargs):
|
def __init__(self, uuid=None, path=None, credentials=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -262,6 +263,27 @@ class BaseBorgRepository(ABC):
|
|||||||
create_subvolume=False, backup_file=archive_path,
|
create_subvolume=False, backup_file=archive_path,
|
||||||
encryption_passphrase=passphrase)
|
encryption_passphrase=passphrase)
|
||||||
|
|
||||||
|
def _get_network_storage_format(self, store_credentials, verified):
|
||||||
|
storage = {
|
||||||
|
'path': self._path,
|
||||||
|
'storage_type': self.storage_type,
|
||||||
|
'added_by_module': 'backups',
|
||||||
|
'verified': verified
|
||||||
|
}
|
||||||
|
if self.uuid:
|
||||||
|
storage['uuid'] = self.uuid
|
||||||
|
if store_credentials:
|
||||||
|
storage['credentials'] = self.credentials
|
||||||
|
return storage
|
||||||
|
|
||||||
|
def save(self, store_credentials=True, verified=False):
|
||||||
|
"""
|
||||||
|
Save the repository in network_storage (kvstore).
|
||||||
|
- store_credentials: Boolean whether credentials should be stored.
|
||||||
|
"""
|
||||||
|
storage = self._get_network_storage_format(store_credentials, verified)
|
||||||
|
self.uuid = network_storage.update_or_add(storage)
|
||||||
|
|
||||||
|
|
||||||
class RootBorgRepository(BaseBorgRepository):
|
class RootBorgRepository(BaseBorgRepository):
|
||||||
"""Borg repository on the root filesystem."""
|
"""Borg repository on the root filesystem."""
|
||||||
@ -287,20 +309,16 @@ class BorgRepository(BaseBorgRepository):
|
|||||||
KNOWN_CREDENTIALS = ['encryption_passphrase']
|
KNOWN_CREDENTIALS = ['encryption_passphrase']
|
||||||
storage_type = 'disk'
|
storage_type = 'disk'
|
||||||
|
|
||||||
@property
|
|
||||||
def is_mounted(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
raise NotImplementedError
|
# TODO Use disk label as the name
|
||||||
|
# Also, name isn't being used yet
|
||||||
|
return self.repo_path
|
||||||
|
|
||||||
@property
|
def remove_repository(self):
|
||||||
def repo_path(self):
|
"""Remove a repository from the kvstore and delete its mountpoint"""
|
||||||
"""
|
self.umount()
|
||||||
Return the path to use for backups actions.
|
network_storage.delete(self.uuid)
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class SshBorgRepository(BaseBorgRepository):
|
class SshBorgRepository(BaseBorgRepository):
|
||||||
@ -331,27 +349,6 @@ class SshBorgRepository(BaseBorgRepository):
|
|||||||
['is-mounted', '--mountpoint', self.mountpoint])
|
['is-mounted', '--mountpoint', self.mountpoint])
|
||||||
return json.loads(output)
|
return json.loads(output)
|
||||||
|
|
||||||
def _get_network_storage_format(self, store_credentials, verified):
|
|
||||||
storage = {
|
|
||||||
'path': self._path,
|
|
||||||
'storage_type': self.storage_type,
|
|
||||||
'added_by_module': 'backups',
|
|
||||||
'verified': verified
|
|
||||||
}
|
|
||||||
if self.uuid:
|
|
||||||
storage['uuid'] = self.uuid
|
|
||||||
if store_credentials:
|
|
||||||
storage['credentials'] = self.credentials
|
|
||||||
return storage
|
|
||||||
|
|
||||||
def save(self, store_credentials=True, verified=False):
|
|
||||||
"""
|
|
||||||
Save the repository in network_storage (kvstore).
|
|
||||||
- store_credentials: Boolean whether credentials should be stored.
|
|
||||||
"""
|
|
||||||
storage = self._get_network_storage_format(store_credentials, verified)
|
|
||||||
self.uuid = network_storage.update_or_add(storage)
|
|
||||||
|
|
||||||
def mount(self):
|
def mount(self):
|
||||||
if self.is_mounted:
|
if self.is_mounted:
|
||||||
return
|
return
|
||||||
@ -400,19 +397,43 @@ class SshBorgRepository(BaseBorgRepository):
|
|||||||
return (arguments, kwargs)
|
return (arguments, kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_ssh_repositories():
|
def get_repositories(storage_type):
|
||||||
|
"""Get all repositories of a given storage type."""
|
||||||
|
if storage_type == 'disk':
|
||||||
|
return _get_disk_repositories()
|
||||||
|
|
||||||
|
return _get_ssh_repositories()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_ssh_repositories():
|
||||||
"""Get all SSH Repositories including the archive content"""
|
"""Get all SSH Repositories including the archive content"""
|
||||||
repositories = {}
|
repositories = {}
|
||||||
for storage in network_storage.get_storages().values():
|
for storage in network_storage.get_storages().values():
|
||||||
repository = SshBorgRepository(**storage)
|
if storage['storage_type'] == 'ssh':
|
||||||
repositories[storage['uuid']] = repository.get_view_content()
|
repository = SshBorgRepository(**storage)
|
||||||
|
repositories[storage['uuid']] = repository.get_view_content()
|
||||||
|
|
||||||
return repositories
|
return repositories
|
||||||
|
|
||||||
|
|
||||||
def get_repository(uuid):
|
def _get_disk_repositories():
|
||||||
"""Get a local or SSH repository object instance."""
|
"""Get all disk repositories including the archive content"""
|
||||||
|
repositories = {}
|
||||||
|
for storage in network_storage.get_storages().values():
|
||||||
|
if storage['storage_type'] == 'disk':
|
||||||
|
repository = BorgRepository(**storage)
|
||||||
|
repositories[storage['uuid']] = repository.get_view_content()
|
||||||
|
|
||||||
|
return repositories
|
||||||
|
|
||||||
|
|
||||||
|
def create_repository(uuid):
|
||||||
|
"""Create a local or SSH repository object instance."""
|
||||||
if uuid == ROOT_REPOSITORY_UUID:
|
if uuid == ROOT_REPOSITORY_UUID:
|
||||||
return RootBorgRepository(path=ROOT_REPOSITORY)
|
return RootBorgRepository(path=ROOT_REPOSITORY)
|
||||||
|
|
||||||
return SshBorgRepository(uuid=uuid)
|
storage = network_storage.get(uuid)
|
||||||
|
if storage['storage_type'] == 'ssh':
|
||||||
|
return SshBorgRepository(uuid=uuid)
|
||||||
|
else:
|
||||||
|
return BorgRepository(uuid=uuid)
|
||||||
|
|||||||
@ -60,10 +60,14 @@
|
|||||||
|
|
||||||
<h3>{% trans 'Existing Backups' %}</h3>
|
<h3>{% trans 'Existing Backups' %}</h3>
|
||||||
|
|
||||||
{% include "backups_repository.inc" with repository=root_repository uuid='root' editable=False %}
|
{% include "backups_repository.inc" with repository=root_repository uuid='root' storage_type='root' editable=False %}
|
||||||
|
|
||||||
{% for uuid,repository in ssh_repositories.items %}
|
{% for uuid,repository in ssh_repositories.items %}
|
||||||
{% include "backups_repository.inc" with editable=True %}
|
{% include "backups_repository.inc" with editable=True storage_type='ssh' %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for uuid,repository in disk_repositories.items %}
|
||||||
|
{% include "backups_repository.inc" with editable=True storage_type='disk' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<a title="{% trans 'Add a backup location' %}"
|
<a title="{% trans 'Add a backup location' %}"
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
{% if editable %}
|
{% if editable %}
|
||||||
|
|
||||||
|
{% if storage_type != 'disk' %}
|
||||||
{% if repository.mounted %}
|
{% if repository.mounted %}
|
||||||
|
|
||||||
<!-- With GET redirects, the browser URL points to the
|
<!-- With GET redirects, the browser URL points to the
|
||||||
@ -62,6 +63,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<a title="{% trans 'Remove Backup Location. This will not delete the remote backup.' %}"
|
<a title="{% trans 'Remove Backup Location. This will not delete the remote backup.' %}"
|
||||||
role="button" class="repository-remove btn btn-sm btn-default"
|
role="button" class="repository-remove btn btn-sm btn-default"
|
||||||
|
|||||||
@ -43,8 +43,8 @@ from . import (ROOT_REPOSITORY, SESSION_PATH_VARIABLE, api, forms,
|
|||||||
split_path)
|
split_path)
|
||||||
from .decorators import delete_tmp_backup_file
|
from .decorators import delete_tmp_backup_file
|
||||||
from .errors import BorgRepositoryDoesNotExistError
|
from .errors import BorgRepositoryDoesNotExistError
|
||||||
from .repository import (RootBorgRepository, SshBorgRepository, get_repository,
|
from .repository import (BorgRepository, RootBorgRepository, SshBorgRepository,
|
||||||
get_ssh_repositories)
|
create_repository, get_repositories)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -62,7 +62,8 @@ class IndexView(TemplateView):
|
|||||||
context['manual_page'] = backups.manual_page
|
context['manual_page'] = backups.manual_page
|
||||||
root_repository = RootBorgRepository(path=ROOT_REPOSITORY)
|
root_repository = RootBorgRepository(path=ROOT_REPOSITORY)
|
||||||
context['root_repository'] = root_repository.get_view_content()
|
context['root_repository'] = root_repository.get_view_content()
|
||||||
context['ssh_repositories'] = get_ssh_repositories()
|
context['ssh_repositories'] = get_repositories('ssh')
|
||||||
|
context['disk_repositories'] = get_repositories('disk')
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ class CreateArchiveView(SuccessMessageMixin, FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Create the archive on valid form submission."""
|
"""Create the archive on valid form submission."""
|
||||||
repository = get_repository(form.cleaned_data['repository'])
|
repository = create_repository(form.cleaned_data['repository'])
|
||||||
if hasattr(repository, 'mount'):
|
if hasattr(repository, 'mount'):
|
||||||
repository.mount()
|
repository.mount()
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ class DeleteArchiveView(SuccessMessageMixin, TemplateView):
|
|||||||
"""Return additional context for rendering the template."""
|
"""Return additional context for rendering the template."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['title'] = _('Delete Archive')
|
context['title'] = _('Delete Archive')
|
||||||
repository = get_repository(self.kwargs['uuid'])
|
repository = create_repository(self.kwargs['uuid'])
|
||||||
context['archive'] = repository.get_archive(self.kwargs['name'])
|
context['archive'] = repository.get_archive(self.kwargs['name'])
|
||||||
if context['archive'] is None:
|
if context['archive'] is None:
|
||||||
raise Http404
|
raise Http404
|
||||||
@ -109,7 +110,7 @@ class DeleteArchiveView(SuccessMessageMixin, TemplateView):
|
|||||||
|
|
||||||
def post(self, request, uuid, name):
|
def post(self, request, uuid, name):
|
||||||
"""Delete the archive."""
|
"""Delete the archive."""
|
||||||
repository = get_repository(uuid)
|
repository = create_repository(uuid)
|
||||||
repository.delete_archive(name)
|
repository.delete_archive(name)
|
||||||
messages.success(request, _('Archive deleted.'))
|
messages.success(request, _('Archive deleted.'))
|
||||||
return redirect('backups:index')
|
return redirect('backups:index')
|
||||||
@ -218,12 +219,12 @@ class RestoreArchiveView(BaseRestoreView):
|
|||||||
"""Save some data used to instantiate the form."""
|
"""Save some data used to instantiate the form."""
|
||||||
name = unquote(self.kwargs['name'])
|
name = unquote(self.kwargs['name'])
|
||||||
uuid = self.kwargs['uuid']
|
uuid = self.kwargs['uuid']
|
||||||
repository = get_repository(uuid)
|
repository = create_repository(uuid)
|
||||||
return repository.get_archive_apps(name)
|
return repository.get_archive_apps(name)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Restore files from the archive on valid form submission."""
|
"""Restore files from the archive on valid form submission."""
|
||||||
repository = get_repository(self.kwargs['uuid'])
|
repository = create_repository(self.kwargs['uuid'])
|
||||||
selected_apps = form.cleaned_data['selected_apps']
|
selected_apps = form.cleaned_data['selected_apps']
|
||||||
repository.restore_archive(self.kwargs['name'], selected_apps)
|
repository.restore_archive(self.kwargs['name'], selected_apps)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
@ -233,7 +234,7 @@ class DownloadArchiveView(View):
|
|||||||
"""View to export and download an archive as stream."""
|
"""View to export and download an archive as stream."""
|
||||||
|
|
||||||
def get(self, request, uuid, name):
|
def get(self, request, uuid, name):
|
||||||
repository = get_repository(uuid)
|
repository = create_repository(uuid)
|
||||||
filename = f'{name}.tar.gz'
|
filename = f'{name}.tar.gz'
|
||||||
|
|
||||||
response = StreamingHttpResponse(repository.get_download_stream(name),
|
response = StreamingHttpResponse(repository.get_download_stream(name),
|
||||||
@ -257,12 +258,21 @@ class AddRepositoryView(SuccessMessageMixin, FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Create and save a Borg repository."""
|
"""Create and save a Borg repository."""
|
||||||
path = pathlib.Path(
|
path = os.path.join(form.cleaned_data.get('disk'), 'FreedomBoxBackups')
|
||||||
form.cleaned_data.get('disk')) / 'FreedomBoxBackups'
|
encryption = form.cleaned_data.get('encryption')
|
||||||
encryption_passphrase = form.cleaned_data.get('encryption_passphrase')
|
encryption_passphrase = form.cleaned_data.get('encryption_passphrase')
|
||||||
if form.cleaned_data.get('encryption') == 'none':
|
if encryption == 'none':
|
||||||
encryption_passphrase = None
|
encryption_passphrase = None
|
||||||
|
|
||||||
|
credentials = {'encryption_passphrase': encryption_passphrase}
|
||||||
|
repository = BorgRepository(path=path, credentials=credentials)
|
||||||
|
try:
|
||||||
|
repository.get_info()
|
||||||
|
except BorgRepositoryDoesNotExistError:
|
||||||
|
repository.create_repository(encryption)
|
||||||
|
repository.save(store_credentials=True, verified=True)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
class AddRemoteRepositoryView(SuccessMessageMixin, FormView):
|
class AddRemoteRepositoryView(SuccessMessageMixin, FormView):
|
||||||
"""View to create a new remote backup repository."""
|
"""View to create a new remote backup repository."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user