borgmatic.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. from argparse import ArgumentParser
  2. import logging
  3. import os
  4. from subprocess import CalledProcessError
  5. import sys
  6. from borgmatic.borg import check, create, prune
  7. from borgmatic.commands import hook
  8. from borgmatic.config import collect, convert, validate
  9. from borgmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS, verbosity_to_log_level
  10. logger = logging.getLogger(__name__)
  11. LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
  12. def parse_arguments(*arguments):
  13. '''
  14. Given command-line arguments with which this script was invoked, parse the arguments and return
  15. them as an ArgumentParser instance.
  16. '''
  17. parser = ArgumentParser(
  18. description=
  19. '''
  20. A simple wrapper script for the Borg backup software that creates and prunes backups.
  21. If none of the --prune, --create, or --check options are given, then borgmatic defaults
  22. to all three: prune, create, and check archives.
  23. '''
  24. )
  25. parser.add_argument(
  26. '-c', '--config',
  27. nargs='+',
  28. dest='config_paths',
  29. default=collect.DEFAULT_CONFIG_PATHS,
  30. help='Configuration filenames or directories, defaults to: {}'.format(' '.join(collect.DEFAULT_CONFIG_PATHS)),
  31. )
  32. parser.add_argument(
  33. '--excludes',
  34. dest='excludes_filename',
  35. help='Deprecated in favor of exclude_patterns within configuration',
  36. )
  37. parser.add_argument(
  38. '-p', '--prune',
  39. dest='prune',
  40. action='store_true',
  41. help='Prune archives according to the retention policy',
  42. )
  43. parser.add_argument(
  44. '-C', '--create',
  45. dest='create',
  46. action='store_true',
  47. help='Create archives (actually perform backups)',
  48. )
  49. parser.add_argument(
  50. '-k', '--check',
  51. dest='check',
  52. action='store_true',
  53. help='Check archives for consistency',
  54. )
  55. parser.add_argument(
  56. '-v', '--verbosity',
  57. type=int,
  58. help='Display verbose progress (1 for some, 2 for lots)',
  59. )
  60. args = parser.parse_args(arguments)
  61. # If any of the three action flags in the given parse arguments have been explicitly requested,
  62. # leave them as-is. Otherwise, assume defaults: Mutate the given arguments to enable all the
  63. # actions.
  64. if not args.prune and not args.create and not args.check:
  65. args.prune = True
  66. args.create = True
  67. args.check = True
  68. return args
  69. def main(): # pragma: no cover
  70. try:
  71. args = parse_arguments(*sys.argv[1:])
  72. logging.basicConfig(level=verbosity_to_log_level(args.verbosity), format='%(message)s')
  73. config_filenames = tuple(collect.collect_config_filenames(args.config_paths))
  74. logger.debug('Ensuring legacy configuration is upgraded')
  75. convert.guard_configuration_upgraded(LEGACY_CONFIG_PATH, config_filenames)
  76. if len(config_filenames) == 0:
  77. raise ValueError('Error: No configuration files found in: {}'.format(' '.join(args.config_paths)))
  78. for config_filename in config_filenames:
  79. logger.info('{}: Parsing configuration file'.format(config_filename))
  80. config = validate.parse_configuration(config_filename, validate.schema_filename())
  81. (location, storage, retention, consistency, hooks) = (
  82. config.get(section_name, {})
  83. for section_name in ('location', 'storage', 'retention', 'consistency', 'hooks')
  84. )
  85. remote_path = location.get('remote_path')
  86. try:
  87. create.initialize(storage)
  88. hook.execute_hook(hooks.get('before_backup'))
  89. for repository in location['repositories']:
  90. if args.prune:
  91. logger.info('{}: Pruning archives'.format(repository))
  92. prune.prune_archives(args.verbosity, repository, retention, remote_path=remote_path)
  93. if args.create:
  94. logger.info('{}: Creating archive'.format(repository))
  95. create.create_archive(
  96. args.verbosity,
  97. repository,
  98. location,
  99. storage,
  100. )
  101. if args.check:
  102. logger.info('{}: Running consistency checks'.format(repository))
  103. check.check_archives(args.verbosity, repository, consistency, remote_path=remote_path)
  104. hook.execute_hook(hooks.get('after_backup'))
  105. except (OSError, CalledProcessError):
  106. hook.execute_hook(hooks.get('on_error'))
  107. raise
  108. except (ValueError, OSError, CalledProcessError) as error:
  109. print(error, file=sys.stderr)
  110. sys.exit(1)