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)
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
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())
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("__")}
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))
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.')
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.")
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
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)
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))
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)
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)
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)
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)
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
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))
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)
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 )
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
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
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
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)
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))
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)
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}'.")
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())
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.")
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)
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)
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)
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)
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))
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')
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
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.")
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
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.'))
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
def execute(cls, name, args, settings, options): narrate('{}:'.format(name)) cls.run(name, args if args else [], settings, options)