diff --git a/actions/backups b/actions/backups index 3b4159f2e..8135d4188 100755 --- a/actions/backups +++ b/actions/backups @@ -21,7 +21,6 @@ Configuration helper for backups. """ import argparse -import glob import json import os import shutil @@ -60,16 +59,11 @@ def parse_arguments(): export_tar.add_argument('--name', help='Archive name', required=True) export_tar.add_argument('--filename', help='Tarball file name', required=True) - list_exports = subparsers.add_parser( - 'list-exports', help='List exported backup archive files') - list_exports.add_argument('--location', required=True, - help='location to check') - - get_export_apps = subparsers.add_parser( - 'get-export-apps', + get_apps_of_exported_archive = subparsers.add_parser( + 'get-apps-of-exported-archive', help='Get list of apps included in exported archive file') - get_export_apps.add_argument( - '--filename', help='Tarball file name', required=True) + get_apps_of_exported_archive.add_argument( + '--path', help='Tarball file path', required=True) get_archive_apps = subparsers.add_parser( 'get-archive-apps', @@ -80,9 +74,8 @@ def parse_arguments(): restore_exported_archive = subparsers.add_parser( 'restore-exported-archive', help='Restore files from an exported archive') - # TODO: rename filename to filepath (or just path) - restore_exported_archive.add_argument('--filename', - help='Tarball file name', required=True) + restore_exported_archive.add_argument('--path', + help='Tarball file path', required=True) restore_archive = subparsers.add_parser( 'restore-archive', help='Restore files from an archive') @@ -184,21 +177,6 @@ def subcommand_export_tar(arguments): pass -def subcommand_list_exports(arguments): - """List exported backup archive files.""" - exports = [] - path = arguments.location - if path[-1] != '/': - path += '/' - - path += 'FreedomBox-backups/' - if os.path.exists(path): - for filename in glob.glob(path + '*.tar.gz'): - exports.append(os.path.basename(filename)) - - print(json.dumps(exports)) - - def _read_archive_file(archive, filepath): """Read the content of a file inside an archive""" arguments = ['borg', 'extract', archive, filepath, '--stdout'] @@ -225,10 +203,10 @@ def subcommand_get_archive_apps(arguments): print(app['name']) -def subcommand_get_export_apps(arguments): +def subcommand_get_apps_of_exported_archive(arguments): """Get list of apps included in exported archive file.""" manifest = None - with tarfile.open(arguments.filename) as t: + with tarfile.open(arguments.path) as t: filenames = t.getnames() for name in filenames: if 'var/lib/plinth/backups-manifests/' in name \ @@ -257,7 +235,7 @@ def subcommand_restore_exported_archive(arguments): locations_data = ''.join(sys.stdin) locations = json.loads(locations_data) - with tarfile.open(arguments.filename) as tar_handle: + with tarfile.open(arguments.path) as tar_handle: for member in tar_handle.getmembers(): path = '/' + member.name if path in locations['files']: diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py index e90c458d8..a42216e9c 100644 --- a/plinth/modules/backups/__init__.py +++ b/plinth/modules/backups/__init__.py @@ -25,9 +25,7 @@ from django.utils.text import get_valid_filename from django.utils.translation import ugettext_lazy as _ from plinth import actions -from plinth.errors import PlinthError from plinth.menu import main_menu -from plinth.modules import storage from . import api @@ -43,8 +41,6 @@ description = [ service = None -BACKUP_FOLDER_NAME = 'FreedomBox-backups' -DEFAULT_BACKUP_LOCATION = ('/var/lib/freedombox/', _('Root Filesystem')) MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/' REPOSITORY = '/var/lib/freedombox/borgbackup' SESSION_BACKUP_VARIABLE = 'fbx-backup-filestamp' @@ -118,58 +114,9 @@ def delete_tmp_backup_file(): os.remove(TMP_BACKUP_PATH) -def export_archive(name, location, tmp_dir=False): - # TODO: find a better solution for distinguishing exports to /tmp - if tmp_dir: - filepath = TMP_BACKUP_PATH - else: - filename = get_valid_filename(name) + '.tar.gz' - filepath = get_exported_archive_path(location, filename) - # TODO: that's a full path, not a filename; rename argument - actions.superuser_run('backups', - ['export-tar', '--name', name, '--filename', filepath]) - - -def get_export_locations(): - """Return a list of storage locations for exported backup archives.""" - locations = [DEFAULT_BACKUP_LOCATION] - if storage.is_running(): - devices = storage.udisks2.list_devices() - for device in devices: - if 'mount_points' in device and len(device['mount_points']) > 0: - location = { - 'path': device['mount_points'][0], - 'label': device['label'] or device['device'], - 'device': device['device'] - } - locations.append(location) - - return locations - - -def get_location_path(device, locations=None): - """Returns the location path given a disk label""" - if locations is None: - locations = get_export_locations() - - for location in locations: - if location['device'] == device: - return location['path'] - - raise PlinthError('Could not find path of location %s' % device) - - -def get_export_files(): - """Return a dict of exported backup archives found in storage locations.""" - locations = get_export_locations() - export_files = [] - for location in locations: - output = actions.superuser_run( - 'backups', ['list-exports', '--location', location['path']]) - location['files'] = json.loads(output) - export_files.append(location) - - return export_files +def export_archive(name, filepath=TMP_BACKUP_PATH): + arguments = ['export-tar', '--name', name, '--filename', filepath] + actions.superuser_run('backups', arguments) def get_archive_path(archive_name): @@ -177,17 +124,6 @@ def get_archive_path(archive_name): return "::".join([REPOSITORY, archive_name]) -def get_exported_archive_path(location, archive_name): - """Get path of an exported archive""" - return os.path.join(location, BACKUP_FOLDER_NAME, archive_name) - - -def find_exported_archive(device, archive_name): - """Return the full path for the exported archive file.""" - location_path = get_location_path(device) - return get_exported_archive_path(location_path, archive_name) - - def get_archive_apps(path): """Get list of apps included in an archive.""" output = actions.superuser_run('backups', @@ -195,10 +131,10 @@ def get_archive_apps(path): return output.splitlines() -def get_export_apps(filename): +def get_apps_of_exported_archive(path): """Get list of apps included in exported archive file.""" - output = actions.superuser_run('backups', - ['get-export-apps', '--filename', filename]) + arguments = ['get-apps-of-exported-archive', '--path', path] + output = actions.superuser_run('backups', arguments) return output.splitlines() @@ -224,13 +160,6 @@ def restore_from_tmp(apps=None): create_subvolume=False, backup_file=TMP_BACKUP_PATH) -def restore_exported(device, archive_name, apps=None): - """Restore files from exported backup archive.""" - filename = find_exported_archive(device, archive_name) - api.restore_apps(_restore_exported_archive_handler, app_names=apps, - create_subvolume=False, backup_file=filename) - - def restore(archive_path, apps=None): """Restore files from a backup archive.""" api.restore_apps(_restore_archive_handler, app_names=apps, diff --git a/plinth/modules/backups/api.py b/plinth/modules/backups/api.py index 8de9c218e..2ee54d0d9 100644 --- a/plinth/modules/backups/api.py +++ b/plinth/modules/backups/api.py @@ -113,6 +113,7 @@ class Packet: """ self.operation = operation self.scope = scope + # TODO: do we need root if we have the path? self.root = root self.apps = apps # TODO: label is an archive path -- rename diff --git a/plinth/modules/backups/forms.py b/plinth/modules/backups/forms.py index 3df3ec2a0..f64299825 100644 --- a/plinth/modules/backups/forms.py +++ b/plinth/modules/backups/forms.py @@ -18,16 +18,12 @@ Forms for backups module. """ -import os - from django import forms from django.core import validators from django.core.validators import FileExtensionValidator from django.utils.translation import ugettext, ugettext_lazy as _ from . import api -from . import get_export_locations, get_exported_archive_path, \ - get_location_path def _get_app_choices(apps): @@ -63,17 +59,18 @@ class CreateArchiveForm(forms.Form): self.fields['selected_apps'].initial = [app.name for app in apps] -class ExportArchiveForm(forms.Form): - disk = forms.ChoiceField( - label=_('Disk'), widget=forms.RadioSelect(), - help_text=_('Disk or removable storage where the backup archive will ' - 'be saved.')) +class RestoreForm(forms.Form): + selected_apps = forms.MultipleChoiceField( + label=_('Restore apps'), + help_text=_('Apps data to restore from the backup'), + widget=forms.CheckboxSelectMultiple) def __init__(self, *args, **kwargs): - """Initialize the form with disk choices.""" + """Initialize the form with selectable apps.""" + apps = kwargs.pop('apps') super().__init__(*args, **kwargs) - self.fields['disk'].choices = [(location['device'], location['label']) - for location in get_export_locations()] + self.fields['selected_apps'].choices = _get_app_choices(apps) + self.fields['selected_apps'].initial = [app.name for app in apps] class RestoreFromTmpForm(forms.Form): @@ -89,56 +86,6 @@ class RestoreFromTmpForm(forms.Form): self.fields['selected_apps'].initial = [app.name for app in apps] -class RestoreForm(forms.Form): - selected_apps = forms.MultipleChoiceField( - label=_('Restore apps'), - help_text=_('Apps data to restore from the backup'), - widget=forms.CheckboxSelectMultiple) - - def __init__(self, *args, **kwargs): - """Initialize the form with selectable apps.""" - apps = kwargs.pop('apps') - super().__init__(*args, **kwargs) - self.fields['selected_apps'].choices = _get_app_choices(apps) - self.fields['selected_apps'].initial = [app.name for app in apps] - - -class UploadForm(forms.Form): - location = forms.ChoiceField( - choices=(), label=_('Location'), initial='', widget=forms.Select(), - required=True, help_text=_('Location to upload the archive to')) - file = forms.FileField( - label=_('Upload File'), required=True, validators=[ - FileExtensionValidator(['gz'], - 'Backup files have to be in .tar.gz format') - ], help_text=_('Select the backup file you want to upload')) - - def __init__(self, *args, **kwargs): - """Initialize the form with location choices.""" - super().__init__(*args, **kwargs) - locations = get_export_locations() - # users should only be able to select a location name -- don't - # provide paths as a form input for security reasons - location_labels = [(location['device'], location['label']) - for location in locations] - self.fields['location'].choices = location_labels - - def clean(self): - """Check that the uploaded file does not yet exist.""" - cleaned_data = super().clean() - file = cleaned_data.get('file') - location_device = cleaned_data.get('location') - location_path = get_location_path(location_device) - # if other errors occured before, 'file' won't be in cleaned_data - if (file and file.name): - filepath = get_exported_archive_path(location_path, file.name) - if os.path.exists(filepath): - raise forms.ValidationError( - "File %s already exists" % file.name) - else: - self.cleaned_data.update({'filepath': filepath}) - - class UploadToTmpForm(forms.Form): file = forms.FileField(label=_('Upload File'), required=True, validators=[FileExtensionValidator(['gz'], diff --git a/plinth/modules/backups/templates/backups.html b/plinth/modules/backups/templates/backups.html index 9457475a4..7aa10d2da 100644 --- a/plinth/modules/backups/templates/backups.html +++ b/plinth/modules/backups/templates/backups.html @@ -39,31 +39,7 @@
{{ paragraph|safe }}
{% endfor %} -- - {% trans 'New backup' %} - -
- {% else %} -- - {% trans 'New backup' %} - -
-- {% blocktrans trimmed %} - No apps that support backup are currently installed. Backup can be - created after an app supporting backups is installed. - {% endblocktrans %} -
- {% endif %} +{% trans 'No archives currently exist.' %}
@@ -82,12 +58,6 @@{% trans 'No existing backup files were found.' %}
- {% else %} -| {% trans "Location" %} | -{% trans "Name" %} | -- |
|---|---|---|
| {{ export.label }} | -{{ name }} | -- - {% trans "Download" %} - - - {% trans "Restore" %} - - | -
- - {% trans 'Upload backup file' %} - -
- {% endblock %} diff --git a/plinth/modules/backups/templates/backups_upload.html b/plinth/modules/backups/templates/backups_upload.html index 724449538..d2f062f25 100644 --- a/plinth/modules/backups/templates/backups_upload.html +++ b/plinth/modules/backups/templates/backups_upload.html @@ -27,6 +27,12 @@ {% block content %}+ {% blocktrans %} + You can choose the apps you wish to import after uploading a backup file. + {% endblocktrans %} +
+