diff --git a/plinth/modules/backups/forms.py b/plinth/modules/backups/forms.py
index 245120711..6d61aa85e 100644
--- a/plinth/modules/backups/forms.py
+++ b/plinth/modules/backups/forms.py
@@ -23,6 +23,9 @@ from django.core import validators
from django.core.validators import FileExtensionValidator
from django.utils.translation import ugettext, ugettext_lazy as _
+from plinth.utils import format_lazy
+from plinth import cfg
+
from . import api
@@ -77,3 +80,43 @@ class UploadForm(forms.Form):
validators=[FileExtensionValidator(['gz'],
'Backup files have to be in .tar.gz format')],
help_text=_('Select the backup file you want to upload'))
+
+
+class CreateRepositoryForm(forms.Form):
+ repository = forms.CharField(
+ label=_('Repository path'), strip=True,
+ help_text=_('Path of the new repository.'))
+ encryption = forms.ChoiceField(
+ label=_('Encryption'),
+ help_text=format_lazy(_('"Key in Repository" means that a '
+ 'password-protected key is stored with the backup.
'
+ 'You need this password to restore a backup!')),
+ choices=[('repokey', 'Key in Repository'), ('none', 'None')]
+ )
+ passphrase = forms.CharField(
+ label=_('Passphrase'),
+ help_text=_('Passphrase; Only needed when using encryption.'),
+ widget=forms.PasswordInput()
+ )
+ confirm_passphrase = forms.CharField(
+ label=_('Confirm Passphrase'),
+ help_text=_('Repeat the passphrase.'),
+ widget=forms.PasswordInput()
+ )
+ store_passphrase = forms.BooleanField(
+ label=_('Store passphrase on FreedomBox'),
+ help_text=format_lazy(_('Store the passphrase on your {box_name}.'
+ '
You need to store the passphrase if you want to run '
+ 'recurrent backups.'), box_name=_(cfg.box_name)),
+ required=False
+ )
+
+ def clean(self):
+ cleaned_data = super(CreateRepositoryForm, self).clean()
+ passphrase = cleaned_data.get("passphrase")
+ confirm_passphrase = cleaned_data.get("confirm_passphrase")
+
+ if passphrase != confirm_passphrase:
+ raise forms.ValidationError(
+ "passphrase and confirm_passphrase do not match"
+ )
diff --git a/plinth/modules/backups/templates/backups_repositories.html b/plinth/modules/backups/templates/backups_repositories.html
new file mode 100644
index 000000000..b79d6d1dd
--- /dev/null
+++ b/plinth/modules/backups/templates/backups_repositories.html
@@ -0,0 +1,39 @@
+{% extends "base.html" %}
+{% comment %}
+#
+# This file is part of FreedomBox.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+{% endcomment %}
+
+{% load i18n %}
+
+{% block content %}
+
+
{% trans 'Backup repositories' %}
+
+
+ {% for repository in repositories %}
+ Repository: {{ repository.repository }}
+ {% endfor %}
+
+
+
+ {% trans 'Add Repository' %}
+
+
+{% endblock %}
diff --git a/plinth/modules/backups/urls.py b/plinth/modules/backups/urls.py
index de6ec073d..7e0090c03 100644
--- a/plinth/modules/backups/urls.py
+++ b/plinth/modules/backups/urls.py
@@ -20,9 +20,9 @@ URLs for the backups module.
from django.conf.urls import url
-from .views import IndexView, CreateArchiveView, DeleteArchiveView, \
- UploadArchiveView, ExportAndDownloadView, RestoreArchiveView, \
- RestoreFromUploadView
+from .views import IndexView, CreateArchiveView, CreateRepositoryView, \
+ DeleteArchiveView, UploadArchiveView, ExportAndDownloadView, \
+ RepositoriesView, RestoreArchiveView, RestoreFromUploadView
urlpatterns = [
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
@@ -36,4 +36,8 @@ urlpatterns = [
RestoreArchiveView.as_view(), name='restore-archive'),
url(r'^sys/backups/restore-from-upload/$',
RestoreFromUploadView.as_view(), name='restore-from-upload'),
+ url(r'^sys/backups/repositories/$',
+ RepositoriesView.as_view(), name='repositories'),
+ url(r'^sys/backups/repositories/create/$',
+ CreateRepositoryView.as_view(), name='create-repository'),
]
diff --git a/plinth/modules/backups/views.py b/plinth/modules/backups/views.py
index 29ddcd661..4ecfa2ff5 100644
--- a/plinth/modules/backups/views.py
+++ b/plinth/modules/backups/views.py
@@ -20,11 +20,13 @@ Views for the backups app.
from datetime import datetime
import gzip
+import json
from io import BytesIO
import logging
import mimetypes
import os
import tempfile
+from uuid import uuid1
from urllib.parse import unquote
from django.contrib import messages
@@ -37,7 +39,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.views.generic import View, FormView, TemplateView
-from plinth import actions
+from plinth import actions, kvstore
from plinth.errors import PlinthError
from plinth.modules import backups, storage
@@ -51,10 +53,13 @@ subsubmenu = [{
'text': ugettext_lazy('Backups')
}, {
'url': reverse_lazy('backups:upload'),
- 'text': ugettext_lazy('Upload backup')
+ 'text': ugettext_lazy('Upload')
}, {
'url': reverse_lazy('backups:create'),
- 'text': ugettext_lazy('Create backup')
+ 'text': ugettext_lazy('Create')
+}, {
+ 'url': reverse_lazy('backups:repositories'),
+ 'text': ugettext_lazy('Repositories')
}]
@@ -284,3 +289,49 @@ class ExportAndDownloadView(View):
response['Content-Disposition'] = 'attachment; filename="%s"' % \
filename
return response
+
+
+class RepositoriesView(TemplateView):
+ """View list of repositories."""
+ template_name = 'backups_repositories.html'
+
+ def get_context_data(self, **kwargs):
+ """Return additional context for rendering the template."""
+ context = super().get_context_data(**kwargs)
+ context['title'] = 'Backup repositories'
+ repositories = kvstore.get_default('backups_repositories', [])
+ context['repositories'] = json.loads(repositories)
+ context['subsubmenu'] = subsubmenu
+ return context
+
+
+class CreateRepositoryView(SuccessMessageMixin, FormView):
+ """View to create a new repository."""
+ form_class = forms.CreateRepositoryForm
+ prefix = 'backups'
+ template_name = 'backups_form.html'
+ success_url = reverse_lazy('backups:repositories')
+ success_message = _('Created new repository.')
+
+ def get_context_data(self, **kwargs):
+ """Return additional context for rendering the template."""
+ context = super().get_context_data(**kwargs)
+ context['title'] = _('Create new repository')
+ context['subsubmenu'] = subsubmenu
+ return context
+
+ def form_valid(self, form):
+ """Restore files from the archive on valid form submission."""
+ repositories = kvstore.get_default('backups_repositories', [])
+ if repositories:
+ repositories = json.loads(repositories)
+ new_repo = {
+ 'uuid': str(uuid1()),
+ 'repository': form.cleaned_data['repository'],
+ 'encryption': form.cleaned_data['encryption'],
+ }
+ if form.cleaned_data['store_passphrase']:
+ new_repo['passphrase'] = form.cleaned_data['passphrase']
+ repositories.append(new_repo)
+ kvstore.set('backups_repositories', json.dumps(repositories))
+ return super().form_valid(form)