Exemple #1
0
    def run_borg(self, cmd, args='', borg_opts=None, emborg_opts=()):

        # prepare the command
        os.environ.update(self.publish_passcode())
        os.environ['BORG_DISPLAY_PASSPHRASE'] = 'no'
        if self.ssh_command:
            os.environ['BORG_RSH'] = self.ssh_command
        executable = self.value('borg_executable', BORG)
        if borg_opts is None:
            borg_opts = self.borg_options(cmd, emborg_opts)
        command = ([executable] + cmd.split() + borg_opts +
                   (args.split() if is_str(args) else args))
        environ = {
            k: v
            for k, v in os.environ.items() if k.startswith('BORG_')
        }
        if 'BORG_PASSPHRASE' in environ:
            environ['BORG_PASSPHRASE'] = '<redacted>'
        narrate('setting environment variables:', render(environ))

        # check if ssh agent is present
        if self.needs_ssh_agent:
            for ssh_var in 'SSH_AGENT_PID SSH_AUTH_SOCK'.split():
                if ssh_var not in os.environ:
                    warn(
                        'environment variable not found, is ssh-agent running?',
                        culprit=ssh_var)

        # run the command
        narrate('running:\n{}'.format(
            indent(render_command(command, borg_options_arg_count))))
        narrating = 'verbose' in emborg_opts or 'narrate' in emborg_opts
        modes = 'soeW' if narrating else 'sOEW'
        return Run(command, modes=modes, stdin='', env=os.environ, log=False)
Exemple #2
0
    def __new__(cls, server, include_file, bypass, trial_run):
        if server in AuthKeys.known:
            self = AuthKeys.known[server]
            if include_file != self.include_file:
                warn(
                    'inconsistent remote include file:',
                    fmt('{include_file} != {self.include_file} in {server}.')
                )
            return self
        self = super(AuthKeys, cls).__new__(cls)
        AuthKeys.known[server] = self
        self.server = server
        self.bypass = bypass
        self.trial_run = trial_run
        self.keys = {}
        self.comment = {}
        self.restrictions = {}
        self.include_file = include_file
        self.include = None

        # get remote include file if it exists
        if include_file and not bypass:
            narrate(fmt('    retrieving remote include file from {server}.'))
            try:
                try:
                    run_sftp(self.server, [
                        fmt('get .ssh/{inc} {inc}.{server}', inc=include_file)
                    ])
                    self.include = to_path(include_file + '.' + server).read_text()
                except OSError as err:
                    comment(fmt('    sftp {server}: {include_file} not found.'))
            except OSError as err:
                error(os_error(err))

        return self
Exemple #3
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        fast = cmdline['--fast']

        # report local information
        src_dirs = (str(d) for d in settings.src_dirs)
        output(f'              config: {settings.config_name}')
        output(f'              source: {", ".join(src_dirs)}')
        output(f'         destination: {settings.destination()}')
        output(f'  settings directory: {settings.config_dir}')
        output(f'              logile: {settings.logfile}')
        try:
            backup_date = arrow.get(settings.date_file.read_text())
            output(
                f'      last backed up: {backup_date}, {backup_date.humanize()}'
            )
        except FileNotFoundError as e:
            narrate(os_error(e))
        except arrow.parser.ParserError as e:
            narrate(e, culprit=settings.date_file)
        if fast:
            return

        # now output the information from borg about the repository
        borg = settings.run_borg(
            cmd='info',
            args=[settings.destination()],
            emborg_opts=options,
            strip_prefix=True,
        )
        out = borg.stdout
        if out:
            output()
            output(out.rstrip())
Exemple #4
0
    def run(self):
        self.ActivePythonFile = self.path
        path = self.path
        narrate("reading:", path)
        try:
            self.code = self.read()
            # need to save the code for the new command
        except OSError as err:
            raise Error(os_error(err))

        try:
            compiled = compile(self.code, str(path), "exec")
        except SyntaxError as err:
            culprit = (err.filename, err.lineno)
            if err.text is None or err.offset is None:
                raise Error(full_stop(err.msg), culprit=culprit)
            else:
                raise Error(
                    err.msg + ":",
                    err.text.rstrip(),
                    (err.offset - 1) * " " + "^",
                    culprit=culprit,
                    sep="\n",
                )

        contents = {}
        try:
            exec(compiled, contents)
        except Exception as err:
            from .utilities import error_source

            raise Error(full_stop(err), culprit=error_source())
        self.ActivePythonFile = None
        # strip out keys that start with '__' and return them
        return {k: v for k, v in contents.items() if not k.startswith("__")}
Exemple #5
0
    def publish_private_key(self):
        keyname = self.keyname
        data = self.data
        clients = self.data.get('clients', [])
        prov = '.provisional' if self.trial_run else ''

        # copy key pair to remote client
        for client in sorted(clients):
            if self.update and client not in self.update:
                continue
            if client in self.skip:
                continue
            narrate('    publishing key pair to', client)
            client_data = clients[client]

            # delete any pre-existing provisional files
            # the goal here is to leave a clean directory when not trial-run
            try:
                run_sftp(client, [
                    fmt('rm .ssh/{keyname}.provisional'),
                    fmt('rm .ssh/{keyname}.pub.provisional'),
                ])
            except OSError as err:
                pass

            # now upload the new files
            try:
                run_sftp(client, [
                    fmt('put -p {keyname} .ssh/{keyname}{prov}'),
                    fmt('put -p {keyname}.pub .ssh/{keyname}.pub{prov}'),
                ])
            except OSError as err:
                error(os_error(err))
Exemple #6
0
    def publish_passcode(self):
        passcommand = self.passcommand
        passcode = self.passphrase

        # process passcomand
        if passcommand:
            if passcode:
                warn('passphrase unneeded.', culprit='passcommand')
                return dict(BORG_PASSCOMMAND=passcommand)

        # get passphrase from avendesora
        if not passcode and self.avendesora_account:
            narrate('running avendesora to access passphrase.')
            try:
                from avendesora import PasswordGenerator
                pw = PasswordGenerator()
                account = pw.get_account(self.value('avendesora_account'))
                field = self.value('avendesora_field', None)
                passcode = str(account.get_value(field))
            except ImportError:
                raise Error('Avendesora is not available',
                            'you must specify passphrase in settings.',
                            sep=', ')

        if passcode:
            return dict(BORG_PASSPHRASE=passcode)

        if self.encryption is None:
            self.encryption = 'none'
        if self.encryption == 'none':
            narrate('passphrase is not available, encryption disabled.')
            return {}
        raise Error('Cannot determine the encryption passphrase.')
Exemple #7
0
def encrypt_archive(config, workspace, passcode):
    narrate("Encrypting the archive")

    with cd(workspace):
        run('tar -cf archive.tgz archive', 'soEW')
        with open('archive.tgz', 'rb') as f:
            cleartext = f.read()

        gpg = GPG()
        encrypted = gpg.encrypt(
            cleartext,
            recipients=None,
            symmetric=True,
            passphrase=str(passcode),
        )
        if encrypted.ok:
            with open('archive.tgz.gpg', 'w') as f:
                f.write(str(encrypted))
        else:
            raise EncryptionFailed(encrypted)

        rm('archive.tgz', 'archive')

    script = workspace / 'decrypt.sh'
    script.write_text('''\
#!/bin/sh
# Decrypts the archive.

gpg -d -o - archive.tgz.gpg | tar xvf -
''')
    chmod(0o700, script, workspace / 'archive.tgz.gpg')
    narrate(f"Local archive '{workspace.name}' created.")
Exemple #8
0
def build_archive(config, interactive=True):
    narrate("Building the archive")

    plugins = select_plugins(config, 'archive')
    if not plugins:
        raise ConfigError(f"'plugins.archive' not specified, nothing to do.")

    # Make the archive directory:
    name = config.get('archive_name', '{host}').format(**PARAMS)
    workspace = to_path(appdirs.user_data_dir(__slug__), name)
    archive = to_path(workspace / 'archive')

    rm(workspace)
    mkdir(archive)

    # Apply any 'archive' plugins:
    for plugin in plugins:
        subconfigs = config.get('archive', {}).get(plugin.name, [])
        run_plugin(plugin, config, subconfigs, archive)

    # Show the user which files were included in the archive.
    display("The following files were included in the archive:")

    for root, _, files in os.walk(archive):
        root = to_path(root).relative_to(archive)
        for file in files:
            display('   ', root / file)
    display()

    if interactive:
        input("Is this correct? <Enter> to continue, <Ctrl-C> to cancel: ")

    return workspace
Exemple #9
0
    def verify(self):
        if self.bypass:
            narrate('skipping post update connection test for', self.server)
            return

        comment('%s: post update connection test.' % (self.server))
        test_access(self.server)
Exemple #10
0
    def create(self, contents, gpg_ids=None):
        path = self.path
        try:
            # check to see if file already exists
            if path.exists():
                # file creation (init) requested, but file already exists
                # don't overwrite the file, instead read it so the information
                # can be used to create any remaining files.
                display("%s: already exists." % path)
                return

            # create the file
            display('%s: creating.' % path)
            if path.suffix in ['.gpg', '.asc']:
                narrate('encrypting.', culprit=path)
                # encrypt it
                if not gpg_ids:
                    raise PasswordError('gpg_ids missing.')
                self.save(contents, gpg_ids)
            else:
                narrate('not encrypting.', culprit=path)
                # file is not encrypted
                with path.open('wb') as f:
                    f.write(contents.encode(get_setting('encoding')))
        except OSError as e:
            raise PasswordError(os_error(e))
Exemple #11
0
 def __init__(self, name, data, update, skip, trial_run):
     self.keyname = name
     self.data = data
     self.update = update
     self.skip = skip
     self.trial_run = trial_run
     self.warning = ''
     narrate(name)
Exemple #12
0
 def execute_late(cls, name, args, settings, options):
     # execute_late() takes same arguments as run(), but is run after all the
     # configurations have been run. As such, the settings argument is None.
     # run_late() is used for commands that want to create a summary that
     # includes the results from all the configurations.
     if hasattr(cls, 'run_late'):
         narrate('running {} post-command'.format(name))
         return cls.run_late(name, args if args else [], settings, options)
Exemple #13
0
 def execute_early(cls, name, args, settings, options):
     # run_early() takes same arguments as run(), but is run before the
     # settings files have been read. As such, the settings argument is None.
     # run_early() is used for commands that do not need settings and should
     # work even if the settings files do not exist or are not valid.
     if hasattr(cls, 'run_early'):
         narrate('running {} pre-command'.format(name))
         return cls.run_early(name, args if args else [], settings, options)
Exemple #14
0
def clean(host):
    try:
        narrate(fmt('Cleaning {host}.'))
        run_sftp(host, ['rm .ssh/*.provisional'])
    except OSError as err:
        if 'no such file or directory' in str(err).lower():
            comment(os_error(err))
        else:
            error('cannot connect.', culprit=host)
Exemple #15
0
def eval_plugin(plugin, config, subconfig, *args, **kwargs):
    narrate(f"Running the '{plugin.stage}.{plugin.name}' plugin")

    try:
        return plugin(subconfig, *args, **kwargs)

    except PluginError as e:
        e.plugin = plugin
        raise
Exemple #16
0
    def run(cls, command, args, settings, options):
        # read command line
        docopt(cls.USAGE, argv=[command] + args)

        try:
            prev_log = settings.prev_logfile.read_text()
            output(prev_log)
        except FileNotFoundError as e:
            narrate(os_error(e))
Exemple #17
0
def run_duplicity(cmd, settings, narrating):
    os.environ.update(publish_passcode(settings))
    for ssh_var in 'SSH_AGENT_PID SSH_AUTH_SOCK'.split():
        if ssh_var not in os.environ:
            warn('environment variable not found, is ssh-agent running?',
                 culprit=ssh_var)
    narrate('running:\n{}'.format(indent(render_command(cmd))))
    modes = 'soeW' if narrating else 'sOEW'
    Run(cmd, modes=modes, env=os.environ)
Exemple #18
0
    def gather_public_keys(self):
        comment('    gathering public keys')
        keyname = self.keyname
        data = self.data
        clients = conjoin(self.data.get('clients', []))
        default_purpose = fmt('This key allows access from {clients}.')
        purpose = self.data.get('purpose', default_purpose)
        servers = self.data.get('servers', [])
        prov = '.provisional' if self.trial_run else ''

        # read contents of public key
        try:
            pubkey = to_path(keyname + '.pub')
            key = pubkey.read_text().strip()
        except OSError as err:
            narrate('%s, skipping.' % os_error(err))
            return

        # get fingerprint of public key
        try:
            keygen = Run(['ssh-keygen', '-l', '-f', pubkey], modes='wOeW')
            fields = keygen.stdout.strip().split()
            fingerprint = ' '.join([fields[0], fields[1], fields[-1]])
        except OSError as err:
            error(os_error(err))
            return

        # contribute commented and restricted public key to the authorized_key 
        # file for each server
        for server in servers:
            if self.update and server not in self.update:
                continue
            if server in self.skip:
                continue
            server_data = servers[server]
            description = server_data.get('description', None)
            restrictions = server_data.get('restrictions', [])
            remarks = [
                '# %s' % t
                for t in cull([purpose, description, self.warning, fingerprint])
                if t
            ]

            include_file = server_data.get(
                'remote-include-filename', data['remote-include-filename']
            )
            bypass = server_data.get('bypass')
            authkeys = AuthKeys(server, include_file, bypass, self.trial_run)
            authkeys.add_public_key(keyname, key, remarks, restrictions)

        if not servers:
            warn(
                'no servers specified, you must update them manually.', 
                culprit=keyname
            )
Exemple #19
0
    def __enter__(self):
        # change to working directory
        working_dir = self.value('working_dir')
        if not working_dir:
            working_dir = self.resolve(DEFAULT_WORKING_DIR)
        self.working_dir = to_path(working_dir)
        mkdir(self.working_dir)
        narrate('changing to working_dir:', working_dir)
        self.starting_dir = cd(self.working_dir).starting_dir

        # resolve src and dest directories
        src_dir = self.resolve(self.src_dir)
        self.src_dir = to_path(src_dir)
        dest_dir = self.resolve(self.dest_dir)
        self.dest_dir = to_path(dest_dir)

        # resolve other files and directories
        config_dir = self.resolve(CONFIG_DIR)
        self.config_dir = to_path(config_dir, config_dir)

        logfile = self.resolve(EMBALM_LOG_FILE)
        self.logfile = to_path(working_dir, logfile)

        incr_date_file = self.resolve(INCR_DATE_FILE)
        self.incr_date_file = to_path(working_dir, incr_date_file)

        full_date_file = self.resolve(FULL_DATE_FILE)
        self.full_date_file = to_path(working_dir, full_date_file)

        restore_dir = self.resolve(RESTORE_DIR)
        self.restore_dir = to_path(working_dir, restore_dir)

        archive_dir = self.resolve(ARCHIVE_DIR)
        self.archive_dir = to_path(working_dir, archive_dir)

        # perform locking
        if self.requires_exclusivity:
            # check for existance of lockfile
            lockfile = self.lockfile = to_path(working_dir, LOCK_FILE)
            if lockfile.exists():
                raise Error(f'currently running (see {lockfile} for details).')

            # create lockfile
            now = arrow.now()
            pid = os.getpid()
            lockfile.write_text(
                dedent(f'''
                started = {now!s}
                pid = {pid}
            ''').lstrip())

        # open logfile
        get_informer().set_logfile(self.logfile)

        return self
Exemple #20
0
    def run_user_commands(self, setting):
        for i, cmd in enumerate(self.values(setting)):
            narrate(f"staging {setting}[{i}] command.")
            try:
                Run(cmd, "SoEW")
            except Error as e:
                e.reraise(culprit=(setting, i, cmd.split()[0]))

        # the following two statements are only useful from run_before_borg
        self.settings[setting] = []  # erase the setting so it is not run again
        self.borg_ran = True  # indicate that before has run so after should run
Exemple #21
0
    def read_hosts(self):
        set_network_name(self.network.name())
        conf_file = to_path(CONFIG_DIR, "hosts.conf")
        narrate("reading:", conf_file)
        PythonFile(conf_file).run()

        # Process each host
        hosts = Hosts(self.network.name(), self.proxy, self.proxies, self)
        for host in HostEntry.all_hosts():
            hosts.process(host, forwards=False)
            hosts.process(host, forwards=True)
        self.hosts = hosts
Exemple #22
0
    def run_borg_raw(self, args):

        # prepare the command
        os.environ.update(self.publish_passcode())
        os.environ['BORG_DISPLAY_PASSPHRASE'] = 'no'
        executable = self.value('borg_executable', BORG)
        repository = str(self.repository)
        command = ([executable] + [(repository if a == '@repo' else a)
                                   for a in args])

        # run the command
        narrate('running:\n{}'.format(
            indent(render_command(command, borg_options_arg_count))))
        return Run(command, modes='soeW', env=os.environ, log=False)
Exemple #23
0
    def publish(self):
        narrate('publishing authorized_keys to', self.server)
        prov = '.provisional' if self.trial_run else ''
        entries = [
            fmt("# This file was generated by sshdeploy on {date}.")
        ]
        if self.include:
            entries += [
                '\n'.join([
                    fmt('# Contents of {self.include_file}:'),
                    self.include
                ])
            ]
        for name in sorted(self.keys.keys()):
            key = self.keys[name]
            comment = self.comment[name]
            comment = [comment] if is_str(comment) else comment
            restrictions = self.restrictions[name]
            if not is_str(restrictions):
                restrictions = ','.join(restrictions)
            restricted_key = ' '.join(cull([restrictions, key]))
            entries.append('\n'.join(comment + [restricted_key]))

        # delete any pre-existing provisional files
        # the goal here is to leave a clean directory when not trial-run
        try:
            run_sftp(self.server, [
                fmt('rm .ssh/authorized_keys.provisional')
            ])
        except OSError as err:
            pass

        # now upload the new authorized_keys file
        try:
            authkey = to_path('authorized_keys.%s' % self.server)
            with authkey.open('w') as f:
                f.write('\n\n'.join(entries) + '\n')
            authkey.chmod(0o600)
            if self.bypass:
                warn(
                    'You must manually upload',
                    fmt('<keydir>/authorized_keys.{self.server}.'),
                    culprit=self.server
                )
            else:
                run_sftp(self.server, [
                    fmt('put -p {authkey} .ssh/authorized_keys{prov}')
                ])
        except OSError as err:
            error(os_error(err))
Exemple #24
0
def query_passcode(config):
    narrate("Getting a passcode for the archive")

    # The authentication system is special in that if no plugins are specified,
    # the 'getpass' plugin will be used by default.
    plugins = select_plugins(config, 'auth', ['getpass'])

    # Try each authentication method until one works.
    for plugin in plugins:
        subconfig = config.get('auth', {}).get(plugin.name, {})

        try:
            return eval_plugin(plugin, config, subconfig)

        except SkipPlugin as e:
            display(f"Skipping '{plugin.name}' authentication: {e}")
            continue

    raise AllAuthFailed(plugins)
Exemple #25
0
def publish_mount(config, workspace):
    """
    Copy the archive to one or more mounted/mountable drives.
    """
    drives = require_one_or_more(config, 'drive')
    remote_dir = config.get('remote_dir', 'backup/sparekeys')
    remote_dir = remote_dir.format(**PARAMS)

    for drive in drives:
        narrate(f"copying archive to '{drive}'.")
        try:
            with mount(drive):
                dest = to_path(drive, remote_dir)
                rm(dest); mkdir(dest)
                cp(workspace, dest)
        except Error as e:
            error(e, culprit=drive, codicil='Skipping.')
        else:
            display(f"Archive copied to '{drive}'.")
Exemple #26
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        paths = cmdline['<path>']
        archive = cmdline['--archive']
        date = cmdline['--date']

        # remove initial / from paths
        src_dirs = [str(p).lstrip('/') for p in settings.src_dirs]
        new_paths = [p.lstrip('/') for p in paths]
        if paths != new_paths:
            for path in paths:
                if path.startswith('/'):
                    narrate('removing initial /.', culprit=path)
            paths = new_paths

        # assure that paths correspond to src_dirs
        unknown_path = False
        for path in paths:
            if not any([path.startswith(src_dir) for src_dir in src_dirs]):
                unknown_path = True
                warn('unknown path.', culprit=path)
        if unknown_path:
            codicil('Paths should start with:', conjoin(src_dirs,
                                                        conj=', or '))

        # get the desired archive
        if date and not archive:
            archive = get_name_of_nearest_archive(settings, date)
        if not archive:
            archive = get_name_of_latest_archive(settings)
        output('Archive:', archive)

        # run borg
        borg = settings.run_borg(
            cmd='extract',
            args=[settings.destination(archive)] + paths,
            emborg_opts=options,
        )
        out = borg.stdout
        if out:
            output(out.rstrip())
Exemple #27
0
    def publish_passcode(self):
        for v in ['BORG_PASSPHRASE', 'BORG_PASSCOMMAND', 'BORG_PASSPHRASE_FD']:
            if v in os.environ:
                narrate(f"Using existing {v}.")
                return

        passcommand = self.value('passcommand')
        passcode = self.passphrase

        # process passcomand
        if passcommand:
            if passcode:
                warn("passphrase unneeded.", culprit="passcommand")
            narrate(f"Setting BORG_PASSCOMMAND.")
            os.environ['BORG_PASSCOMMAND'] = passcommand
            self.borg_passcode_env_var_set_by_emborg = 'BORG_PASSCOMMAND'
            return

        # get passphrase from avendesora
        if not passcode and self.avendesora_account:
            narrate("running avendesora to access passphrase.")
            try:
                from avendesora import PasswordGenerator

                pw = PasswordGenerator()
                account_spec = self.value("avendesora_account")
                if ':' in account_spec:
                    passcode = str(pw.get_value(account_spec))
                else:
                    account = pw.get_account(self.value("avendesora_account"))
                    field = self.value("avendesora_field", None)
                    passcode = str(account.get_value(field))
            except ImportError:
                raise Error(
                    "Avendesora is not available",
                    "you must specify passphrase in settings.",
                    sep=", ",
                )

        if passcode:
            os.environ['BORG_PASSPHRASE'] = passcode
            narrate(f"Setting BORG_PASSPHRASE.")
            self.borg_passcode_env_var_set_by_emborg = 'BORG_PASSPHRASE'
            return

        if self.encryption is None:
            self.encryption = "none"
        if self.encryption == "none" or self.encryption.startswith(
                'authenticated'):
            comment("Encryption is disabled.")
            return
        raise Error("Cannot determine the encryption passphrase.")
Exemple #28
0
def publish_passcode(settings):
    passcode = settings.gpg_passphrase
    if not passcode and settings.avendesora_account:
        narrate('running avendesora to access passphrase.')
        try:
            from avendesora import PasswordGenerator, PasswordError
            pw = PasswordGenerator()
            account = pw.get_account(settings.value('avendesora_account'))
            passcode = str(account.get_value('passcode'))
        except PasswordError as err:
            settings.fail(err)
        except ImportError:
            settings.fail('Avendesora is not available',
                          'you must specify gpg_passphrase in settings.',
                          sep=', ')
    else:
        settings.fail('you must specify gpg_passphrase in settings.')

    narrate('gpg passphrase is set.')
    return dict(PASSPHRASE=passcode)
Exemple #29
0
def test_access(host):
    try:
        narrate(fmt('Testing connection to {host}.'))
        payload = fmt('test payload for {host}')
        ref = to_path('.ref')
        test = to_path('.test')
        ref.write_text(payload)
        rm(test)
        run_sftp(host, [
            fmt('put {ref}'),
            fmt('get {ref} {test}'),
            fmt('rm {ref}')
        ])
        if test.read_text() == payload:
            comment('connection successful.', culprit=host)
        else:
            error('cannot connect.', culprit=host)
    except OSError as err:
        error('cannot connect.', culprit=host)
    rm(ref, test)
Exemple #30
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        paths = cmdline['<path>']
        date = ['--time', cmdline['--date']] if cmdline['--date'] else []

        # run duplicity
        rm('duplicity.log')
        mkdir(settings.restore_dir)
        for path in paths:
            desired = to_path(settings.starting_dir,
                              path).relative_to(settings.src_dir)
            narrate('restoring:', path)
            dest = to_path(settings.restore_dir, desired.name)

            cmd = (f'duplicity restore --file-to-restore {desired}'.split() +
                   duplicity_options(settings, options) +
                   archive_dir_command(settings) + sftp_command(settings) +
                   date + [destination(settings), dest])
            run_duplicity(cmd, settings, 'narrate' in options)
            output(f"restored as: {dest}", culprit=path)
Exemple #31
0
    def report_borg_error(self, e, cmd):
        narrate('Borg terminates with exit status:', e.status)
        codicil = None
        if e.stderr:
            if 'previously located at' in e.stderr:
                codicil = dedent(f'''
                    If repository was intentionally relocated, re-run with --relocated:
                        emborg --relocated {cmd} ...
                ''')
            if 'Failed to create/acquire the lock' in e.stderr:
                codicil = [
                    'If another Emborg or Borg process is using this repository,',
                    'please wait for it to finish.',
                    'Perhaps you still have an archive mounted?',
                    'If so, use ‘emborg umount’ to unmount it.',
                    'Perhaps a previous run was killed or terminated with an error?',
                    'If so, use ‘emborg breaklock’ to clear the lock.',
                ]

            if 'Mountpoint must be a writable directory' in e.stderr:
                codicil = 'Perhaps an archive is already mounted there?'
        e.reraise(culprit=cmd, codicil=codicil)
Exemple #32
0
 def create(self, contents, gpg_ids):
     path = self.path
     try:
         to_path(get_setting('settings_dir')).mkdir(parents=True, exist_ok=True)
         if path.exists():
             # file creation (init) requested, but file already exists
             # don't overwrite the file, instead read it so the information 
             # can be used to create any remaining files.
             display("%s: already exists." % path)
             return
         # create the file
         display('%s: creating.' % path)
         if path.suffix in ['.gpg', '.asc']:
             narrate('encrypting.', culprit=path)
             # encrypt it
             self.save(contents, gpg_ids)
         else:
             narrate('not encrypting.', culprit=path)
             # file is not encrypted
             with path.open('w') as f:
                 f.write(contents)
     except OSError as err:
         raise Error(os_error(err))
Exemple #33
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        kind = 'full' if command in 'full f'.split() else 'incr'

        # check the dependencies are available
        for each in settings.values('must_exist'):
            path = to_path(each)
            if not path.exists():
                raise Error('does not exist, perform setup and restart.',
                            culprit=each)

        # run prerequisites
        for each in settings.values('run_before_backup'):
            narrate('running:', each)
            Run(each, 'SoeW')
        rm('duplicity.log')

        # run duplicity
        cmd = (f'duplicity {kind}'.split() +
               duplicity_options(settings, options) +
               archive_dir_command(settings) + sftp_command(settings) +
               excludes(settings) +
               [render_path(settings.src_dir),
                destination(settings)])
        run_duplicity(cmd, settings, 'narrate' in options)

        # update the date files
        now = arrow.now()
        if kind == 'full':
            settings.full_date_file.write_text(str(now))
        settings.incr_date_file.write_text(str(now))

        # run any scripts specified to be run after a backup
        for each in settings.values('run_after_backup'):
            narrate('running:', each)
            Run(each, 'SoeW')
Exemple #34
0
    def run(self):
        global ActivePythonFile
        ActivePythonFile = self.path
        path = self.path
        self.encrypted = path.suffix in ['.gpg', '.asc']
        narrate('reading.', culprit=path)
        try:
            self.code = self.read()
            # need to save the code for the new command
        except OSError as e:
            raise PasswordError(os_error(e))

        try:
            compiled = compile(self.code, str(path), 'exec')
        except SyntaxError as e:
            culprit = (e.filename, e.lineno)
            if e.text is None or e.offset is None:
                raise PasswordError(full_stop(e.msg), culprit=culprit)
            else:
                raise PasswordError(e.msg + ':',
                                    e.text.rstrip(),
                                    (e.offset - 1) * ' ' + '^',
                                    culprit=culprit,
                                    sep='\n')
                # File "/home/ken/.config/avendesora/config", line 18
                #   'c': 'google-chrome %s'
                #      ^

        contents = {}
        try:
            exec(compiled, contents)
        except Exception as e:
            from .utilities import error_source
            raise PasswordError(full_stop(e), culprit=error_source())
        ActivePythonFile = None
        return contents
Exemple #35
0
    def publish_passcode(self):
        passcommand = self.value('passcommand')
        passcode = self.passphrase

        # process passcomand
        if passcommand:
            if passcode:
                warn("passphrase unneeded.", culprit="passcommand")
            return dict(BORG_PASSCOMMAND=passcommand)

        # get passphrase from avendesora
        if not passcode and self.avendesora_account:
            narrate("running avendesora to access passphrase.")
            try:
                from avendesora import PasswordGenerator

                pw = PasswordGenerator()
                account = pw.get_account(self.value("avendesora_account"))
                field = self.value("avendesora_field", None)
                passcode = str(account.get_value(field))
            except ImportError:
                raise Error(
                    "Avendesora is not available",
                    "you must specify passphrase in settings.",
                    sep=", ",
                )

        if passcode:
            return dict(BORG_PASSPHRASE=passcode)

        if self.encryption is None:
            self.encryption = "none"
        if self.encryption == "none":
            comment("passphrase is not available, encryption disabled.")
            return {}
        raise Error("Cannot determine the encryption passphrase.")
Exemple #36
0
    def run_borg_raw(self, args):

        # prepare the command
        os.environ.update(self.publish_passcode())
        os.environ['BORG_DISPLAY_PASSPHRASE'] = 'no'
        executable = self.value('borg_executable', BORG)
        remote_path = self.value('remote_path')
        remote_path = ['--remote-path', remote_path] if remote_path else []
        repository = str(self.repository)
        command = ([executable] + remote_path +
                   [(repository if a == '@repo' else a) for a in args])

        # run the command
        narrate('running:\n{}'.format(
            indent(render_command(command, borg_options_arg_count))))
        starts_at = arrow.now()
        narrate('starts at: {!s}'.format(starts_at))
        borg = Run(command, modes='soeW', env=os.environ, log=False)
        ends_at = arrow.now()
        narrate('ends at: {!s}'.format(ends_at))
        narrate('elapsed = {!s}'.format(ends_at - starts_at))
        return borg
Exemple #37
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)

        # check for required settings
        src_dirs = render_paths(settings.src_dirs)
        if not src_dirs:
            raise Error('src_dirs: setting has no value.')

        # check the dependencies are available
        for each in settings.values('must_exist'):
            path = to_path(each)
            if not path.exists():
                raise Error('does not exist, perform setup and restart.',
                            culprit=each)

        # run prerequisites
        cmds = settings.value('run_before_backup')
        if is_str(cmds):
            cmds = [cmds]
        for cmd in cull(cmds):
            narrate('running pre-backup script:', cmd)
            try:
                Run(cmd, 'SoEW')
            except Error as e:
                e.reraise(culprit=('run_before_backup', cmd.split()[0]))

        # run borg
        try:
            settings.run_borg(
                cmd='create',
                args=[settings.destination(True)] +
                render_paths(settings.src_dirs),
                emborg_opts=options,
            )
        except Error as e:
            if e.stderr and 'is not a valid repository' in e.stderr:
                e.reraise(
                    codicil="Run 'emborg init' to initialize the repository.")
            else:
                raise

        # update the date files
        narrate('update date file')
        now = arrow.now()
        settings.date_file.write_text(str(now))

        # run any scripts specified to be run after a backup
        cmds = settings.value('run_after_backup')
        if is_str(cmds):
            cmds = [cmds]
        for cmd in cull(cmds):
            narrate('running post-backup script:', cmd)
            try:
                Run(cmd, 'SoEW')
            except Error as e:
                e.reraise(culprit=('run_after_backup', cmd.split()[0]))

        if cmdline['--fast']:
            return

        # prune the archives if requested
        try:
            # check the archives if requested
            activity = 'checking'
            if settings.check_after_create:
                narrate('checking archive')
                check = CheckCommand()
                check.run('check', [], settings, options)

            activity = 'pruning'
            if settings.prune_after_create:
                narrate('pruning archives')
                prune = PruneCommand()
                prune.run('prune', [], settings, options)
        except Error as e:
            e.reraise(
                codicil=(f'This error occurred while {activity} the archives.',
                         'No error was reported while creating the archive.'))
Exemple #38
0
 def execute(cls, name, args, settings, options):
     if hasattr(cls, 'run'):
         narrate('running {} command'.format(name))
         exit_status = cls.run(name, args if args else [], settings,
                               options)
         return 0 if exit_status is None else exit_status
Exemple #39
0
 def execute(cls, name, args, settings, options):
     narrate('{}:'.format(name))
     cls.run(name, args if args else [], settings, options)