A wrapper script for Borg backup software that creates and prunes backups
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

create.py 5.5KB

  1. import glob
  2. import itertools
  3. import logging
  4. import os
  5. import subprocess
  6. import tempfile
  7. logger = logging.getLogger(__name__)
  8. def _expand_directory(directory):
  9. '''
  10. Given a directory path, expand any tilde (representing a user's home directory) and any globs
  11. therein. Return a list of one or more resulting paths.
  12. '''
  13. expanded_directory = os.path.expanduser(directory)
  14. return glob.glob(expanded_directory) or [expanded_directory]
  15. def _expand_directories(directories):
  16. '''
  17. Given a sequence of directory paths, expand tildes and globs in each one. Return all the
  18. resulting directories as a single flattened tuple.
  19. '''
  20. if directories is None:
  21. return ()
  22. return tuple(
  23. itertools.chain.from_iterable(_expand_directory(directory) for directory in directories)
  24. )
  25. def _write_pattern_file(patterns=None):
  26. '''
  27. Given a sequence of patterns, write them to a named temporary file and return it. Return None
  28. if no patterns are provided.
  29. '''
  30. if not patterns:
  31. return None
  32. pattern_file = tempfile.NamedTemporaryFile('w')
  33. pattern_file.write('\n'.join(patterns))
  34. pattern_file.flush()
  35. return pattern_file
  36. def _make_pattern_flags(location_config, pattern_filename=None):
  37. '''
  38. Given a location config dict with a potential pattern_from option, and a filename containing any
  39. additional patterns, return the corresponding Borg flags for those files as a tuple.
  40. '''
  41. pattern_filenames = tuple(location_config.get('patterns_from') or ()) + (
  42. (pattern_filename,) if pattern_filename else ()
  43. )
  44. return tuple(
  45. itertools.chain.from_iterable(
  46. ('--patterns-from', pattern_filename) for pattern_filename in pattern_filenames
  47. )
  48. )
  49. def _make_exclude_flags(location_config, exclude_filename=None):
  50. '''
  51. Given a location config dict with various exclude options, and a filename containing any exclude
  52. patterns, return the corresponding Borg flags as a tuple.
  53. '''
  54. exclude_filenames = tuple(location_config.get('exclude_from') or ()) + (
  55. (exclude_filename,) if exclude_filename else ()
  56. )
  57. exclude_from_flags = tuple(
  58. itertools.chain.from_iterable(
  59. ('--exclude-from', exclude_filename) for exclude_filename in exclude_filenames
  60. )
  61. )
  62. caches_flag = ('--exclude-caches',) if location_config.get('exclude_caches') else ()
  63. if_present = location_config.get('exclude_if_present')
  64. if_present_flags = ('--exclude-if-present', if_present) if if_present else ()
  65. return exclude_from_flags + caches_flag + if_present_flags
  66. def create_archive(
  67. dry_run,
  68. repository,
  69. location_config,
  70. storage_config,
  71. local_path='borg',
  72. remote_path=None,
  73. progress=False,
  74. json=False,
  75. ):
  76. '''
  77. Given vebosity/dry-run flags, a local or remote repository path, a location config dict, and a
  78. storage config dict, create a Borg archive.
  79. '''
  80. sources = _expand_directories(location_config['source_directories'])
  81. pattern_file = _write_pattern_file(location_config.get('patterns'))
  82. exclude_file = _write_pattern_file(_expand_directories(location_config.get('exclude_patterns')))
  83. checkpoint_interval = storage_config.get('checkpoint_interval', None)
  84. chunker_params = storage_config.get('chunker_params', None)
  85. compression = storage_config.get('compression', None)
  86. remote_rate_limit = storage_config.get('remote_rate_limit', None)
  87. umask = storage_config.get('umask', None)
  88. lock_wait = storage_config.get('lock_wait', None)
  89. files_cache = location_config.get('files_cache')
  90. default_archive_name_format = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
  91. archive_name_format = storage_config.get('archive_name_format', default_archive_name_format)
  92. full_command = (
  93. (
  94. local_path,
  95. 'create',
  96. '{repository}::{archive_name_format}'.format(
  97. repository=repository, archive_name_format=archive_name_format
  98. ),
  99. )
  100. + sources
  101. + _make_pattern_flags(location_config, pattern_file.name if pattern_file else None)
  102. + _make_exclude_flags(location_config, exclude_file.name if exclude_file else None)
  103. + (('--checkpoint-interval', str(checkpoint_interval)) if checkpoint_interval else ())
  104. + (('--chunker-params', chunker_params) if chunker_params else ())
  105. + (('--compression', compression) if compression else ())
  106. + (('--remote-ratelimit', str(remote_rate_limit)) if remote_rate_limit else ())
  107. + (('--one-file-system',) if location_config.get('one_file_system') else ())
  108. + (('--read-special',) if location_config.get('read_special') else ())
  109. + (('--nobsdflags',) if location_config.get('bsd_flags') is False else ())
  110. + (('--files-cache', files_cache) if files_cache else ())
  111. + (('--remote-path', remote_path) if remote_path else ())
  112. + (('--umask', str(umask)) if umask else ())
  113. + (('--lock-wait', str(lock_wait)) if lock_wait else ())
  114. + (('--list', '--filter', 'AME-') if logger.isEnabledFor(logging.INFO) else ())
  115. + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
  116. + (('--stats',) if not dry_run and logger.isEnabledFor(logging.INFO) else ())
  117. + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
  118. + (('--dry-run',) if dry_run else ())
  119. + (('--progress',) if progress else ())
  120. + (('--json',) if json else ())
  121. )
  122. logger.debug(' '.join(full_command))
  123. subprocess.check_call(full_command)