Beispiel #1
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
            )
Beispiel #2
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        mount_point = cmdline['<mount_point>']
        archive = cmdline['--archive']
        date = cmdline['--date']
        mount_all = cmdline['--all']
        include_external_archives = cmdline['--include-external']

        # get the desired archive
        if not archive:
            if date:
                archive = get_name_of_nearest_archive(settings, date)
            elif not mount_all:
                archive = get_name_of_latest_archive(settings)

        # create mount point if it does not exist
        try:
            mkdir(mount_point)
        except OSError as e:
            raise Error(os_error(e))

        # run borg
        borg = settings.run_borg(
            cmd='mount',
            args=[settings.destination(archive), mount_point],
            emborg_opts=options,
            strip_prefix=include_external_archives,
        )
        out = borg.stdout
        if out:
            output(out.rstrip())
Beispiel #3
0
def main():
    with Inform(notify_if_no_tty=True, version=version) as inform:
        try:
            # assure config and log directories exist
            to_path(CONFIG_DIR).mkdir(parents=True, exist_ok=True)
            to_path(DATA_DIR).mkdir(parents=True, exist_ok=True)
            inform.set_logfile(to_path(DATA_DIR, LOG_FILE))

            # read command line
            cmdline = docopt(synopsis, options_first=True, version=version)
            command = cmdline["<command>"]
            args = cmdline["<args>"]
            if cmdline["--quiet"]:
                inform.quiet = True

            # find and run command
            settings = Settings(cmdline)
            cmd, cmd_name = Command.find(command)
            cmd.execute(cmd_name, args, settings, cmdline)

        except KeyboardInterrupt:
            display("Terminated by user.")
        except Error as e:
            e.terminate()
        except OSError as e:
            fatal(os_error(e))
        done()
Beispiel #4
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())
Beispiel #5
0
def main():
    try:
        # read config file
        read_config()

        # read command line
        cmdline = docopt(
            __doc__.format(commands=Command.summarize()),
            version='avendesora {} ({})'.format(__version__, __released__),
            options_first=True,
        )

        # start logging
        logfile = BufferedFile(get_setting('log_file'), True)
        Inform(logfile=logfile,
               hanging_indent=False,
               stream_policy='header',
               notify_if_no_tty=True)
        shlib.set_prefs(use_inform=True, log_cmd=True)

        # run the requested command
        Command.execute(cmdline['<command>'], cmdline['<args>'])
        done()
    except KeyboardInterrupt:
        output('\nTerminated by user.')
        terminate()
    except (PasswordError, Error) as e:
        e.terminate()
    except OSError as e:
        fatal(os_error(e))
    done()
Beispiel #6
0
    def run(self):
        global ActiveFile
        ActiveFile = self.path
        path = self.path
        self.encrypted = path.suffix in ['.gpg', '.asc']
        log('reading.', culprit=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:
            raise Error(
                err.msg + ':', err.text, (err.offset-1)*' ' + '^',
                culprit=(err.filename, err.lineno), sep='\n'
            )
            # File "/home/ken/.config/avendesora/config", line 18
            #   'g': 'google-chrome %s'
            #      ^

        contents = {}
        exec(compiled, contents)
        ActiveFile = None
        return contents
Beispiel #7
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        mount_point = cmdline['<mount_point>']
        if not mount_point:
            mount_point = settings.value('default_mount_point')
            if not mount_point:
                raise Error('must specify directory to use as mount point')
        mount_point = to_path(mount_point)

        # run borg
        try:
            settings.run_borg(
                cmd='umount',
                args=[mount_point],
                emborg_opts=options,
            )
            try:
                to_path(mount_point).rmdir()
            except OSError as e:
                warn(os_error(e))
        except Error as e:
            if 'busy' in str(e):
                e.reraise(
                    codicil=
                    f"Try running 'lsof +D {mount_point}' to find culprit.")
Beispiel #8
0
def test_archive():
    try:
        result = subprocess.check_output("avendesora help archive".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Generates archive of all account information.

        Usage:
            avendesora archive
            avendesora A

        This command creates an encrypted archive that contains all the
        information in your accounts files, including the fully generated
        secrets.  You should never need this file, but its presence protects
        you in case you lose access to Avendesora. To access your secrets
        without Avendesora, simply decrypt the archive file with GPG.  The
        actual secrets will be hidden, but it easy to retrieve them even
        without Avendesora. When hidden, the secrets are encoded in base64.
        You can decode it by running 'base64 -d -' and pasting the encoded
        secret into the terminal.

        When you run this command it overwrites the existing archive. If you
        have accidentally deleted an account or changed a secret, then
        replacing the archive could cause the last copy of the original
        information to be lost. To prevent this from occurring it is a good
        practice to run the 'changed' command before regenerating the
        archive.  It describes all of the changes that have occurred since
        the last time the archive was generated. You should only regenerate
        the archive once you have convinced yourself all of the changes are
        as expected.
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #9
0
def test_reveal():
    try:
        result = subprocess.check_output("avendesora help reveal".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Reveal concealed text.

        Transform concealed text to reveal its original form.

        Usage:
            avendesora reveal [<text>]
            avendesora r      [<text>]

        Options:
            -e <encoding>, --encoding <encoding>
                                    Encoding used when revealing information.

        Though available as an option for convenience, you should not pass
        the text to be revealed as an argument as it is possible for others
        to examine the commands you run and their argument list. For any
        sensitive secret, you should simply run 'avendesora reveal' and then
        enter the encoded text when prompted.
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #10
0
def main():
    with Inform(error_status=2, flush=True, version=version) as inform:
        # read command line
        cmdline = docopt(expanded_synopsis, options_first=True, version=version)
        config = cmdline["--config"]
        command = cmdline["<command>"]
        args = cmdline["<args>"]
        if cmdline["--mute"]:
            inform.mute = True
        if cmdline["--quiet"]:
            inform.quiet = True
        emborg_opts = cull(
            [
                "verbose" if cmdline["--verbose"] else "",
                "narrate" if cmdline["--narrate"] else "",
                "dry-run" if cmdline["--dry-run"] else "",
                "no-log" if cmdline["--no-log"] else "",
            ]
        )
        if cmdline["--narrate"]:
            inform.narrate = True

        try:
            # find the command
            cmd, cmd_name = Command.find(command)

            # execute the command initialization
            exit_status = cmd.execute_early(cmd_name, args, None, emborg_opts)
            if exit_status is not None:
                terminate(exit_status)

            worst_exit_status = 0
            try:
                while True:
                    with Settings(config, cmd, emborg_opts) as settings:
                        try:
                            exit_status = cmd.execute(
                                cmd_name, args, settings, emborg_opts
                            )
                        except Error as e:
                            settings.fail(e, cmd=' '.join(sys.argv))
                            e.terminate()

                    if exit_status and exit_status > worst_exit_status:
                        worst_exit_status = exit_status
            except NoMoreConfigs:
                pass

            # execute the command termination
            exit_status = cmd.execute_late(cmd_name, args, None, emborg_opts)
            if exit_status and exit_status > worst_exit_status:
                worst_exit_status = exit_status

        except KeyboardInterrupt:
            display("Terminated by user.")
        except Error as e:
            e.terminate()
        except OSError as e:
            fatal(os_error(e))
        terminate(worst_exit_status)
Beispiel #11
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
Beispiel #12
0
 def read_defaults(self):
     settings = {}
     try:
         from appdirs import user_config_dir
         config_file = to_path(user_config_dir('vdiff'), 'config')
         try:
             code = config_file.read_text()
             try:
                 compiled = compile(code, str(config_file), 'exec')
                 exec(compiled, settings)
             except Exception as e:
                 error(e, culprit=config_file)
         except FileNotFoundError:
             pass
         except OSError as e:
             warn(os_error(e))
         if self.useGUI is not None:
             settings['gui'] = self.useGUI
     except ImportError:
         pass
     if settings.get('gui', DEFAULT_GUI):
         if 'DISPLAY' not in os.environ:
             warn('$DISPLAY not set, ignoring request for gvim.')
         else:
             self.cmd = settings.get('gvimdiff', DEFAULT_GVIM)
             return
     self.cmd = settings.get('vimdiff', DEFAULT_VIM)
Beispiel #13
0
def test_new():
    try:
        result = subprocess.check_output("avendesora help new".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Create new accounts file.

        Usage:
            avendesora new [--gpg-id <id>]... <name>
            avendesora N   [--gpg-id <id>]... <name>

        Options:
            -g <id>, --gpg-id <id>  Use this ID when creating any missing encrypted files.

        Creates a new accounts file. Accounts that share the same file share
        the same master password by default and, if the file is encrypted,
        can be decrypted by the same recipients.

        Generally you would create a new accounts file for each person or
        group with which you wish to share accounts. You would also use
        separate files for passwords with different security domains. For
        example, a high-value passwords might be placed in an encrypted file
        that would only be placed highly on protected computers. Conversely,
        low-value passwords might be contained in perhaps an unencrypted
        file that is found on many computers.

        Add a '.gpg' extension to <name> to encrypt the file.
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #14
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))
Beispiel #15
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        mount_point = cmdline['<mount_point>']
        archive = cmdline['--archive']
        date = cmdline['--date']

        # get the desired archive
        if date and not archive:
            archive = get_nearest_archive(settings, date)
            if not archive:
                raise Error('archive not available.', culprit=date)

        # create mount point if it does not exist
        try:
            mkdir(mount_point)
        except OSError as e:
            raise Error(os_error(e))

        # run borg
        borg = settings.run_borg(
            cmd='mount',
            args=[settings.destination(archive), mount_point],
            emborg_opts=options,
        )
        out = borg.stdout
        if out:
            output(out.rstrip())
Beispiel #16
0
def test_summary():
    try:
        result = run('avendesora values -s mb')
    except OSError as err:
        result = os_error(err)
    expected = dedent("""\
        names: mybank, mb
        accounts:
            checking: reveal with: avendesora value mybank accounts.checking
            savings: reveal with: avendesora value mybank accounts.savings
            creditcard: reveal with: avendesora value mybank accounts.creditcard
        birthdate: 1981-10-01
        checking: {accounts.checking}
        comment:
            This is a multiline comment.
            It spans more than one line.
        customer service: 1-866-229-6633
        email: [email protected]
        passcode: reveal with: avendesora value mybank passcode
        pin: reveal with: avendesora value mybank pin
        questions:
            0: What city were you born in?, reveal with: avendesora value mybank questions.0
            1: What street did you grow up on?, reveal with: avendesora value mybank questions.1
            2: What was your childhood nickname?, reveal with: avendesora value mybank questions.2
        urls: https://mb.com
        username: pizzaman
        verbal: reveal with: avendesora value mybank verbal
    """)
    assert result.decode('utf-8') == expected
Beispiel #17
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        mount_point = cmdline["<mount_point>"]
        if mount_point:
            mount_point = settings.to_path(mount_point, resolve=False)
        else:
            mount_point = settings.as_path("default_mount_point")
        if not mount_point:
            raise Error("must specify directory to use as mount point")

        # run borg
        try:
            settings.run_borg(
                cmd="umount",
                args=[mount_point],
                emborg_opts=options,
            )
            try:
                mount_point.rmdir()
            except OSError as e:
                warn(os_error(e))
        except Error as e:
            if "busy" in str(e):
                e.reraise(
                    codicil=
                    f"Try running 'lsof +D {mount_point!s}' to find culprit.")
Beispiel #18
0
def main():
    with Inform() as inform:
        # read command line
        cmdline = docopt(
            __doc__.format(commands=Command.summarize()),
            options_first=True
        )
        config = cmdline['--config']
        command = cmdline['<command>']
        args = cmdline['<args>']
        options = cull([
            'verbose' if cmdline['--verbose'] else '',
            'narrate' if cmdline['--narrate'] else '',
            'trial-run' if cmdline['--trial-run'] else '',
        ])
        if cmdline['--narrate']:
            inform.narrate = True

        try:
            cmd, name = Command.find(command)

            with Settings(config, cmd.REQUIRES_EXCLUSIVITY) as settings:
                cmd.execute(name, args, settings, options)

        except KeyboardInterrupt:
            display('Terminated by user.')
        except Error as err:
            err.terminate()
        except OSError as err:
            fatal(os_error(err))
        terminate()
Beispiel #19
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("__")}
Beispiel #20
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))
Beispiel #21
0
def test_changed():
    try:
        result = subprocess.check_output("avendesora help changed".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Identify any changes that have occurred since the archive was created.

        Usage:
            avendesora changed
            avendesora C

        When you run the 'archive' command it overwrites the existing
        archive. If you have accidentally deleted an account or changed a
        secret, then replacing the archive could cause the last copy of the
        original information to be lost. To prevent this from occurring it
        is a good practice to run the 'changed' command before regenerating
        the archive.  It describes all of the changes that have occurred
        since the last time the archive was generated. You should only
        regenerate the archive once you have convinced yourself all of the
        changes are as expected.
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #22
0
    def read_manifests(self):  # {{{2
        if self.name_index:
            return
        cache_dir = get_setting('cache_dir')
        manifests_path = cache_dir / MANIFESTS_FILENAME
        try:
            encrypted = manifests_path.read_bytes()

            user_key = get_setting('user_key')
            if not user_key:
                raise Error('no user key.')
            key = base64.urlsafe_b64encode(sha256(user_key.encode('ascii')).digest())
            fernet = Fernet(key)
            contents = fernet.decrypt(encrypted)

            try:
                cache = pickle.loads(contents, **PICKLE_ARGS)
                self.name_manifests = cache['names']
                self.url_manifests = cache['urls']
                self.title_manifests = cache['titles']

                # build the name_index by inverting the name_manifests
                self.name_index = {
                    h:n for n,l in self.name_manifests.items() for h in l
                }
            except (ValueError, pickle.UnpicklingError) as e:
                warn('garbled manifest.', culprit=manifests_path, codicil=str(e))
                manifests_path.unlink()
            assert isinstance(self.name_index, dict)
        except OSErrors as e:
            comment(os_error(e))
        except Exception as e:
            comment(e)
Beispiel #23
0
    def start(self, stdin=None):
        """
        Start the command, will not wait for it to terminate.

        If stdin is given, it should be a string. Otherwise, no connection is
        made to stdin of the command.
        """
        self.stdin = None
        import subprocess

        if is_str(self.cmd):
            cmd = self.cmd if self.use_shell else split_cmd(self.cmd)
        else:
            cmd = self.cmd
        if _use_log(self.log):
            from inform import log

            log("running:", render_command(cmd, option_args=self.option_args))

        if self.save_stdout or self.save_stderr:
            try:
                DEVNULL = subprocess.DEVNULL
            except AttributeError:
                DEVNULL = open(os.devnull, "wb")
        assert self.merge_stderr_into_stdout is False, "M not supported, use E"

        streams = {}
        if stdin is not None:
            streams["stdin"] = subprocess.PIPE
        if self.save_stdout:
            streams["stdout"] = DEVNULL
        if self.save_stderr:
            streams["stderr"] = DEVNULL

        # run the command
        try:
            process = subprocess.Popen(cmd,
                                       shell=self.use_shell,
                                       env=self.env,
                                       **streams)
        except OSError as e:
            if PREFERENCES["use_inform"]:
                from inform import Error, os_error

                raise Error(msg=os_error(e),
                            cmd=render_command(self.cmd),
                            template="{msg}")
            else:
                raise
        self.running = True

        # store needed information and wait for termination if desired
        self.pid = process.pid
        self.process = process

        # write to stdin
        if stdin is not None:
            process.stdin.write(stdin.encode(self.encoding))
            process.stdin.close()
Beispiel #24
0
def test_alertscc_discovery():
    try:
        result = run(
            'avendesora value --title https://alertscc.bbcportal.com --stdout alertscc'
        )
    except OSError as err:
        result = os_error(err)
    assert result == b'email is [email protected], password is R7ibHyPjWtG2\n'
Beispiel #25
0
def test_scc_browse():
    try:
        result = run('avendesora browse --list scc')
    except OSError as err:
        result = os_error(err)
    assert sorted(result) == sorted(
        b'              validation: https://alertscc.bbcportal.com/Validation\n'
        b'                   login: https://alertscc.bbcportal.com\n')
Beispiel #26
0
    def run(self, stdin=None):
        """
        Run the command, will wait for it to terminate.

        If stdin is given, it should be a string. Otherwise, no connection is
        made to stdin of the command.

        Returns exit status if wait_for_termination is True.
        If wait_for_termination is False, you must call wait(), otherwise stdin
        is not be applied.  If you don't want to wait, call start() instead.
        """
        self.stdin = stdin
        import subprocess

        if is_str(self.cmd):
            cmd = self.cmd if self.use_shell else split_cmd(self.cmd)
        else:
            # cannot use to_str() because it can change some arguments when not intended.
            # this is particularly problematic the duplicity arguments in embalm
            cmd = [str(c) for c in self.cmd]
        if _use_log(self.log):
            from inform import log

            log("running:", render_command(cmd, option_args=self.option_args))

        # indicate streams to intercept
        streams = {}
        if stdin is not None:
            streams["stdin"] = subprocess.PIPE
        if self.save_stdout:
            streams["stdout"] = subprocess.PIPE
        if self.save_stderr:
            streams["stderr"] = subprocess.PIPE
        if self.merge_stderr_into_stdout:
            streams["stderr"] = subprocess.STDOUT

        # run the command
        try:
            process = subprocess.Popen(cmd,
                                       shell=self.use_shell,
                                       env=self.env,
                                       **streams)
        except OSError as e:
            if PREFERENCES["use_inform"]:
                from inform import Error, os_error

                raise Error(msg=os_error(e),
                            cmd=render_command(self.cmd),
                            template="{msg}")
            else:
                raise
        self.running = True

        # store needed information and wait for termination if desired
        self.pid = process.pid
        self.process = process
        if self.wait_for_termination:
            return self.wait()
Beispiel #27
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))
Beispiel #28
0
 def now_playing(self):
     if self.now_playing_path:
         out = [each for each in [self.artist, self.title] if each]
         try:
             self.now_playing_path.write_text(' - '.join(out))
         except OSError as e:
             if not self.warned:
                 warn(os_error(e))
                 self.warned = True
Beispiel #29
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)
Beispiel #30
0
def main():
    with Inform(error_status=2, flush=True, version=version) as inform:
        # read command line
        cmdline = docopt(expanded_synopsis, options_first=True, version=version)
        config = cmdline['--config']
        command = cmdline['<command>']
        args = cmdline['<args>']
        if cmdline['--mute']:
            inform.mute = True
        if cmdline['--quiet']:
            inform.quiet = True
        options = cull([
            'verbose' if cmdline['--verbose'] else '',
            'narrate' if cmdline['--narrate'] else '',
            'trial-run' if cmdline['--trial-run'] else '',
            'no-log' if cmdline['--no-log'] else '',
        ])
        if cmdline['--narrate']:
            inform.narrate = True

        try:
            # find the command
            cmd, cmd_name = Command.find(command)

            # execute the command initialization
            exit_status = cmd.execute_early(cmd_name, args, None, options)
            if exit_status is not None:
                terminate(exit_status)

            worst_exit_status = 0
            try:
                while True:
                    with Settings(config, cmd, options) as settings:
                        try:
                            exit_status = cmd.execute(cmd_name, args, settings, options)
                        except Error as e:
                            settings.fail(e)
                            e.terminate()

                    if exit_status and exit_status > worst_exit_status:
                        worst_exit_status = exit_status
            except NoMoreConfigs:
                pass

            # execute the command termination
            exit_status = cmd.execute_late(cmd_name, args, None, options)
            if exit_status and exit_status > worst_exit_status:
                worst_exit_status = exit_status

        except KeyboardInterrupt:
            display('Terminated by user.')
        except Error as e:
            e.terminate()
        except OSError as e:
            fatal(os_error(e))
        terminate(worst_exit_status)
def test_find():
    try:
        result = subprocess.check_output('avendesora find bank'.split())
    except OSError as err:
        result = os_error(err)
    expected = dedent("""\
        bank:
            mybank (mb)
    """)
    assert result == bytes(expected, encoding='ascii')
def test_conceal():
    try:
        result = subprocess.check_output('avendesora conceal 12345678'.split())
    except OSError as err:
        result = os_error(err)
    assert result == dedent('''
        Hidden(
            "MTIzNDU2Nzg="
        )
    ''').lstrip().encode('ascii')
Beispiel #33
0
def test_find():
    try:
        result = run('avendesora find bank')
    except OSError as err:
        result = os_error(err)
    expected = dedent("""\
        bank:
            mybank (mb)
    """)
    assert result.decode('utf-8') == expected
Beispiel #34
0
    def run(cls, command, args):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        archive_file = get_setting('archive_file')

        # first, save existing archive if it exists
        try:
            previous_archive = get_setting('previous_archive_file')
            if previous_archive and archive_file.is_file():
                rm(previous_archive)
                mv(archive_file, previous_archive)
        except OSError as err:
            raise Error(os_error(err))

        # run the generator
        generator = PasswordGenerator()

        # get dictionary that fully describes the contents of each account
        entries = []
        for account in generator.all_accounts:
            entry = account.archive()
            if entry:
                entries.append(indent('%r: %s,' % (
                    account.get_name(), to_python(entry)
                ), '    '))

        # build file contents
        from .preferences import ARCHIVE_FILE_CONTENTS
        import arrow
        contents = ARCHIVE_FILE_CONTENTS.format(
            encoding = get_setting('encoding'),
            date=str(arrow.now()),
            accounts = '\n\n'.join(entries)
        )

        archive = GnuPG(archive_file)
        if not archive.will_encrypt():
            warn('archive file is not encrypted.', culprit=archive_file)
        try:
            archive.save(contents)
            chmod(0o600, archive_file)
        except OSError as err:
            raise Error(os_error(err), culprit=archive_file)
def test_search():
    try:
        result = subprocess.check_output('avendesora search pizza'.split())
    except OSError as err:
        result = os_error(err)
    expected = dedent("""\
        pizza:
            alertscc (scc)
            mybank (mb)
    """)
    assert result == bytes(expected, encoding='ascii')
Beispiel #36
0
 def cleanup(self):
     if self.vim:
         self.vim.kill()
         for each in cull([self.file1, self.file2, self.file3, self.file4]):
             path = to_path(each)
             dn = path.parent
             fn = path.name
             swpfile = to_path(dn, '.' + fn + '.swp')
             try:
                 rm(swpfile)
             except OSError as e:
                 error(os_error(e))
Beispiel #37
0
def test_search():
    try:
        result = run('avendesora search pizza')
    except OSError as err:
        result = os_error(err)
    expected = dedent("""\
        pizza:
            alertscc (scc)
            margaritaville
            mybank (mb)
    """)
    assert result.decode('utf-8') == expected
Beispiel #38
0
def test_stealth():
    try:
        avendesora = pexpect.spawn('avendesora', 'value -s xkcd'.split())
        avendesora.expect('account name: ', timeout=4)
        avendesora.sendline('an-account-name')
        avendesora.expect(pexpect.EOF)
        avendesora.close()
        result = avendesora.before.decode('utf-8')
    except (pexpect.EOF, pexpect.TIMEOUT):
        result = avendesora.before.decode('utf8')
    except OSError as err:
        result = os_error(err)
    assert result.strip() == 'underdog crossword apron whinny'
Beispiel #39
0
    def render(self):
        try:
            contents = self.contents.render()
        except AttributeError:
            contents = self.contents
        try:
            path = to_path(self.path)
            path.write_text(contents)
            path.chmod(self.mode)
        except OSError as e:
            raise PasswordError(os_error(e))

        return 'Contents written to {}.'.format(str(path))
Beispiel #40
0
 def differ(self):
     try:
         with open(self.file1) as f:
             lcontents = f.read()
         with open(self.file2) as f:
             rcontents = f.read()
         return lcontents != rcontents
     except OSError as e:
         raise Error(os_error(e))
     except:
         # Any other errors, just assume files differ and move on.
         # Unicode errors can occur on old versions of CentOS.
         return True
Beispiel #41
0
def test_alertscc_seed():
    try:
        avendesora = pexpect.spawn('avendesora', 'value -S -s alertscc password'.split())
        avendesora.expect('seed for alertscc: ', timeout=4)
        avendesora.sendline('frozen-chaos')
        avendesora.expect(pexpect.EOF)
        avendesora.close()
        result = avendesora.before.decode('utf-8')
    except (pexpect.EOF, pexpect.TIMEOUT):
        result = avendesora.before.decode('utf8')
    except OSError as err:
        result = os_error(err)
    assert result.strip() == 'tRT7vXLeZrbz'
Beispiel #42
0
def test_version():
    try:
        result = subprocess.check_output("avendesora help version".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Display Avendesora version.

        Usage:
            avendesora version
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #43
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))
Beispiel #44
0
def get_psf_filename(psf_file):
    if not psf_file:
        try:
            with open(saved_psf_file_filename) as f:
                psf_file = f.read().strip()
            display('Using PSF file:', psf_file)
        except OSError:
            fatal('missing PSF file name.')
    try:
        with open(saved_psf_file_filename, 'w') as f:
            f.write(psf_file)
    except OSError as e:
        warn(os_error(e))
    return psf_file
Beispiel #45
0
def main():
    """
    Construct, encrypt, and publish backup keys.  

    As the primary entry point for the end user, this function is also 
    responsible for integrating information from command-line arguments, 
    configuration files, and `setuptools` plugins.
    """
    set_shlib_prefs(use_inform=True, log_cmd=True)
    args = docopt.docopt(__doc__)

    if args['--verbose']:
        set_output_prefs(verbose=True, narrate=True)
    elif args['--quiet']:
        set_output_prefs(quiet=True)

    try:
        config_path, config = load_config()
        try:
            if args['plugins']:
                list_plugins(config)
                sys.exit()

            # Get the passcode before building the archive, so if something
            # goes wrong with the passcode, we don't need to worry about
            # cleaning up the unencrypted archive.
            passcode = query_passcode(config)
            batch = args['--yes'] or args['--quiet']
            archive = build_archive(config, not batch)
            encrypt_archive(config, archive, passcode)
            publish_archive(config, archive)

        except ConfigError as e:
            e.reraise(culprit=config_path)
        finally:
            if 'archive' in locals():
                delete_archive(config, archive)

    except KeyboardInterrupt:
        print()

    except Error as e:
        if args['--verbose']: raise
        else: e.report()

    except OSError as e:
        fatal(os_error(e))

    terminate()
Beispiel #46
0
def test_help():
    try:
        result = subprocess.check_output("avendesora help help".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Give information about commands or other topics.

        Usage:
            avendesora help [<topic>]
            avendesora h    [<topic>]
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #47
0
def test_value():
    try:
        result = subprocess.check_output("avendesora help value".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Show an account value.

        Produce an account value. If the value is secret, it is produced only
        temporarily unless --stdout is specified.

        Usage:
            avendesora value [options] [--stdout | --clipboard] [<account> [<field>]]
            avendesora val   [options] [--stdout | --clipboard] [<account> [<field>]]
            avendesora v     [options] [--stdout | --clipboard] [<account> [<field>]]

        Options:
            -c, --clipboard         Write output to clipboard rather than stdout.
            -s, --stdout            Write output to the standard output without
                                    any annotation or protections.
            -S, --seed              Interactively request additional seed for
                                    generated secrets.
            -v, --verbose           Add additional information to log file to
                                    help identify issues in account discovery.
            -t <title>, --title <title>
                                    Use account discovery on this title.

        You would request a scalar value by specifying its name after the
        account. For example:

            avendesora value pin

        If is a composite value, you should also specify a key that indicates
        which of the composite values you want. For example, if the 'accounts'
        field is a dictionary, you would specify accounts.checking or
        accounts[checking] to get information on your checking account. If the
        value is an array, you would give the index of the desired value. For
        example, questions.0 or questions[0]. If no value is requested, the
        passcode value is returned (this can be changed by specifying
        'default_field' in the account or in the config file).  If you only
        specify a number, then the name is assumed to be 'questions', as in the
        list of security questions (this can be changed by specifying the
        desired name as the 'default_vector_field' in the account or the config
        file).
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #48
0
    def __init__(self, path):
        # find the dictionary, initially look in the settings directory
        if not path.exists():
            # if not there look in install directory
            from pkg_resources import resource_filename
            path = to_path(resource_filename(__name__, 'words'))

        # open the dictionary
        try:
            contents= path.read_text()
        except OSError as err:
            error(os_error(err))
            contents = ''

        self.hash = hashlib.md5(contents.encode('utf-8')).hexdigest()
        self.words = contents.split()
Beispiel #49
0
 def create(self, contents):
     path = self.path
     try:
         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)
         # file is not encrypted
         with path.open("wb") as f:
             f.write(contents.encode("utf-8"))
     except OSError as err:
         raise Error(os_error(err))
Beispiel #50
0
def test_abraxas():
    try:
        result = subprocess.check_output("avendesora help abraxas".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Avendesora generalizes and replaces Abraxas, its predecessor.  To
        transition from Abraxas to Avendesora, you will first need to
        upgrade Abraxas to version 1.8 or higher (use 'abraxas -v' to
        determine version). Then run:

            abraxas --export

        It will create a collection of Avendesora accounts files in
        ~/.config/abraxas/avendesora. You need to manually add these files
        to your list of accounts files in Avendesora. Say one such file in
        created: ~/.config/abraxas/avendesora/accounts.gpg.  This could be
        added to Avendesora as follows:

        1. create a symbolic link from
           ~/.config/avendesora/abraxas_accounts.gpg to
           ~/.config/abraxas/avendesora/accounts.gpg:

            cd ~/.config/avendesora
            ln -s ../abraxas/avendesora/accounts.gpg abraxas_accounts.gpg

        2. add abraxas_accounts.gpg to account_files list in .accounts_files.

        Now all of the Abraxas accounts contained in abraxas_accounts.gpg
        should be available though Avendesora and the various features of
        the account should operate as expected. However, secrets in accounts
        exported by Abraxas are no longer generated secrets. Instead, the
        actual secrets are placed in a hidden form in the exported accounts
        files.

        If you would like to enhance the imported accounts to take advantage
        of the new features of Avendesora, it is recommended that you do not
        manually modify the imported files. Instead, copy the account
        information to one of your own account files before modifying it.
        To avoid conflict, you must then delete the account from the
        imported file. To do so, create ~/.config/abraxas/do-not-export if
        it does not exist, then add the account name to this file, and
        reexport your accounts from Abraxas.
    """
    ).strip()
    assert result == bytes(expected, encoding="utf8")
Beispiel #51
0
    def _validate_components(self):
        from pkg_resources import resource_filename

        # check permissions on the settings directory
        path = get_setting('settings_dir')
        mask = get_setting('config_dir_mask')
        try:
            permissions = getmod(path)
        except FileNotFoundError:
            raise PasswordError('missing, must run initialize.', culprit=path)
        violation = permissions & mask
        if violation:
            recommended = permissions & ~mask & 0o777
            warn("directory permissions are too loose.", culprit=path)
            codicil("Recommend running: chmod {:o} {}".format(
                recommended, path))

        # Check that files that are critical to the integrity of the generated
        # secrets have not changed
        for path, kind in [
            (to_path(resource_filename(__name__,
                                       'secrets.py')), 'secrets_hash'),
            (to_path(resource_filename(__name__,
                                       'charsets.py')), 'charsets_hash'),
            ('default', 'dict_hash'),
            ('mnemonic', 'mnemonic_hash'),
        ]:
            try:
                contents = path.read_text()
            except AttributeError:
                contents = '\n'.join(Dictionary(path).get_words())
            except OSErrors as e:
                raise PasswordError(os_error(e))
            md5 = hashlib.md5(contents.encode('utf-8')).hexdigest()
            # Check that file has not changed.
            if md5 != get_setting(kind):
                warn("file contents have changed.", culprit=path)
                lines = wrap(
                    dedent("""\
                        This could result in passwords that are inconsistent with
                        those created in the past.  Use 'avendesora changed' to
                        assure that nothing has changed. Then, to suppress this
                        message, change {hashes} to contain:
                    """.format(hashes=get_setting('hashes_file'))))
                lines.append("     {kind} = '{md5}'".format(kind=kind,
                                                            md5=md5))
                codicil(*lines, sep='\n')
Beispiel #52
0
def test_search():
    try:
        result = subprocess.check_output("avendesora help search".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Search accounts.

        Search for accounts whose values contain the search text.

        Usage:
            avendesora search <text>
            avendesora s      <text>
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #53
0
def test_find():
    try:
        result = subprocess.check_output("avendesora help find".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Find an account.

        Find accounts whose name contains the search text.

        Usage:
            avendesora find <text>
            avendesora f    <text>
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #54
0
def test_conceal():
    try:
        result = subprocess.check_output("avendesora help conceal".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Conceal text by encoding it.

        Usage:
            avendesora [options] conceal [<text>]
            avendesora [options] c       [<text>]

        Options:
            -e <encoding>, --encoding <encoding>
                                    Encoding used when concealing information.
            -s, --symmetric         Encrypt with a passphrase rather than using your
                                    GPG key (only appropriate for gpg encodings).

        Possible encodings include (default encoding is base64):

        base64:
            This encoding obscures but does not encrypt the text. It can
            protect text from observers that get a quick glance of the
            encoded text, but if they are able to capture it they can easily
            decode it.

        gpg:
            This encoding fully encrypts/decrypts the text with GPG key.
            By default your GPG key is used, but you can specify symmetric
            encryption, in which case a passphrase is used.

        scrypt:
            This encoding fully encrypts the text with your user key. Only
            you can decrypt it, secrets encoded with scrypt cannot be
            shared.

        Though available as an option for convenience, you should not pass
        the text to be hidden as an argument as it is possible for others to
        examine the commands you run and their argument list. For any
        sensitive secret, you should simply run 'avendesora conceal' and
        then enter the secret text when prompted.
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #55
0
def test_stealth():
    try:
        result = subprocess.check_output("avendesora help stealth".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Normally Avendesora uses information from an account that is
        contained in an account file to generate the secrets for that
        account. In some cases, the presence of the account itself, even
        though it is contained within an encrypted file can be problematic.
        The mere presence of an encrypted file may result in you being
        compelled to open it. For the most damaging secrets, it is best if
        there is no evidence that the secret exists at all. This is the
        purpose of stealth accounts. (Misdirection is an alternative to
        stealth accounts; see 'avendesora help misdirection').

        Generally one uses the predefined stealth accounts, which all  have
        names that are descriptive of the form of the secret they generate,
        for example Word6 generates a 6-word pass phrase. The predefined
        accounts are kept in ~/.config/avendesora/stealth_accounts.

        Stealth accounts are subclasses of the StealthAccount class. These
        accounts differ from normal accounts in that they do not contribute
        the account name to the secrets generators for use as a seed.
        Instead, the user is requested to provide the account name every
        time the secret is generated. The secret depends strongly
        on this account name, so it is essential you give precisely the same
        name each time. The term 'account name' is being use here, but you
        can enter any text you like.  Best to make this text very difficult
        to guess if you are concerned about being compelled to disclose your
        GPG keys.

        The secret generator will combine the account name with the master
        password before generating the secret. This allows you to use simple
        predictable account names and still get an unpredictable secret.
        The master password used is taken from master_password in the file
        that contains the stealth account if it exists, or the users key if
        it does not. By default the stealth accounts file does not contain a
        master password, which makes it difficult to share stealth accounts.
        You can create additional stealth account files that do contain
        master passwords that you can share with your associates.
    """
    ).strip()
    assert result == bytes(expected, encoding="utf8")
Beispiel #56
0
def test_values():
    try:
        result = subprocess.check_output("avendesora help values".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Display all account values.

        Show all account values.

        Usage:
            avendesora values <account>
            avendesora vals   <account>
            avendesora V      <account>
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #57
0
def test_misdirection():
    try:
        result = subprocess.check_output("avendesora help misdirection".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        One way to avoid being compelled to disclose a secret is to disavow
        any knowledge of the secret.  However, the presence of an account in
        Avendesora that pertains to that secret undercuts this argument.
        This is the purpose of stealth accounts. They allow you to generate
        secrets for accounts for which Avendesora has no stored information.
        In this case Avendesora ask you for the minimal amount of
        information that it needs to generate the secret. However in some
        cases, the amount of information that must be retained is simply too
        much to keep in your head. In that case another approach, referred
        to as secret misdirection, can be used.

        With secret misdirection, you do not disavow any knowledge of the
        secret, instead you say your knowledge is out of date. So you would
        say something like "I changed the password and then forgot it", or
        "The account is closed". To support this ruse, you must use the
        --seed (or -S) option to 'avendsora value' when generating your
        secret (secrets misdirection only works with generated passwords,
        not stored passwords). This causes Avendesora to ask you for an
        additional seed at the time you request the secret. If you do not
        use --seed or you do and give the wrong seed, you will get a
        different value for your secret.  In effect, using --seed when
        generating the original value of the secret cause Avendesora to
        generate the wrong secret by default, allowing you to say "See, I
        told you it would not work". But when you want it to work, you just
        interactively provide the right additional seed.

        You would typically only use misdirection for secrets you are
        worried about being compelled to disclose. So it behooves you to use
        an unpredictable additional seed for these secrets to reduce the
        chance someone could guess it.

        Be aware that when you employ misdirection on a secret, the value of
        the secret stored in in the archive will not be the true value, it
        will instead be the misdirected value.
    """
    ).strip()
    assert result == bytes(expected, encoding="utf8")
Beispiel #58
0
def get_argv():
    argv = sys.argv[1:]
    if argv:
        # save the command line arguments for next time
        try:
            with open(saved_arguments_filename, 'w') as f:
                args = [a for a in argv if a not in ['-c', '--no-cache']]
                f.write('\n'.join(args))
        except OSError as e:
            warn(os_error(e))
    else:
        # command line arguments not give, reuse previous ones
        try:
            with open(saved_arguments_filename) as f:
                argv = f.read().split('\n')
            display('Using command:', ' '.join(argv))
        except OSError:
            done()
    return argv
Beispiel #59
0
def test_edit():
    try:
        result = subprocess.check_output("avendesora help edit".split())
    except OSError as err:
        result = os_error(err)
    expected = dedent(
        """
        Edit an account.

        Usage:
            avendesora edit <account>
            avendesora e    <account>

        Opens an existing account in your editor.

        You can specify the editor by changing the 'edit_account' setting in
        the config file (~/.config/avendesora/config).
    """
    ).strip()
    assert result == bytes(expected, encoding="ascii")
Beispiel #60
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        mount_point = cmdline['<mount_point>']

        # run borg
        try:
            settings.run_borg(
                cmd='umount',
                args=[mount_point],
                emborg_opts=options,
            )
            try:
                to_path(mount_point).rmdir()
            except OSError as e:
                warn(os_error(e))
        except Error as e:
            if 'busy' in str(e):
                e.reraise(
                    codicil=
                    f"Try running 'lsof +D {mount_point}' to find culprit.")