def prepare(cls, profile): """ Prepare for running Borg. This function in the base class should be called from all subclasses and calls that define their own `cmd`. The `prepare()` step does these things: - validate if all conditions to run command are met - build borg command `prepare()` is run 2x. First at the global level and then for each subcommand. :return: dict(ok: book, message: str) """ ret = {'ok': False} # Do checks to see if running Borg is possible. if cls.is_running(): ret['message'] = trans_late('messages', 'Backup is already in progress.') return ret if cls.prepare_bin() is None: ret['message'] = trans_late('messages', 'Borg binary was not found.') return ret if profile.repo is None: ret['message'] = trans_late('messages', 'Add a backup repository first.') return ret if not borg_compat.check('JSON_LOG'): ret['message'] = trans_late('messages', 'Your Borg version is too old. >=1.1.0 is required.') return ret # Try to get password from chosen keyring backend. keyring = get_keyring() logger.debug("Using %s keyring to store passwords.", keyring.__class__.__name__) ret['password'] = keyring.get_password('vorta-repo', profile.repo.url) # Try to fall back to DB Keyring, if we use the system keychain. if ret['password'] is None and keyring.is_primary: logger.debug('Password not found in primary keyring. Falling back to VortaDBKeyring.') ret['password'] = VortaDBKeyring().get_password('vorta-repo', profile.repo.url) # Give warning and continue if password is found there. if ret['password'] is not None: logger.warning('Found password in database, but secure storage was available. ' 'Consider re-adding the repo to use it.') ret['ssh_key'] = profile.ssh_key ret['repo_id'] = profile.repo.id ret['repo_url'] = profile.repo.url ret['extra_borg_arguments'] = profile.repo.extra_borg_arguments ret['profile_name'] = profile.name ret['ok'] = True return ret
def init_encryption(self): encryption_algos = [ ['Repokey-Blake2 (Recommended, key stored in repository)', 'repokey-blake2'], ['Repokey', 'repokey'], ['Keyfile-Blake2 (Key stored in home directory)', 'keyfile-blake2'], ['Keyfile', 'keyfile'], ['None (not recommended)', 'none'] ] for desc, name in encryption_algos: self.encryptionComboBox.addItem(self.tr(desc), name) if not borg_compat.check('BLAKE2'): self.encryptionComboBox.model().item(0).setEnabled(False) self.encryptionComboBox.model().item(2).setEnabled(False) self.encryptionComboBox.setCurrentIndex(1)
def prepare(cls, profile, archive_name_1, archive_name_2): ret = super().prepare(profile) if not ret['ok']: return ret ret['cmd'] = ['borg', 'diff', '--info', '--log-json'] ret['json_lines'] = False if borg_compat.check('DIFF_JSON_LINES'): ret['cmd'].append('--json-lines') ret['json_lines'] = True ret['cmd'].extend( [f'{profile.repo.url}::{archive_name_1}', f'{archive_name_2}']) ret['ok'] = True ret['archive_name_older'] = archive_name_1 ret['archive_name_newer'] = archive_name_2 return ret
def prepare(cls, profile): ret = super().prepare(profile) if not ret['ok']: return ret else: ret['ok'] = False # Set back to false, so we can do our own checks here. if not borg_compat.check('COMPACT_SUBCOMMAND'): ret['ok'] = False ret['message'] = trans_late( 'messages', 'This feature needs Borg 1.2.0 or higher.') return ret cmd = ['borg', '--info', '--log-json', 'compact', '--cleanup-commits'] cmd.append(f'{profile.repo.url}') ret['ok'] = True ret['cmd'] = cmd return ret
def toggle_available_compression(self): use_zstd = borg_compat.check('ZSTD') for algo in ['zstd,3', 'zstd,8']: ix = self.repoCompression.findData(algo) self.repoCompression.model().item(ix).setEnabled(use_zstd)
def prepare(cls, profile): """ Prepare for running Borg. This function in the base class should be called from all subclasses and calls that define their own `cmd`. The `prepare()` step does these things: - validate if all conditions to run command are met - build borg command `prepare()` is run 2x. First at the global level and then for each subcommand. :return: dict(ok: book, message: str) """ ret = {'ok': False} if cls.prepare_bin() is None: ret['message'] = trans_late('messages', 'Borg binary was not found.') return ret if profile.repo is None: ret['message'] = trans_late('messages', 'Add a backup repository first.') return ret if not borg_compat.check('JSON_LOG'): ret['message'] = trans_late('messages', 'Your Borg version is too old. >=1.1.0 is required.') return ret # Try to get password from chosen keyring backend. with keyring_lock: cls.keyring = VortaKeyring.get_keyring() logger.debug("Using %s keyring to store passwords.", cls.keyring.__class__.__name__) ret['password'] = cls.keyring.get_password('vorta-repo', profile.repo.url) # Check if keyring is locked if profile.repo.encryption != 'none' and not cls.keyring.is_unlocked: ret['message'] = trans_late('messages', 'Please unlock your system password manager or disable it under Misc') return ret # Try to fall back to DB Keyring, if we use the system keychain. if ret['password'] is None and cls.keyring.is_system: logger.debug('Password not found in primary keyring. Falling back to VortaDBKeyring.') ret['password'] = VortaDBKeyring().get_password('vorta-repo', profile.repo.url) # Give warning and continue if password is found there. if ret['password'] is not None: logger.warning('Found password in database, but secure storage was available. ' 'Consider re-adding the repo to use it.') # Password is required for encryption, cannot continue if ret['password'] is None and not isinstance(profile.repo, FakeRepo) and profile.repo.encryption != 'none': ret['message'] = trans_late( 'messages', "Your repo passphrase was stored in a password manager which is no longer available.\n" "Try unlinking and re-adding your repo.") return ret ret['ssh_key'] = profile.ssh_key ret['repo_id'] = profile.repo.id ret['repo_url'] = profile.repo.url ret['extra_borg_arguments'] = profile.repo.extra_borg_arguments ret['profile_name'] = profile.name ret['profile_id'] = profile.id ret['ok'] = True return ret
def prepare(cls, profile): """ `borg create` is called from different places and needs some preparation. Centralize it here and return the required arguments to the caller. """ ret = super().prepare(profile) if not ret['ok']: return ret else: ret['ok'] = False # Set back to False, so we can do our own checks here. n_backup_folders = SourceFileModel.select().count() if n_backup_folders == 0: ret['message'] = trans_late('messages', 'Add some folders to back up first.') return ret network_status_monitor = get_network_status_monitor() current_wifi = network_status_monitor.get_current_wifi() if current_wifi is not None: wifi_is_disallowed = WifiSettingModel.select().where( (WifiSettingModel.ssid == current_wifi) & ( WifiSettingModel.allowed == False # noqa ) & (WifiSettingModel.profile == profile)) if wifi_is_disallowed.count() > 0 and profile.repo.is_remote_repo( ): ret['message'] = trans_late('messages', 'Current Wifi is not allowed.') return ret if profile.repo.is_remote_repo() and profile.dont_run_on_metered_networks \ and network_status_monitor.is_network_metered(): ret['message'] = trans_late( 'messages', 'Not running backup over metered connection.') return ret ret['profile'] = profile ret['repo'] = profile.repo # Run user-supplied pre-backup command if cls.pre_post_backup_cmd(ret) != 0: ret['message'] = trans_late( 'messages', 'Pre-backup command returned non-zero exit code.') return ret if not profile.repo.is_remote_repo() and not os.path.exists( profile.repo.url): ret['message'] = trans_late('messages', 'Repo folder not mounted or moved.') return ret if 'zstd' in profile.compression and not borg_compat.check('ZSTD'): ret['message'] = trans_late( 'messages', 'Your current Borg version does not support ZStd compression.') return ret cmd = [ 'borg', 'create', '--list', '--progress', '--info', '--log-json', '--json', '--filter=AM', '-C', profile.compression, ] # Add excludes # Partly inspired by borgmatic/borgmatic/borg/create.py if profile.exclude_patterns is not None: exclude_dirs = [] for p in profile.exclude_patterns.split('\n'): if p.strip(): expanded_directory = os.path.expanduser(p.strip()) exclude_dirs.append(expanded_directory) if exclude_dirs: pattern_file = tempfile.NamedTemporaryFile('w', delete=False) pattern_file.write('\n'.join(exclude_dirs)) pattern_file.flush() cmd.extend(['--exclude-from', pattern_file.name]) if profile.exclude_if_present is not None: for f in profile.exclude_if_present.split('\n'): if f.strip(): cmd.extend(['--exclude-if-present', f.strip()]) # 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}") for f in SourceFileModel.select().where( SourceFileModel.profile == profile.id): cmd.append(f.dir) ret['message'] = trans_late('messages', 'Starting backup...') ret['ok'] = True ret['cmd'] = cmd return ret