From 2b2d61baa5f3339898c82a5a5539e0cae4617a12 Mon Sep 17 00:00:00 2001 From: real-yfprojects Date: Fri, 20 Jan 2023 17:01:12 +0100 Subject: [PATCH] Implement borg v2 compatibility for most commands. Adjust available encryptions for borg v2. Use `-r` for all/most commands. Implement `rinfo`. Use `ssh://` style URL as a placeholder. Implement compatibility for `borg extract`. Adjust for beta3, use --match-archives for deletions Co-authored-by: real-yfprojects Co-authored-by: Manu --- src/vorta/assets/UI/repoadd.ui | 543 ++++++++++++++--------------- src/vorta/borg/borg_job.py | 2 +- src/vorta/borg/check.py | 6 +- src/vorta/borg/compact.py | 7 +- src/vorta/borg/create.py | 6 +- src/vorta/borg/delete.py | 9 +- src/vorta/borg/diff.py | 6 +- src/vorta/borg/extract.py | 6 +- src/vorta/borg/info_archive.py | 14 +- src/vorta/borg/info_repo.py | 6 +- src/vorta/borg/init.py | 18 +- src/vorta/borg/list_archive.py | 7 +- src/vorta/borg/list_repo.py | 6 +- src/vorta/borg/mount.py | 6 +- src/vorta/borg/prune.py | 5 +- src/vorta/borg/rename.py | 6 +- src/vorta/views/repo_add_dialog.py | 35 +- 17 files changed, 377 insertions(+), 311 deletions(-) diff --git a/src/vorta/assets/UI/repoadd.ui b/src/vorta/assets/UI/repoadd.ui index be337569..fff7c4b4 100644 --- a/src/vorta/assets/UI/repoadd.ui +++ b/src/vorta/assets/UI/repoadd.ui @@ -1,279 +1,278 @@ - AddRepository - - - - 0 - 0 - 464 - 274 - - - - true - - - - 0 - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 11 - - - - - - - Qt::PlainText - - - false - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - + AddRepository + + + + 0 + 0 + 466 + 274 + + + true - - - - - - - - 0 - 0 - - - - 0 - - - - General - - - - QFormLayout::ExpandingFieldsGrow - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 75 - true - - - - Initialize New Backup Repository - - - - - - - Repository URL: - - - - - - - 0 - - - 0 - - - - - ssh://abc123@abc123.repo.borgbase.com/./repo - - - - - - - Choose a local folder - - - ... - - - - :/icons/folder-open.svg:/icons/folder-open.svg - - - - - - - Choose a remote repository - - - ... - - - - :/icons/globe.svg:/icons/globe.svg - - - - - - - - - Borg passphrase: - - - - - - - true - - - QLineEdit::Password - - - - - - - true - - - QLineEdit::Password - - - - - - - Confirm passphrase: - - - - - - - TextLabel - - - - - - - - Advanced - - - - QFormLayout::ExpandingFieldsGrow - - - 5 - - - 5 - - - 5 - - - 5 - - - - - SSH Key: - - - - - - - - 0 - 0 - - - - - Automatically choose SSH Key (default) + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 20 + + + + + 11 + + + + + + + Qt::PlainText + + + false + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true - - - - - - Encryption: - + + + + + + 0 + 0 + + + + 0 + + + + General + + + + QFormLayout::ExpandingFieldsGrow + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + true + + + + Initialize New Backup Repository + + + + + + + Repository URL: + + + + + + + 0 + + + 0 + + + + + ssh://abc123@abc123.repo.borgbase.com/./repo + + + + + + + Choose a local folder + + + ... + + + + :/icons/folder-open.svg:/icons/folder-open.svg + + + + + + + Choose a remote repository + + + ... + + + + :/icons/globe.svg:/icons/globe.svg + + + + + + + + + Borg passphrase: + + + + + + + true + + + QLineEdit::Password + + + + + + + true + + + QLineEdit::Password + + + + + + + Confirm passphrase: + + + + + + + TextLabel + + + + + + + + Advanced + + + + QFormLayout::ExpandingFieldsGrow + + + 5 + + + 5 + + + 5 + + + 5 + + + + + SSH Key: + + + + + + + + 0 + 0 + + + + + Automatically choose SSH Key (default) + + + + + + + + Encryption: + + + + + + + + 0 + 0 + + + + + + + + Extra Borg Arguments: + + + + + + + + - - - - - - 0 - 0 - - + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + - - - - - Extra Borg Arguments: - - - - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - + + + + + + \ No newline at end of file diff --git a/src/vorta/borg/borg_job.py b/src/vorta/borg/borg_job.py index 7017a4dd..d77a32f4 100644 --- a/src/vorta/borg/borg_job.py +++ b/src/vorta/borg/borg_job.py @@ -286,7 +286,7 @@ class BorgJob(JobInterface, BackupProfileMixin): msg = ( f"{translate('BorgJob','Files')}: {parsed['nfiles']}, " f"{translate('BorgJob','Original')}: {pretty_bytes(parsed['original_size'])}, " - f"{translate('BorgJob','Compressed')}: {pretty_bytes(parsed['compressed_size'])}, " + # f"{translate('BorgJob','Compressed')}: {pretty_bytes(parsed['compressed_size'])}, " f"{translate('BorgJob','Deduplicated')}: {pretty_bytes(parsed['deduplicated_size'])}" # noqa: E501 ) self.app.backup_progress_event.emit(msg) diff --git a/src/vorta/borg/check.py b/src/vorta/borg/check.py index 72d1150c..eb7713a7 100644 --- a/src/vorta/borg/check.py +++ b/src/vorta/borg/check.py @@ -1,4 +1,5 @@ from typing import Any, Dict +from vorta.utils import borg_compat from .borg_job import BorgJob @@ -33,7 +34,10 @@ class BorgCheckJob(BorgJob): ret['ok'] = False # Set back to false, so we can do our own checks here. cmd = ['borg', 'check', '--info', '--log-json', '--progress'] - cmd.append(f'{profile.repo.url}') + if borg_compat.check('V2'): + cmd = cmd + ["-r", profile.repo.url] + else: + cmd.append(f'{profile.repo.url}') ret['ok'] = True ret['cmd'] = cmd diff --git a/src/vorta/borg/compact.py b/src/vorta/borg/compact.py index 6dc18605..4e1a67b0 100644 --- a/src/vorta/borg/compact.py +++ b/src/vorta/borg/compact.py @@ -38,8 +38,11 @@ class BorgCompactJob(BorgJob): ret['message'] = trans_late('messages', 'This feature needs Borg 1.2.0 or higher.') return ret - cmd = ['borg', '--info', '--log-json', 'compact', '--cleanup-commits', '--progress'] - cmd.append(f'{profile.repo.url}') + cmd = ['borg', '--info', '--log-json', 'compact', '--progress'] + if borg_compat.check('V2'): + cmd = cmd + ["-r", profile.repo.url] + else: + cmd.append(f'{profile.repo.url}') ret['ok'] = True ret['cmd'] = cmd diff --git a/src/vorta/borg/create.py b/src/vorta/borg/create.py index f54f70d5..f693412d 100644 --- a/src/vorta/borg/create.py +++ b/src/vorta/borg/create.py @@ -168,7 +168,11 @@ class BorgCreateJob(BorgJob): # Add repo url and source dirs. new_archive_name = format_archive_name(profile, profile.new_archive_name) - cmd.append(f"{profile.repo.url}::{new_archive_name}") + + if borg_compat.check('V2'): + cmd += ["-r", profile.repo.url, new_archive_name] + else: + cmd.append(f"{profile.repo.url}::{new_archive_name}") for f in SourceFileModel.select().where(SourceFileModel.profile == profile.id): cmd.append(f.dir) diff --git a/src/vorta/borg/delete.py b/src/vorta/borg/delete.py index ac640690..873fc704 100644 --- a/src/vorta/borg/delete.py +++ b/src/vorta/borg/delete.py @@ -1,5 +1,6 @@ from typing import List from vorta.store.models import RepoModel +from vorta.utils import borg_compat from .borg_job import BorgJob @@ -33,8 +34,12 @@ class BorgDeleteJob(BorgJob): return ret cmd = ['borg', 'delete', '--info', '--log-json'] - cmd.append(f'{profile.repo.url}::{archives[0]}') - cmd.extend(archives[1:]) + if borg_compat.check('V2'): + cmd = cmd + ["-r", profile.repo.url, '-a'] + cmd.append(f"re:({'|'.join(archives)})") + else: + cmd.append(f'{profile.repo.url}::{archives[0]}') + cmd.extend(archives[1:]) ret['archives'] = archives ret['cmd'] = cmd diff --git a/src/vorta/borg/diff.py b/src/vorta/borg/diff.py index eea94339..f52d4ee7 100644 --- a/src/vorta/borg/diff.py +++ b/src/vorta/borg/diff.py @@ -24,7 +24,11 @@ class BorgDiffJob(BorgJob): ret['cmd'].append('--json-lines') ret['json_lines'] = True - ret['cmd'].extend([f'{profile.repo.url}::{archive_name_1}', f'{archive_name_2}']) + if borg_compat.check('V2'): + ret['cmd'].extend(['-r', profile.repo.url, archive_name_1, archive_name_2]) + else: + ret['cmd'].extend([f'{profile.repo.url}::{archive_name_1}', archive_name_2]) + ret['ok'] = True ret['archive_name_older'] = archive_name_1 ret['archive_name_newer'] = archive_name_2 diff --git a/src/vorta/borg/extract.py b/src/vorta/borg/extract.py index 54c0178b..d952938a 100644 --- a/src/vorta/borg/extract.py +++ b/src/vorta/borg/extract.py @@ -1,5 +1,6 @@ import tempfile from PyQt5.QtCore import QModelIndex, Qt +from vorta.utils import borg_compat from vorta.views.extract_dialog import ExtractTree, FileData from vorta.views.partials.treemodel import FileSystemItem, path_to_str from .borg_job import BorgJob @@ -24,7 +25,10 @@ class BorgExtractJob(BorgJob): ret['ok'] = False # Set back to false, so we can do our own checks here. cmd = ['borg', 'extract', '--list', '--info', '--log-json'] - cmd.append(f'{profile.repo.url}::{archive_name}') + if borg_compat.check('V2'): + cmd += ['-r', profile.repo.url, archive_name] + else: + cmd.append(f'{profile.repo.url}::{archive_name}') # process selected items # all items will be excluded beside the one actively selected in the diff --git a/src/vorta/borg/info_archive.py b/src/vorta/borg/info_archive.py index b25a698d..abe1f163 100644 --- a/src/vorta/borg/info_archive.py +++ b/src/vorta/borg/info_archive.py @@ -1,4 +1,5 @@ from vorta.store.models import ArchiveModel, RepoModel +from vorta.utils import borg_compat from .borg_job import BorgJob @@ -19,13 +20,11 @@ class BorgInfoArchiveJob(BorgJob): return ret ret['ok'] = True - ret['cmd'] = [ - 'borg', - 'info', - '--log-json', - '--json', - f'{profile.repo.url}::{archive_name}', - ] + ret['cmd'] = ['borg', 'info', '--log-json', '--json'] + if borg_compat.check('V2'): + ret['cmd'].extend(["-r", profile.repo.url, '-a', archive_name]) + else: + ret['cmd'].append(f'{profile.repo.url}::{archive_name}') ret['archive_name'] = archive_name return ret @@ -52,7 +51,6 @@ class BorgInfoArchiveJob(BorgJob): stats = result['data']['cache']['stats'] repo = RepoModel.get(id=result['params']['repo_id']) repo.total_size = stats['total_size'] - repo.unique_csize = stats['unique_csize'] repo.unique_size = stats['unique_size'] repo.total_unique_chunks = stats['total_unique_chunks'] repo.save() diff --git a/src/vorta/borg/info_repo.py b/src/vorta/borg/info_repo.py index 302ebbcb..5970d194 100644 --- a/src/vorta/borg/info_repo.py +++ b/src/vorta/borg/info_repo.py @@ -1,5 +1,6 @@ from vorta.i18n import trans_late from vorta.store.models import RepoModel +from vorta.utils import borg_compat from .borg_job import BorgJob, FakeProfile, FakeRepo @@ -27,7 +28,10 @@ class BorgInfoRepoJob(BorgJob): else: ret['ok'] = False # Set back to false, so we can do our own checks here. - cmd = ["borg", "info", "--info", "--json", "--log-json"] + if borg_compat.check('V2'): + cmd = ["borg", "rinfo", "--info", "--json", "--log-json", "-r"] + else: + cmd = ["borg", "info", "--info", "--json", "--log-json"] cmd.append(profile.repo.url) ret['additional_env'] = { diff --git a/src/vorta/borg/init.py b/src/vorta/borg/init.py index 8f7c4a3a..b26b4b7d 100644 --- a/src/vorta/borg/init.py +++ b/src/vorta/borg/init.py @@ -1,4 +1,5 @@ from vorta.store.models import RepoModel +from vorta.utils import borg_compat from .borg_job import BorgJob, FakeProfile, FakeRepo @@ -28,9 +29,20 @@ class BorgInitJob(BorgJob): else: ret['ok'] = False # Set back to false, so we can do our own checks here. - cmd = ["borg", "init", "--info", "--log-json"] - cmd.append(f"--encryption={params['encryption']}") - cmd.append(params['repo_url']) + if borg_compat.check('V2'): + cmd = [ + "borg", + "rcreate", + "--info", + "--log-json", + f"--encryption={params['encryption']}", + "-r", + params['repo_url'], + ] + else: + cmd = ["borg", "init", "--info", "--log-json"] + cmd.append(f"--encryption={params['encryption']}") + cmd.append(params['repo_url']) ret['additional_env'] = {'BORG_RSH': 'ssh -oStrictHostKeyChecking=accept-new'} diff --git a/src/vorta/borg/list_archive.py b/src/vorta/borg/list_archive.py index bcbfb18b..eb3ccf58 100644 --- a/src/vorta/borg/list_archive.py +++ b/src/vorta/borg/list_archive.py @@ -30,8 +30,13 @@ class BorgListArchiveJob(BorgJob): "{mode}{user}{group}{size}{" + ('isomtime' if borg_compat.check('V122') else 'mtime') + "}{path}{source}{health}{NL}", - f'{profile.repo.url}::{archive_name}', ] + + if borg_compat.check('V2'): + ret['cmd'].extend(["-r", profile.repo.url, archive_name]) + else: + ret['cmd'].append(f'{profile.repo.url}::{archive_name}') + ret['ok'] = True return ret diff --git a/src/vorta/borg/list_repo.py b/src/vorta/borg/list_repo.py index 424ae680..7b60a8c6 100644 --- a/src/vorta/borg/list_repo.py +++ b/src/vorta/borg/list_repo.py @@ -1,5 +1,6 @@ from datetime import datetime as dt from vorta.store.models import ArchiveModel, RepoModel +from vorta.utils import borg_compat from .borg_job import BorgJob @@ -21,7 +22,10 @@ class BorgListRepoJob(BorgJob): else: ret['ok'] = False # Set back to false, so we can do our own checks here. - cmd = ['borg', 'list', '--info', '--log-json', '--json'] + if borg_compat.check('V2'): + cmd = ['borg', 'rlist', '--info', '--log-json', '--json', '-r'] + else: + cmd = ['borg', 'list', '--info', '--log-json', '--json'] cmd.append(f'{profile.repo.url}') ret['ok'] = True diff --git a/src/vorta/borg/mount.py b/src/vorta/borg/mount.py index 927b6b7b..cac9ef38 100644 --- a/src/vorta/borg/mount.py +++ b/src/vorta/borg/mount.py @@ -1,5 +1,6 @@ import os from vorta.store.models import SettingsModel +from vorta.utils import borg_compat from .borg_job import BorgJob @@ -23,7 +24,10 @@ class BorgMountJob(BorgJob): if override_mount_permissions: cmd += ['-o', f"umask=0277,uid={os.getuid()}"] - cmd += [f"{profile.repo.url}"] + if borg_compat.check('V2'): + cmd.extend(["-r", profile.repo.url]) + else: + cmd.append(f'{profile.repo.url}') ret['ok'] = True ret['cmd'] = cmd diff --git a/src/vorta/borg/prune.py b/src/vorta/borg/prune.py index bf86a5cb..94b87836 100644 --- a/src/vorta/borg/prune.py +++ b/src/vorta/borg/prune.py @@ -55,7 +55,10 @@ class BorgPruneJob(BorgJob): if profile.prune_keep_within: pruning_opts += ['--keep-within', profile.prune_keep_within] cmd += pruning_opts - cmd.append(f'{profile.repo.url}') + if borg_compat.check('V2'): + cmd.extend(["-r", profile.repo.url]) + else: + cmd.append(f'{profile.repo.url}') ret['ok'] = True ret['cmd'] = cmd diff --git a/src/vorta/borg/rename.py b/src/vorta/borg/rename.py index af07aa9f..c6ec787a 100644 --- a/src/vorta/borg/rename.py +++ b/src/vorta/borg/rename.py @@ -1,4 +1,5 @@ from vorta.store.models import ArchiveModel, RepoModel +from vorta.utils import borg_compat from .borg_job import BorgJob @@ -15,7 +16,10 @@ class BorgRenameJob(BorgJob): ret['ok'] = False # Set back to false, so we can do our own checks here. cmd = ['borg', 'rename', '--info', '--log-json'] - cmd.append(f'{profile.repo.url}') + if borg_compat.check('V2'): + cmd.extend(["-r", profile.repo.url]) + else: + cmd.append(f'{profile.repo.url}') ret['ok'] = True ret['cmd'] = cmd diff --git a/src/vorta/views/repo_add_dialog.py b/src/vorta/views/repo_add_dialog.py index 73a5e033..dbb88a29 100644 --- a/src/vorta/views/repo_add_dialog.py +++ b/src/vorta/views/repo_add_dialog.py @@ -148,19 +148,28 @@ class AddRepoWindow(AddRepoBase, AddRepoUI): self._set_status(self.tr('Unable to add your repository.')) def init_encryption(self): - encryption_algos = [ - [ - self.tr('Repokey-Blake2 (Recommended, key stored in repository)'), - 'repokey-blake2', - ], - [self.tr('Repokey'), 'repokey'], - [ - self.tr('Keyfile-Blake2 (Key stored in home directory)'), - 'keyfile-blake2', - ], - [self.tr('Keyfile'), 'keyfile'], - [self.tr('None (not recommended)'), 'none'], - ] + if borg_compat.check('V2'): + encryption_algos = [ + [ + self.tr('Repokey-ChaCha20-Poly1305 (Recommended, key stored in repository)'), + 'repokey-blake2-chacha20-poly1305', + ], + [ + self.tr('Keyfile-ChaCha20-Poly1305 (Key stored in home directory)'), + 'keyfile-blake2-chacha20-poly1305', + ], + [self.tr('Repokey-AES256-OCB'), 'repokey-blake2-aes-ocb'], + [self.tr('Keyfile-AES256-OCB'), 'keyfile-blake2-aes-ocb'], + [self.tr('None (not recommended)'), 'none'], + ] + else: + encryption_algos = [ + [self.tr('Repokey-Blake2 (Recommended, key stored in repository)'), 'repokey-blake2'], + [self.tr('Repokey'), 'repokey'], + [self.tr('Keyfile-Blake2 (Key stored in home directory)'), 'keyfile-blake2'], + [self.tr('Keyfile'), 'keyfile'], + [self.tr('None (not recommended)'), 'none'], + ] for desc, name in encryption_algos: self.encryptionComboBox.addItem(desc, name)