Пример #1
0
    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
Пример #2
0
    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)
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
 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)
Пример #6
0
    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
Пример #7
0
    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