123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- #!/usr/bin/env python3
- # PYTHON_ARGCOMPLETE_OK
- import hashlib
- import os
- import shlex
- import subprocess
- import tempfile
- import time
- import urllib.parse
- import yaml
- def command_join(args):
- return ' '.join([shlex.quote(a) for a in args])
- def sshfs_mount(url, path):
- url_attr = urllib.parse.urlparse(url)
- assert url_attr.scheme in ['sftp']
- mount_command = [
- 'sshfs',
- '{}:{}'.format(url_attr.netloc, url_attr.path),
- path,
- ]
- print('+ {}'.format(command_join(mount_command)))
- subprocess.check_call(mount_command)
- def sshfs_unmount(path):
- unmount_command = [
- 'fusermount',
- '-u',
- path,
- ]
- print('+ {}'.format(command_join(unmount_command)))
- subprocess.check_call(unmount_command)
- def backup(config, no_print_statistics, tab_dry):
- for backup in config:
- print(yaml.dump({"backup": backup}, default_flow_style = False))
- backup_command = ['duplicity']
- # encryption
- try:
- encryption = backup['encryption']
- except KeyError:
- encryption = True
- if 'encryption' in backup and not backup['encryption']:
- backup_command += ['--no-encryption']
- else:
- if 'encrypt_key' in backup:
- backup_command += ['--encrypt-key', backup['encrypt_key']]
- # selectors
- try:
- selectors = backup['selectors']
- except KeyError:
- selectors = []
- for selector in selectors:
- if selector['option'] in ['include', 'exclude']:
- backup_command += ['--{}'.format(selector['option']), selector['shell_pattern']]
- else:
- raise Exception("unsupported selector option '{}'".format(selector['option']))
- # statistics
- if no_print_statistics:
- backup_command.append('--no-print-statistics')
- # source path
- backup_command += [backup['source_path']]
- # target path
- try:
- target_mount_path = None
- if 'target_via_sshfs' in backup and backup['target_via_sshfs']:
- target_mount_path = tempfile.mkdtemp(prefix = 'duplitab-target-sshfs-')
- backup_command += ['file://' + target_mount_path]
- sshfs_mount(backup['target_url'], target_mount_path)
- # set backup name to make archive dir persistent
- # (default name: hash of target url)
- backup_command += ['--name', hashlib.sha1(backup['target_url'].encode('utf-8')).hexdigest()]
- else:
- backup_command += [backup['target_url']]
- try:
- if tab_dry:
- print('* {}'.format(command_join(backup_command)))
- else:
- print('+ {}'.format(command_join(backup_command)))
- subprocess.check_call(backup_command)
- finally:
- if target_mount_path:
- try:
- sshfs_unmount(target_mount_path)
- except subprocess.CalledProcessError:
- time.sleep(1)
- sshfs_unmount(target_mount_path)
- finally:
- if target_mount_path:
- os.rmdir(target_mount_path)
- def run(command, config_path, no_print_statistics, tab_dry):
- with open(config_path) as config_file:
- config = yaml.load(config_file.read())
- if command == 'backup':
- backup(
- config = config,
- no_print_statistics = no_print_statistics,
- tab_dry = tab_dry,
- )
- def _init_argparser():
- import argparse
- argparser = argparse.ArgumentParser(description = None)
- argparser.add_argument(
- '-c',
- '--config',
- dest = 'config_path',
- default = '/etc/duplitab',
- )
- subparsers = argparser.add_subparsers(
- dest = 'command',
- )
- subparser_backup = subparsers.add_parser('backup')
- subparser_backup.add_argument(
- '--no-print-statistics',
- action = 'store_true',
- )
- subparser_backup.add_argument(
- '--tab-dry',
- action = 'store_true',
- )
- return argparser
- def main(argv):
- argparser = _init_argparser()
- try:
- import argcomplete
- argcomplete.autocomplete(argparser)
- except ImportError:
- pass
- args = argparser.parse_args(argv)
- run(**vars(args))
- return 0
- if __name__ == "__main__":
- import sys
- sys.exit(main(sys.argv[1:]))
|