Compare commits

..
56 Commits
Author SHA1 Message Date
Dan Helfman 604b3d5e17 Bump version. 2020-11-21 13:56:19 -08:00
Dan Helfman 667e1e5b15 Update document about new --override behavior (#361). 2020-11-19 11:01:53 -08:00
Dan Helfman 9b819f32f8 Fix traceback when upgrading old INI-style configuration with upgrade-borgmatic-config (#367). 2020-11-18 15:01:10 -08:00
Dan Helfman b619bde037 Fix broken end-to-end test. 2020-11-18 14:20:07 -08:00
Dan Helfman 97af16bd86 Add Lima-Labs to examples. 2020-11-18 12:05:38 -08:00
Dan Helfman fa75f89acc Merge ssh://projects.torsion.org:3022/witten/borgmatic 2020-11-18 11:54:44 -08:00
Dan Helfman 222b61b577 Update changelog with note about #369. 2020-11-18 11:26:20 -08:00
Dan Helfman e77757f0fd Mention placeholders for repositories in documentation; fix #369
Reviewed-on: https://projects.torsion.org/witten/borgmatic/pulls/371
2020-11-18 19:24:59 +00:00
François PoulainandCliss XXI - dev ebac02f118 Mention placeholders for repositories in documentation; fix #369 2020-11-18 20:22:41 +01:00
Dan Helfman 1c9ae81987 Fix signal forwarding from borgmatic to Borg resulting in recursion traceback (#368). 2020-11-18 10:29:03 -08:00
Dan Helfman 7b1fb68c18 Add referral link to Lima-Labs. 2020-11-17 14:04:24 -08:00
Dan Helfman 8aa7830f0d Fix broken "--override" action in Python 3.7 and below. 2020-11-15 13:39:15 -08:00
Dan HelfmanandGitHub 79bee755ee Merge pull request #37 from ThorpeJosh/patch-1
Fix typo in large backup docs.
2020-11-03 22:22:12 -08:00
Josh ThorpeandGitHub cde0ee96ff Fix typo in large backup docs 2020-11-04 17:20:32 +11:00
Dan Helfman 1ea04aedf0 Merge override values when specifying the "--override" flag multiple times (#361). 2020-10-23 16:06:00 -07:00
Dan HelfmanandGitHub 446a2bc15a Fixed minor typo in help for "info" command. 2020-10-11 15:08:43 -07:00
Diego Blanco 2d10e758e0 Fixed minor typo in help for "info" command 2020-10-12 00:04:15 +02:00
Dan Helfman 0e978299cf Fix traceback when a database hook value is null in a configuration file (#355). 2020-10-07 15:41:45 -07:00
Dan Helfman d06c1f2943 Minor edit. 2020-09-21 10:00:38 -07:00
Dan Helfman d768b50b97 Add note about empty source_directories.
Reviewed-on: https://projects.torsion.org/witten/borgmatic/pulls/358
2020-09-21 16:59:18 +00:00
Luke Murphy 034ade48f2 Add note about empty source_directories 2020-09-21 17:35:26 +02:00
Dan Helfman d1e9f74087 Restore referal link. 2020-08-27 15:21:53 -07:00
Dan Helfman f262f77dbd Add temporary_directory option to changelog. 2020-08-26 09:51:55 -07:00
Dan Helfman a3387953a9 Add configuration option for settings Borg's temporary directory (#341).
Reviewed-on: https://projects.torsion.org/witten/borgmatic/pulls/356
2020-08-26 16:54:49 +00:00
root 7cad5a8608 Add temporary_directory as a configuration option defaulting to $TMPDIR 2020-08-26 15:29:02 +00:00
Dan Helfman 9b83fcbf06 Add comment about MemoryDenyWriteExecute value and the tradeoffs thereof. 2020-08-23 14:11:19 -07:00
Dan Helfman 32a93ce8a2 Loosen systemd memory security setting to allow Healthchecks ping. 2020-08-22 13:37:34 -07:00
Dan Helfman e428329c03 Add systemd security improvement to NEWS. 2020-08-22 09:49:06 -07:00
Dan Helfman e844bbee15 Improve systemd security settings (#352). 2020-08-22 16:50:06 +00:00
palto42 631c3068a9 systemd security settings 2020-08-22 15:41:25 +02:00
Dan Helfman 79d4888e22 Add documentation navigation links on left side of all documentation pages. 2020-08-21 14:27:47 -07:00
Dan Helfman de61fdef48 Mention "before_extract"/"after_extract" in README. 2020-08-12 14:12:58 -07:00
Dan Helfman 93caeba200 Add before_extract and after_extract hooks (#347).
Reviewed-on: https://projects.torsion.org/witten/borgmatic/pulls/347
2020-08-12 21:14:15 +00:00
networkjanitor 3c723e8d99 Merge branch 'master' into master 2020-08-12 21:11:45 +00:00
networkjanitor c5776447b9 fixed description strings for before_extract and after_extract 2020-08-12 23:07:57 +02:00
Dan Helfman 5356f487a5 Move before/after_everything hooks so they're easier to find. 2020-08-12 13:46:31 -07:00
Dan Helfman 72bd96c656 Fix traceback when a configuration directory is non-readable due to directory permissions (#350). 2020-08-12 11:32:00 -07:00
Dan Helfman f611fe7be3 Clarify documentation overview of monitoring options. 2020-08-12 10:40:39 -07:00
Dan Helfman dd6ea40a36 Clarify encryption section: It's not just for cron users. 2020-08-10 14:43:03 -07:00
Dan Helfman ea1274d1c6 Remove inactive referral link. 2020-08-07 10:55:05 -07:00
Dan HelfmanandGitHub 8526468975 Fix configuration schema comment on after_everything option. 2020-08-07 09:49:25 -07:00
Jakub Duchateau 95c415f416 Change "before" to "after" in desc 2020-08-07 16:41:35 +02:00
Dan Helfman 06dc336481 Fix schema exclude patterns example path. 2020-08-06 09:59:48 -07:00
networkjanitor 893fca2816 Add before_extract and after_extract hooks 2020-08-01 16:08:32 +02:00
Dan Helfman 99590cb6b6 Clarify documentation on configuration overrides, specifically the portion about list syntax. 2020-07-23 21:33:42 -07:00
Dan Helfman b3fd1be5f6 Add "borgmatic export-tar" action to export an archive to a tar-formatted file or stream (#300). 2020-07-22 21:53:55 -07:00
Dan Helfman a23083f737 Clarify schema documentation about skipping pruning. 2020-07-22 10:35:16 -07:00
Dan Helfman 8306b758e8 Add documentation on how to make backups redundant with multiple repositories. 2020-07-17 16:00:50 -07:00
Dan Helfman 218cbd5289 In database documentation, include command for finding named pipes and special devices. 2020-07-13 10:56:59 -07:00
Dan Helfman 2ac58670d5 Clarify database documentation about excluding named pipes and character/block devices to prevent hangs. 2020-07-13 10:45:58 -07:00
Dan Helfman 6f82c9979b Add #339 to NEWS and add test. 2020-07-07 22:31:17 -07:00
Dan Helfman 0a659a397f Attempt to fix no-repro test failure of log_outputs() (#339). 2020-07-07 22:05:53 -07:00
Dan Helfman 2781873faf Bump version for release. 2020-06-30 21:47:34 -07:00
Dan Helfman 3aaa89fb08 Formatting. 2020-06-25 20:25:29 -07:00
Dan Helfman 35d542a676 Fix for traceback when running Cronitor, Cronhub, and PagerDuty monitor hooks (#336). 2020-06-25 20:23:25 -07:00
Dan Helfman d0b9c436b1 Bump version for release. 2020-06-23 11:21:43 -07:00
50 changed files with 1324 additions and 251 deletions
+2
View File
@@ -1,9 +1,11 @@
const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const inclusiveLangPlugin = require("@11ty/eleventy-plugin-inclusive-language");
const navigationPlugin = require("@11ty/eleventy-navigation");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(pluginSyntaxHighlight);
eleventyConfig.addPlugin(inclusiveLangPlugin);
eleventyConfig.addPlugin(navigationPlugin);
let markdownIt = require("markdown-it");
let markdownItAnchor = require("markdown-it-anchor");
+34 -1
View File
@@ -1,4 +1,37 @@
1.5.7.dev0
1.5.12
* Fix for previous release with incorrect version suffix in setup.py. No other changes.
1.5.11
* #341: Add "temporary_directory" option for changing Borg's temporary directory.
* #352: Lock down systemd security settings in sample systemd service file.
* #355: Fix traceback when a database hook value is null in a configuration file.
* #361: Merge override values when specifying the "--override" flag multiple times. The previous
behavior was to take the value of the last "--override" flag only.
* #367: Fix traceback when upgrading old INI-style configuration with upgrade-borgmatic-config.
* #368: Fix signal forwarding from borgmatic to Borg resulting in recursion traceback.
* #369: Document support for Borg placeholders in repository names.
1.5.10
* #347: Add hooks that run for the "extract" action: "before_extract" and "after_extract".
* #350: Fix traceback when a configuration directory is non-readable due to directory permissions.
* Add documentation navigation links on left side of all documentation pages.
* Clarify documentation on configuration overrides, specifically the portion about list syntax:
http://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#configuration-overrides
* Clarify documentation overview of monitoring options:
http://torsion.org/borgmatic/docs/how-to/monitor-your-backups/
1.5.9
* #300: Add "borgmatic export-tar" action to export an archive to a tar-formatted file or stream.
* #339: Fix for intermittent timing-related test failure of logging function.
* Clarify database documentation about excluding named pipes and character/block devices to prevent
hangs.
* Add documentation on how to make backups redundant with multiple repositories:
https://torsion.org/borgmatic/docs/how-to/make-backups-redundant/
1.5.8
* #336: Fix for traceback when running Cronitor, Cronhub, and PagerDuty monitor hooks.
1.5.7
* #327: Fix broken pass-through of BORG_* environment variables to Borg.
* #328: Fix duplicate logging to Healthchecks and send "after_*" hooks output to Healthchecks.
* #331: Add SSL support to PostgreSQL database configuration.
+8 -17
View File
@@ -24,6 +24,7 @@ location:
repositories:
- 1234@usw-s001.rsync.net:backups.borg
- k8pDxu32@k8pDxu32.repo.borgbase.com:repo
- user1@scp2.cdn.lima-labs.com:repo
- /var/lib/backups/local.borg
retention:
@@ -71,25 +72,14 @@ borgmatic is powered by [Borg Backup](https://www.borgbackup.org/).
<a href="https://www.borgbase.com/?utm_source=borgmatic"><img src="docs/static/borgbase.png" alt="BorgBase" height="60px" style="margin-bottom:20px;"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
## How-to guides
## Getting started
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/) ⬅ *Start here!*
* [Make per-application backups](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/)
* [Deal with very large backups](https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Extract a backup](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/)
* [Backup your databases](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Backup to a removable drive or an intermittent server](https://torsion.org/borgmatic/docs/how-to/backup-to-a-removable-drive-or-an-intermittent-server/)
* [Upgrade borgmatic](https://torsion.org/borgmatic/docs/how-to/upgrade/)
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
Your first step is to [install and configure
borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/).
## Reference guides
* [borgmatic configuration reference](https://torsion.org/borgmatic/docs/reference/configuration/)
* [borgmatic command-line reference](https://torsion.org/borgmatic/docs/reference/command-line/)
For additional documentation, check out the links above for <a
href="https://torsion.org/borgmatic/#documentation">borgmatic how-to and
reference guides</a>.
## Hosting providers
@@ -102,6 +92,7 @@ links, but without any tracking scripts or cookies.)
<ul>
<li class="referral"><a href="https://www.rsync.net/cgi-bin/borg.cgi?campaign=borg&adgroup=borgmatic">rsync.net</a>: Cloud Storage provider with full support for borg and any other SSH/SFTP tool</li>
<li class="referral"><a href="https://www.borgbase.com/?utm_source=borgmatic">BorgBase</a>: Borg hosting service with support for monitoring, 2FA, and append-only repos</li>
<li class="referral"><a href="https://storage.lima-labs.com/special-pricing-offer-for-borgmatic-users/">Lima-Labs</a>: Affordable, reliable cloud data storage accessable via SSH/SCP/FTP for Borg backups or any other bulk storage needs</li>
</ul>
## Support and contributing
+1
View File
@@ -9,6 +9,7 @@ OPTION_TO_ENVIRONMENT_VARIABLE = {
'encryption_passcommand': 'BORG_PASSCOMMAND',
'encryption_passphrase': 'BORG_PASSPHRASE',
'ssh_command': 'BORG_RSH',
'temporary_directory': 'TMPDIR',
}
DEFAULT_BOOL_OPTION_TO_ENVIRONMENT_VARIABLE = {
+64
View File
@@ -0,0 +1,64 @@
import logging
import os
from borgmatic.execute import DO_NOT_CAPTURE, execute_command
logger = logging.getLogger(__name__)
def export_tar_archive(
dry_run,
repository,
archive,
paths,
destination_path,
storage_config,
local_path='borg',
remote_path=None,
tar_filter=None,
files=False,
strip_components=None,
):
'''
Given a dry-run flag, a local or remote repository path, an archive name, zero or more paths to
export from the archive, a destination path to export to, a storage configuration dict, optional
local and remote Borg paths, an optional filter program, whether to include per-file details,
and an optional number of path components to strip, export the archive into the given
destination path as a tar-formatted file.
If the destination path is "-", then stream the output to stdout instead of to a file.
'''
umask = storage_config.get('umask', None)
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(local_path, 'export-tar')
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--list',) if files else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--dry-run',) if dry_run else ())
+ (('--tar-filter', tar_filter) if tar_filter else ())
+ (('--strip-components', str(strip_components)) if strip_components else ())
+ ('::'.join((repository if ':' in repository else os.path.abspath(repository), archive)),)
+ (destination_path,)
+ (tuple(paths) if paths else ())
)
if files and logger.getEffectiveLevel() == logging.WARNING:
output_log_level = logging.WARNING
else:
output_log_level = logging.INFO
if dry_run:
logging.info('{}: Skipping export to tar file (dry run)'.format(repository))
return
execute_command(
full_command,
output_file=DO_NOT_CAPTURE if destination_path == '-' else None,
output_log_level=output_log_level,
borg_local_path=local_path,
)
+65 -2
View File
@@ -1,5 +1,5 @@
import collections
from argparse import ArgumentParser
from argparse import Action, ArgumentParser
from borgmatic.config import collect
@@ -9,6 +9,7 @@ SUBPARSER_ALIASES = {
'create': ['--create', '-C'],
'check': ['--check', '-k'],
'extract': ['--extract', '-x'],
'export-tar': ['--export-tar'],
'mount': ['--mount', '-m'],
'umount': ['--umount', '-u'],
'restore': ['--restore', '-r'],
@@ -101,6 +102,20 @@ def parse_global_arguments(unparsed_arguments, top_level_parser, subparsers):
return top_level_parser.parse_args(remaining_arguments)
class Extend_action(Action):
'''
An argparse action to support Python 3.8's "extend" action in older versions of Python.
'''
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest, None)
if items:
items.extend(values)
else:
setattr(namespace, self.dest, list(values))
def parse_arguments(*unparsed_arguments):
'''
Given command-line arguments with which this script was invoked, parse the arguments and return
@@ -110,6 +125,7 @@ def parse_arguments(*unparsed_arguments):
unexpanded_config_paths = collect.get_default_config_paths(expand_home=False)
global_parser = ArgumentParser(add_help=False)
global_parser.register('action', 'extend', Extend_action)
global_group = global_parser.add_argument_group('global arguments')
global_group.add_argument(
@@ -177,6 +193,7 @@ def parse_arguments(*unparsed_arguments):
metavar='SECTION.OPTION=VALUE',
nargs='+',
dest='overrides',
action='extend',
help='One or more configuration file options to override with specified values',
)
global_group.add_argument(
@@ -358,6 +375,52 @@ def parse_arguments(*unparsed_arguments):
'-h', '--help', action='help', help='Show this help message and exit'
)
export_tar_parser = subparsers.add_parser(
'export-tar',
aliases=SUBPARSER_ALIASES['export-tar'],
help='Export an archive to a tar-formatted file or stream',
description='Export an archive to a tar-formatted file or stream',
add_help=False,
)
export_tar_group = export_tar_parser.add_argument_group('export-tar arguments')
export_tar_group.add_argument(
'--repository',
help='Path of repository to export from, defaults to the configured repository if there is only one',
)
export_tar_group.add_argument(
'--archive', help='Name of archive to export (or "latest")', required=True
)
export_tar_group.add_argument(
'--path',
metavar='PATH',
nargs='+',
dest='paths',
help='Paths to export from archive, defaults to the entire archive',
)
export_tar_group.add_argument(
'--destination',
metavar='PATH',
dest='destination',
help='Path to destination export tar file, or "-" for stdout (but be careful about dirtying output with --verbosity or --files)',
required=True,
)
export_tar_group.add_argument(
'--tar-filter', help='Name of filter program to pipe data through'
)
export_tar_group.add_argument(
'--files', default=False, action='store_true', help='Show per-file details'
)
export_tar_group.add_argument(
'--strip-components',
type=int,
metavar='NUMBER',
dest='strip_components',
help='Number of leading path components to remove from each exported path. Skip paths with fewer elements',
)
export_tar_group.add_argument(
'-h', '--help', action='help', help='Show this help message and exit'
)
mount_parser = subparsers.add_parser(
'mount',
aliases=SUBPARSER_ALIASES['mount'],
@@ -534,7 +597,7 @@ def parse_arguments(*unparsed_arguments):
help='Show info for first N archives after other filters are applied',
)
info_group.add_argument(
'--last', metavar='N', help='Show info for first N archives after other filters are applied'
'--last', metavar='N', help='Show info for last N archives after other filters are applied'
)
info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+44 -1
View File
@@ -12,6 +12,7 @@ import pkg_resources
from borgmatic.borg import check as borg_check
from borgmatic.borg import create as borg_create
from borgmatic.borg import environment as borg_environment
from borgmatic.borg import export_tar as borg_export_tar
from borgmatic.borg import extract as borg_extract
from borgmatic.borg import info as borg_info
from borgmatic.borg import init as borg_init
@@ -90,6 +91,14 @@ def run_configuration(config_filename, config, arguments):
'pre-check',
global_arguments.dry_run,
)
if 'extract' in arguments:
command.execute_hook(
hooks.get('before_extract'),
hooks.get('umask'),
config_filename,
'pre-extract',
global_arguments.dry_run,
)
if prune_create_or_check:
dispatch.call_hooks(
'ping_monitor',
@@ -164,6 +173,14 @@ def run_configuration(config_filename, config, arguments):
'post-check',
global_arguments.dry_run,
)
if 'extract' in arguments:
command.execute_hook(
hooks.get('after_extract'),
hooks.get('umask'),
config_filename,
'post-extract',
global_arguments.dry_run,
)
if prune_create_or_check:
dispatch.call_hooks(
'ping_monitor',
@@ -347,6 +364,30 @@ def run_actions(
strip_components=arguments['extract'].strip_components,
progress=arguments['extract'].progress,
)
if 'export-tar' in arguments:
if arguments['export-tar'].repository is None or validate.repositories_match(
repository, arguments['export-tar'].repository
):
logger.info(
'{}: Exporting archive {} as tar file'.format(
repository, arguments['export-tar'].archive
)
)
borg_export_tar.export_tar_archive(
global_arguments.dry_run,
repository,
borg_list.resolve_archive_name(
repository, arguments['export-tar'].archive, storage, local_path, remote_path
),
arguments['export-tar'].paths,
arguments['export-tar'].destination,
storage,
local_path=local_path,
remote_path=remote_path,
tar_filter=arguments['export-tar'].tar_filter,
files=arguments['export-tar'].files,
strip_components=arguments['export-tar'].strip_components,
)
if 'mount' in arguments:
if arguments['mount'].repository is None or validate.repositories_match(
repository, arguments['mount'].repository
@@ -617,7 +658,9 @@ def collect_configuration_run_summary_logs(configs, arguments):
if not configs:
yield from make_error_log_records(
'{}: No configuration files found'.format(' '.join(arguments['global'].config_paths))
'{}: No valid configuration files found'.format(
' '.join(arguments['global'].config_paths)
)
)
return
+3 -1
View File
@@ -99,7 +99,9 @@ def main(): # pragma: no cover
)
generate.write_configuration(
args.destination_config_filename, destination_config, mode=source_config_file_mode
args.destination_config_filename,
generate.render_configuration(destination_config),
mode=source_config_file_mode,
)
display_result(args)
+3
View File
@@ -44,6 +44,9 @@ def collect_config_filenames(config_paths):
yield path
continue
if not os.access(path, os.R_OK):
continue
for filename in sorted(os.listdir(path)):
full_filename = os.path.join(path, filename)
matching_filetype = full_filename.endswith('.yaml') or full_filename.endswith('.yml')
+2 -2
View File
@@ -99,7 +99,7 @@ def _comment_out_optional_configuration(rendered_config):
return '\n'.join(lines)
def _render_configuration(config):
def render_configuration(config):
'''
Given a config data structure of nested OrderedDicts, render the config as YAML and return it.
'''
@@ -284,5 +284,5 @@ def generate_sample_configuration(source_filename, destination_filename, schema_
write_configuration(
destination_filename,
_comment_out_optional_configuration(_render_configuration(destination_config)),
_comment_out_optional_configuration(render_configuration(destination_config)),
)
+51 -25
View File
@@ -27,10 +27,14 @@ map:
desc: |
Paths to local or remote repositories (required). Tildes are
expanded. Multiple repositories are backed up to in
sequence. See ssh_command for SSH options like identity file
or port.
sequence. Borg placeholders can be used. See the output of
"borg help placeholders" for details. See ssh_command for
SSH options like identity file or port. If systemd service
is used, then add local repository paths in the systemd
service file to the ReadWritePaths list.
example:
- user@backupserver:sourcehostname.borg
- "user@backupserver:{fqdn}"
one_file_system:
type: bool
desc: |
@@ -123,7 +127,7 @@ map:
patterns" for more details.
example:
- '*.pyc'
- ~/*/.cache
- /home/*/.cache
- /etc/ssl
exclude_from:
seq:
@@ -226,6 +230,12 @@ map:
Remote network upload rate limit in kiBytes/second. Defaults
to unlimited.
example: 100
temporary_directory:
type: str
desc: |
Directory where temporary files are stored. Defaults to
$TMPDIR
example: /path/to/tmpdir
ssh_command:
type: str
desc: |
@@ -329,8 +339,8 @@ map:
Retention policy for how many backups to keep in each category. See
https://borgbackup.readthedocs.io/en/stable/usage/prune.html for
details. At least one of the "keep" options is required for pruning
to work. See borgmatic documentation if you'd like to skip pruning
entirely.
to work. To skip pruning entirely, run "borgmatic create" or "check"
without the "prune" action. See borgmatic documentation for details.
map:
keep_within:
type: str
@@ -473,6 +483,14 @@ map:
before consistency checks, run once per configuration file.
example:
- echo "Starting checks."
before_extract:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute
before extracting a backup, run once per configuration file.
example:
- echo "Starting extracting."
after_backup:
seq:
- type: str
@@ -497,6 +515,14 @@ map:
after consistency checks, run once per configuration file.
example:
- echo "Finished checks."
after_extract:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute
after extracting a backup, run once per configuration file.
example:
- echo "Finished extracting."
on_error:
seq:
- type: str
@@ -506,6 +532,26 @@ map:
"check" action or an associated before/after hook.
example:
- echo "Error during prune/create/check."
before_everything:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute
before running all actions (if one of them is "create").
These are collected from all configuration files and then
run once before all of them (prior to all actions).
example:
- echo "Starting actions."
after_everything:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute
after running all actions (if one of them is "create").
These are collected from all configuration files and then
run once after all of them (after any action).
example:
- echo "Completed actions."
postgresql_databases:
seq:
- map:
@@ -693,26 +739,6 @@ map:
documentation for details.
example:
https://cronhub.io/start/1f5e3410-254c-11e8-b61d-55875966d01
before_everything:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute
before running all actions (if one of them is "create").
These are collected from all configuration files and then
run once before all of them (prior to all actions).
example:
- echo "Starting actions."
after_everything:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute
after running all actions (if one of them is "create").
These are collected from all configuration files and then
run once before all of them (prior to all actions).
example:
- echo "Completed actions."
umask:
type: scalar
desc: |
+6 -1
View File
@@ -84,9 +84,14 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path):
logger.log(output_log_level, line)
still_running = False
for process in processes:
exit_code = process.poll() if output_buffers else process.wait()
if exit_code is None:
still_running = True
# If any process errors, then raise accordingly.
if exit_code_indicates_error(process, exit_code, borg_local_path):
# If an error occurs, include its output in the raised exception so that we don't
@@ -108,7 +113,7 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path):
exit_code, command_for_process(process), '\n'.join(last_lines)
)
if all(process.poll() is not None for process in processes):
if not still_running:
break
# Consume any remaining output that we missed (if any).
+9
View File
@@ -39,3 +39,12 @@ def ping_monitor(ping_url, config_filename, state, monitoring_log_level, dry_run
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
requests.get(ping_url)
def destroy_monitor(
ping_url_or_uuid, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
'''
No destruction is necessary for this monitor.
'''
pass
+9
View File
@@ -38,3 +38,12 @@ def ping_monitor(ping_url, config_filename, state, monitoring_log_level, dry_run
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
requests.get(ping_url)
def destroy_monitor(
ping_url_or_uuid, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
'''
No destruction is necessary for this monitor.
'''
pass
+1 -1
View File
@@ -58,5 +58,5 @@ def call_hooks(function_name, hooks, log_prefix, hook_names, *args, **kwargs):
return {
hook_name: call_hook(function_name, hooks, log_prefix, hook_name, *args, **kwargs)
for hook_name in hook_names
if hook_name in hooks
if hooks.get(hook_name)
}
+9
View File
@@ -69,3 +69,12 @@ def ping_monitor(integration_key, config_filename, state, monitoring_log_level,
logging.getLogger('urllib3').setLevel(logging.ERROR)
requests.post(EVENTS_API_URL, data=payload.encode('utf-8'))
def destroy_monitor(
ping_url_or_uuid, config_filename, monitoring_log_level, dry_run
): # pragma: no cover
'''
No destruction is necessary for this monitor.
'''
pass
+6 -1
View File
@@ -4,8 +4,13 @@ import signal
def _handle_signal(signal_number, frame): # pragma: no cover
'''
Send the signal to all processes in borgmatic's process group, which includes child process.
Send the signal to all processes in borgmatic's process group, which includes child processes.
'''
# Prevent infinite signal handler recursion. If the parent frame is this very same handler
# function, we know we're recursing.
if frame.f_back.f_code.co_name == _handle_signal.__name__:
return
os.killpg(os.getpgrp(), signal_number)
+1
View File
@@ -16,6 +16,7 @@ WORKDIR /source
RUN npm install @11ty/eleventy \
@11ty/eleventy-plugin-syntaxhighlight \
@11ty/eleventy-plugin-inclusive-language \
@11ty/eleventy-navigation \
markdown-it \
markdown-it-anchor \
markdown-it-replace-link
+165 -13
View File
@@ -1,8 +1,7 @@
/* Buzzwords */
@keyframes rainbow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
100% { background-position: 100% 50%; }
}
.buzzword-list,
.inlinelist {
@@ -25,6 +24,7 @@
margin: 4px 4px 4px 0;
transition: .15s linear outline;
}
.inlinelist .inlinelist-item.active {
background-color: #222;
color: #fff;
@@ -36,6 +36,38 @@
}
.inlinelist .inlinelist-item code {
background-color: transparent;
font-size: 80%;
margin-left: 6px;
padding-left: 6px;
display: inline-block;
position: relative;
}
@media (max-width: 26.8125em) { /* 429px */
.inlinelist .inlinelist-item {
overflow: hidden;
}
.inlinelist .inlinelist-item code {
float: right;
line-height: 1.75;
}
}
@media (min-width: 26.875em) { /* 430px */
.inlinelist .inlinelist-item code {
float: none;
}
.inlinelist .inlinelist-item code:before {
content: " ";
border-left: 1px solid rgba(255,255,255,.8);
position: absolute;
left: -2px;
top: -2px;
bottom: 2px;
}
@media (prefers-color-scheme: dark) {
.inlinelist .inlinelist-item code:before {
border-left-color: rgba(0,0,0,.8);
}
}
}
a.buzzword {
text-decoration: underline;
@@ -59,44 +91,74 @@ a.buzzword {
.buzzword {
background-color: #f7f7f7;
}
@media (prefers-color-scheme: dark) {
.buzzword-list li,
.buzzword {
background-color: #080808;
}
}
.inlinelist .inlinelist-item {
background-color: #e9e9e9;
}
@media (prefers-color-scheme: dark) {
.inlinelist .inlinelist-item {
background-color: #000;
}
.inlinelist .inlinelist-item a {
color: #fff;
}
.inlinelist .inlinelist-item code {
color: inherit;
}
}
.inlinelist .inlinelist-item:hover,
.inlinelist .inlinelist-item:focus,
.buzzword-list li:hover,
.buzzword-list li:focus,
.buzzword:hover,
.buzzword:focus {
.buzzword:focus,
.rainbow-active:hover,
.rainbow-active:focus {
position: relative;
background-image: linear-gradient(238deg, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff0080);
background-size: 1200% 1200%;
background-position: 2% 80%;
color: #fff;
text-shadow: 0 0 2px rgba(0,0,0,.9);
animation: rainbow 1.6s infinite;
animation: rainbow 4s ease-out alternate infinite;
}
.rainbow-active-noanim {
animation: none !important;
}
.inlinelist .inlinelist-item:hover a,
.inlinelist .inlinelist-item:focus a,
.buzzword-list li:hover a,
.buzzword-list li:focus a,
a.buzzword:hover,
a.buzzword:focus {
a.buzzword:focus,
a.rainbow-active:hover,
a.rainbow-active:focus {
color: #fff;
text-decoration: none;
}
/*
I wish there were a PE friendly way to do this but media queries dont work work with @supports
@media (prefers-reduced-motion: no-preference) {
@media (prefers-reduced-motion: reduce) {
.inlinelist .inlinelist-item:hover,
.inlinelist .inlinelist-item:focus,
.buzzword-list li:hover,
.buzzword-list li:focus,
.buzzword:hover,
.buzzword:focus {
animation: rainbow 1s infinite;
.buzzword:focus,
.rainbow-active:hover,
.rainbow-active:focus {
animation: none;
}
}*/
}
.buzzword-list li:hover:after,
.buzzword-list li:focus:after,
.buzzword:hover:after,
.buzzword:focus:after {
font-family: system-ui, sans-serif;
font-family: system-ui, -apple-system, sans-serif;
content: "Buzzword alert!!!";
position: absolute;
left: 0;
@@ -123,4 +185,94 @@ main h2 a.buzzword,
main h3 a.buzzword,
main p a.buzzword {
text-decoration: underline;
}
}
/* Small viewport */
@media (max-width: 26.8125em) { /* 429px */
.inlinelist .inlinelist-item {
display: block;
width: auto;
padding: 0;
line-height: 1.4;
}
.inlinelist .inlinelist-item > a {
display: block;
padding: .2em .5em;
}
}
@media (min-width: 26.875em) { /* 430px */
.inlinelist .inlinelist-item > a {
display: inline-block;
white-space: nowrap;
}
}
.numberflag {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #dff7ff;
border-radius: 50%;
width: 1.75em;
height: 1.75em;
font-weight: 600;
}
@media (prefers-color-scheme: dark) {
.numberflag {
background-color: #00bcd4;
color: #222;
}
}
h1 .numberflag,
h2 .numberflag,
h3 .numberflag,
h4 .numberflag,
h5 .numberflag {
width: 1.25em;
height: 1.25em;
}
h2 .numberflag {
position: relative;
margin-right: 0.25em; /* 10px /40 */
}
h2 .numberflag:after {
content: " ";
position: absolute;
bottom: -1px;
left: 0;
height: 1px;
background-color: #fff;
width: calc(100% + 0.4em); /* 16px /40 */
}
@media (prefers-color-scheme: dark) {
h2 .numberflag:after {
background-color: #222;
}
}
/* Super featured list on home page */
.list-superfeatured .avatar {
width: calc(30px + 5vw);
height: calc(30px + 5vw);
max-width: 60px;
max-height: 60px;
margin-left: 0;
}
@media (max-width: 26.8125em) { /* 429px */
.list-superfeatured .inlinelist-item > a {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
@media (min-width: 26.875em) { /* 430px */
.list-superfeatured .inlinelist-item {
font-size: 110%;
}
}
/* Only top level */
.inlinelist-no-nest ul,
.inlinelist-no-nest ol {
display: none;
}
+69 -2
View File
@@ -10,7 +10,20 @@
font-weight: 500;
margin: 0 0.4285714285714em 0.07142857142857em 0; /* 0 6px 1px 0 /14 */
line-height: 1.285714285714; /* 18px /14 */
font-family: system-ui, sans-serif;
font-family: system-ui, -apple-system, sans-serif;
}
@media (prefers-color-scheme: dark) {
.minilink {
background-color: #222;
/*
!important to override .elv-callout a
see _includes/components/callout.css
*/
color: #fff !important;
}
}
table .minilink {
margin-top: 6px;
}
.minilink[href] {
box-shadow: 0 1px 1px 0 rgba(0,0,0,.5);
@@ -19,6 +32,12 @@
.minilink[href]:focus {
background-color: #bbb;
}
@media (prefers-color-scheme: dark) {
.minilink[href]:hover,
.minilink[href]:focus {
background-color: #444;
}
}
pre + .minilink {
color: #fff;
border-radius: 0 0 0.2857142857143em 0.2857142857143em; /* 4px /14 */
@@ -35,6 +54,54 @@ p.minilink {
margin-left: 2em;
margin-bottom: 2em;
}
h1 .minilink,
h2 .minilink,
h3 .minilink,
h4 .minilink {
font-size: 0.9375rem; /* 15px /16 */
vertical-align: middle;
margin-left: 1em;
}
h3 .minilink,
h4 .minilink {
font-size: 0.8125rem; /* 13px /16 */
}
.minilink + pre[class*=language-] {
clear: both;
}
}
.minilink-addedin {
text-transform: none;
box-shadow: 0 0 0 1px rgba(0,0,0,0.3);
}
@media (prefers-color-scheme: dark) {
.minilink-addedin {
box-shadow: 0 0 0 1px rgba(255,255,255,0.3);
}
}
.minilink-addedin:not(:first-child) {
margin-left: .5em;
}
.minilink-addedin.minilink-inline {
margin: 0 4px;
background-color: #fff;
}
.minilink-lower {
text-transform: none;
background-color: transparent;
}
.minilink-lower[href] {
box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
}
.minilink-lower[href]:hover,
.minilink-lower[href]:focus {
background-color: #eee;
}
.minilink > .minilink {
margin: -.125em .375em -.125em -.375em;
box-shadow: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
+70 -22
View File
@@ -1,63 +1,111 @@
.elv-toc {
font-size: 1rem; /* Reset */
}
.elv-toc details {
--details-force-closed: (max-width: 63.9375em); /* 1023px */
}
.elv-toc details > summary {
font-size: 1.375rem; /* 22px /16 */
margin-bottom: .5em;
}
@media (min-width: 64em) { /* 1024px */
.elv-toc {
position: absolute;
left: -17rem;
left: 3rem;
width: 16rem;
z-index: 1;
}
.elv-toc details > summary {
margin-top: 0;
}
.js .elv-toc details > summary {
display: none;
}
}
.elv-toc-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding-left: 0;
padding-right: 0;
margin: 0 0 2.5em;
list-style: none;
}
.elv-toc-list li {
font-size: 0.9375em; /* 15px /16 */
line-height: 1.466666666667; /* 22px /15 */
}
/* Nested lists */
.elv-toc-list ul {
padding: 0;
display: none;
margin-bottom: 1.5em;
padding: 0 0 .75em 0;
margin: 0;
list-style: none;
}
.elv-toc-list ul li {
padding-left: 0.875em; /* 14px /16 */
/* Menus nested 2 or more deep */
.elv-toc-list ul ul {
padding-bottom: 0;
padding-left: 0.625rem; /* 10px /16 */
}
@media (min-width: 64em) and (min-height: 48em) { /* 1024 x 768px */
.elv-toc-list ul {
display: block;
}
/* Hide inactive menus 3 or more deep */
.elv-toc-list ul ul > li:not(.elv-toc-active) > ul > li:not(.elv-toc-active) {
display: none;
}
/* List items */
.elv-toc summary,
.elv-toc-list a {
padding: .15em .25em;
}
.elv-toc-list a {
display: block;
}
.elv-toc-list a:not(:hover) {
text-decoration: none;
}
.elv-toc-list li {
padding-top: 0;
padding-bottom: 0;
margin: .1em 0 .5em;
margin: 0;
padding: 0;
}
.elv-toc-list > li {
flex-grow: 1;
flex-basis: 14.375rem; /* 230px /16 */
}
/* Top level links */
.elv-toc-list > li > a {
font-weight: 400;
font-size: 1.0625em; /* 17px /16 */
color: #222;
font-weight: 600;
border-bottom: 1px solid #ddd;
margin-bottom: 0.25em; /* 4px /16 */
}
@media (prefers-color-scheme: dark) {
.elv-toc-list > li > a {
color: #fff;
border-color: #444;
}
}
/* Active links */
.elv-toc-list li.elv-toc-active > a {
font-weight: 700;
text-decoration: underline;
background-color: #dff7ff;
}
.elv-toc-active > a:after {
content: " ⬅";
line-height: .5;
@media (prefers-color-scheme: dark) {
.elv-toc-list li.elv-toc-active > a {
background-color: #353535;
}
}
.elv-toc-list ul .elv-toc-active > a:after {
content: "";
}
/* Show only active nested lists */
.elv-toc-list ul.elv-toc-active,
.elv-toc-list li.elv-toc-active > ul {
display: block;
}
}
/* Footer catgory navigation */
.elv-cat-list-active {
font-weight: 600;
}
+77 -32
View File
@@ -234,6 +234,9 @@ pre + .note {
max-width: 42rem;
clear: both;
}
header.elv-layout {
padding: 0 1rem;
}
footer.elv-layout {
margin-bottom: 5em;
}
@@ -242,7 +245,7 @@ footer.elv-layout {
}
@media (min-width: 64em) { /* 1024px */
.elv-layout-toc {
margin-left: 18rem;
padding-left: 15rem;
max-width: 60rem;
margin-right: 1rem;
position: relative;
@@ -254,7 +257,6 @@ footer.elv-layout {
/* Header */
.elv-header {
color: #222;
position: relative;
}
.elv-header-default {
@@ -262,6 +264,13 @@ footer.elv-layout {
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 0;
}
.elv-header-c {
width: 100%;
}
.elv-header-docs .elv-header-c {
padding: 1rem 0;
}
.elv-header-docs:before,
.elv-header-docs:after {
@@ -272,53 +281,89 @@ footer.elv-layout {
clear: both;
}
/* Header Hero */
.elv-hero img {
max-width: 80vw;
max-height: 60vh;
.elv-hero {
background-color: #222;
}
@media (prefers-color-scheme: dark) {
.elv-hero {
background-color: #292929;
}
}
.elv-hero img,
.elv-hero svg {
width: 42.95774646vh;
height: 60vh;
}
.elv-hero:hover img,
.elv-hero:hover svg {
background-color: inherit;
}
.elv-header-default .elv-hero {
display: flex;
justify-content: center;
width: calc(100% + 2rem);
margin-left: -1rem;
margin-right: -1rem;
}
.elv-hero:hover {
background-color: #333;
}
.elv-header-docs .elv-hero {
float: left;
margin-right: 1.5em;
margin-right: .5em;
}
.elv-header-docs .elv-hero img {
.elv-header-default .elv-hero img,
.elv-header-default .elv-hero svg {
position: relative;
background-color: transparent;
z-index: 1;
}
.elv-header-docs .elv-hero img,
.elv-header-docs .elv-hero svg {
width: auto;
height: 3em;
}
@media (min-width: 37.5em) { /* 600px */
.elv-header-docs .elv-hero img {
@media (min-width: 43.75em) { /* 700px */
.elv-header-docs .elv-hero {
margin-right: 1em;
}
.elv-header-docs .elv-hero img,
.elv-header-docs .elv-hero svg {
width: 4.303125em; /* 68.85px /16 */
height: 6em;
}
}
/* Header Possum */
.elv-possum-anchor {
display: block;
}
.elv-possum {
display: none;
position: absolute;
right: 1em;
top: 1em;
width: 16vmin;
right: .5rem;
top: 1rem;
transition: .3s opacity ease-out;
}
@media (min-width: 31.25em) { /* 500px */
.elv-possum {
display: block;
}
.elv-header-docs .elv-possum {
width: 15vw;
max-width: 6.25rem; /* 100px /16 */
}
/* Header Heading */
.elv-hed {
font-size: 3em;
margin-top: 1.5em;
margin-bottom: .25em;
text-align: center;
text-transform: none;
.elv-header-default {
overflow: hidden;
}
.elv-header-docs .elv-hed {
font-size: 2.3em;
margin: 0;
text-align: left;
.elv-header-default .elv-possum {
pointer-events: none;
width: auto;
height: calc((60vh - 2rem) / 1.6);
top: 36%;
left: 1vw;
right: auto;
animation-duration: 180s;
animation-name: balloonFloat;
}
@media (min-width: 37.5em) { /* 600px */
.elv-header-docs .elv-hed {
font-size: 3em;
@media (prefers-reduced-motion: reduce) {
.elv-header-default .elv-possum {
display: none;
}
}
+22 -4
View File
@@ -6,9 +6,27 @@ headerClass: elv-header-default
{% include "header.njk" %}
<main class="elv-layout{% if layoutClass %} {{ layoutClass }}{% endif %}">
<article>
{{ content | safe }}
<div id="documentation" class="elv-toc">
<div>
{% set navPages = collections.all | eleventyNavigation %}
{% macro renderNavListItem(entry) -%}
<li{% if entry.url == page.url %} class="elv-toc-active"{% endif %}>
<a {% if entry.url %}href="https://torsion.org/borgmatic/docs{{ entry.url | url }}"{% endif %}>{{ entry.title }}</a>
{%- if entry.children.length -%}
<ul>
{%- for child in entry.children %}{{ renderNavListItem(child) }}{% endfor -%}
</ul>
{%- endif -%}
</li>
{%- endmacro %}
{% include 'components/suggestion-form.html' %}
</article>
<ul class="elv-toc-list">
{%- for entry in navPages %}{{ renderNavListItem(entry) }}{%- endfor -%}
</ul>
</div>
</div>
{{ content | safe }}
{% include 'components/suggestion-form.html' %}
</main>
@@ -1,5 +1,9 @@
---
title: How to add preparation and cleanup steps to backups
eleventyNavigation:
key: Add preparation and cleanup steps
parent: How-to guides
order: 8
---
## Preparation and cleanup hooks
@@ -73,11 +77,3 @@ with the user permissions of borgmatic itself. So to prevent potential shell
injection or privilege escalation, do not forget to set secure permissions
on borgmatic configuration files (`chmod 0600`) and scripts (`chmod 0700`)
invoked by hooks.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Backup your databases](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
@@ -1,5 +1,9 @@
---
title: How to backup to a removable drive or an intermittent server
eleventyNavigation:
key: Backup to a removable drive or server
parent: How-to guides
order: 9
---
## Occasional backups
@@ -98,10 +102,3 @@ There are some caveats you should be aware of with this feature.
* The soft failure feature also works for `before_prune`, `after_prune`,
`before_check`, and `after_check` hooks. But it is not implemented for
`before_everything` or `after_everything`.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Make per-application backups](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
+26 -6
View File
@@ -1,5 +1,9 @@
---
title: How to backup your databases
eleventyNavigation:
key: Backup your databases
parent: How-to guides
order: 7
---
## Database dump hooks
@@ -34,6 +38,7 @@ in the `location` section of borgmatic's configuration.
Also note that using a database hook implicitly enables both the
`read_special` and `one_file_system` configuration settings (even if they're
disabled in your configuration) to support this dump and restore streaming.
See Limitations below for more on this.
Here's a more involved example that connects to remote databases:
@@ -69,6 +74,17 @@ hooks:
Note that you may need to use a `username` of the `postgres` superuser for
this to work with PostgreSQL.
If you would like to backup databases only and not source directories, you can
specify an empty `source_directories` value because it is a mandatory field:
```yaml
location:
source_directories: []
hooks:
mysql_databases:
- name: all
```
### Configuration backups
@@ -167,6 +183,12 @@ borgmatic's own configuration file. So include your configuration file in
backups to avoid getting caught without a way to restore a database.
3. borgmatic does not currently support backing up or restoring multiple
databases that share the exact same name on different hosts.
4. Because database hooks implicitly enable the `read_special` configuration
setting to support dump and restore streaming, you'll need to ensure that any
special files are excluded from backups (named pipes, block devices, and
character devices) to prevent hanging. Try a command like `find / -type c,b,p`
to find such files. Common directories to exclude are `/dev` and `/run`, but
that may not be exhaustive.
### Manual restoration
@@ -205,10 +227,8 @@ hooks:
options: "--single-transaction --quick"
```
### borgmatic hangs during backup
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Extract a backup](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/)
See Limitations above about `read_special`. You may need to exclude certain
paths with named pipes, block devices, or character devices on which borgmatic
is hanging.
+5 -6
View File
@@ -1,5 +1,9 @@
---
title: How to deal with very large backups
eleventyNavigation:
key: Deal with very large backups
parent: How-to guides
order: 3
---
## Biggish data
@@ -14,7 +18,7 @@ repositories.
If you find yourself in this situation, you have some options. First, you can
run borgmatic's pruning, creating, or checking actions separately. For
instance, the the following optional actions are available:
instance, the following optional actions are available:
```bash
borgmatic prune
@@ -120,8 +124,3 @@ the following to the `~/.ssh/config` file on the client:
This should make the client keep the connection alive while validating
backups.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
+4 -4
View File
@@ -1,5 +1,9 @@
---
title: How to develop on borgmatic
eleventyNavigation:
key: Develop on borgmatic
parent: How-to guides
order: 11
---
## Source code
@@ -139,7 +143,3 @@ http://localhost:8080 to view the documentation with your changes.
To close the documentation server, ctrl-C the script. Note that it does not
currently auto-reload, so you'll need to stop it and re-run it for any
additional documentation changes to take effect.
## Related documentation
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
+4 -8
View File
@@ -1,5 +1,9 @@
---
title: How to extract a backup
eleventyNavigation:
key: Extract a backup
parent: How-to guides
order: 6
---
## Extract
@@ -132,11 +136,3 @@ When you're all done exploring your files, unmount your mount point. No
```bash
borgmatic umount --mount-point /mnt
```
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Backup your databases](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
+5
View File
@@ -0,0 +1,5 @@
---
eleventyNavigation:
key: How-to guides
permalink: false
---
+4 -8
View File
@@ -1,5 +1,9 @@
---
title: How to inspect your backups
eleventyNavigation:
key: Inspect your backups
parent: How-to guides
order: 4
---
## Backup progress
@@ -98,11 +102,3 @@ Note that if you use the `--log-file` flag, you are responsible for rotating
the log file so it doesn't grow too large, for example with
[logrotate](https://wiki.archlinux.org/index.php/Logrotate). Also, there is a
`--log-file-verbosity` flag to customize the log file's log level.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
+46
View File
@@ -0,0 +1,46 @@
---
title: How to make backups redundant
eleventyNavigation:
key: Make backups redundant
parent: How-to guides
order: 2
---
## Multiple repositories
If you really care about your data, you probably want more than one backup of
it. borgmatic supports this in its configuration by specifying multiple backup
repositories. Here's an example:
```yaml
location:
# List of source directories to backup.
source_directories:
- /home
- /etc
# Paths of local or remote repositories to backup to.
repositories:
- 1234@usw-s001.rsync.net:backups.borg
- k8pDxu32@k8pDxu32.repo.borgbase.com:repo
- user1@scp2.cdn.lima-labs.com:repo
- /var/lib/backups/local.borg
```
When you run borgmatic with this configuration, it invokes Borg once for each
configured repository in sequence. (So, not in parallel.) That means—in each
repository—borgmatic creates a single new backup archive containing all of
your source directories.
Here's a way of visualizing what borgmatic does with the above configuration:
1. Backup `/home` and `/etc` to `1234@usw-s001.rsync.net:backups.borg`
2. Backup `/home` and `/etc` to `k8pDxu32@k8pDxu32.repo.borgbase.com:repo`
3. Backup `/home` and `/etc` to `user1@scp2.cdn.lima-labs.com:repo`
4. Backup `/home` and `/etc` to `/var/lib/backups/local.borg`
This gives you redundancy of your data across repositories and even
potentially across providers.
See [Borg repository URLs
documentation](https://borgbackup.readthedocs.io/en/stable/usage/general.html#repository-urls)
for more information on how to specify local and remote repository paths.
+32 -8
View File
@@ -1,5 +1,9 @@
---
title: How to make per-application backups
eleventyNavigation:
key: Make per-application backups
parent: How-to guides
order: 1
---
## Multiple backup configurations
@@ -129,27 +133,47 @@ Whatever the reason, you can override borgmatic configuration options at the
command-line via the `--override` flag. Here's an example:
```bash
borgmatic create --override location.remote_path=borg1
borgmatic create --override location.remote_path=/usr/local/bin/borg1
```
What this does is load your configuration files, and for each one, disregard
the configured value for the `remote_path` option in the `location` section,
and use the value of `borg1` instead.
and use the value of `/usr/local/bin/borg1` instead.
Note that the value is parsed as an actual YAML string, so you can even set
You can even override multiple values at once. For instance:
```bash
borgmatic create --override section.option1=value1 section.option2=value2
```
This will accomplish the same thing:
```bash
borgmatic create --override section.option1=value1 --override section.option2=value2
```
Note that each value is parsed as an actual YAML string, so you can even set
list values by using brackets. For instance:
```bash
borgmatic create --override location.repositories=[test1.borg,test2.borg]
```
Or even a single list element:
```bash
borgmatic create --override location.repositories=[/root/test1.borg]
```
There is not currently a way to override a single element of a list without
replacing the whole list.
Note that if you override an option of the list type (like
`location.repositories`), you do need to use the `[ ]` list syntax. See the
[configuration
reference](https://torsion.org/borgmatic/docs/reference/configuration/) for
which options are list types. (YAML list values look like `- this` with an
indentation and a leading dash.)
Be sure to quote your overrides if they contain spaces or other characters
that your shell may interpret.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
+56 -41
View File
@@ -1,5 +1,9 @@
---
title: How to monitor your backups
eleventyNavigation:
key: Monitor your backups
parent: How-to guides
order: 5
---
## Monitoring and alerting
@@ -10,48 +14,68 @@ and alerting comes in.
There are several different ways you can monitor your backups and find out
whether they're succeeding. Which of these you choose to do is up to you and
your particular infrastructure:
your particular infrastructure.
1. **Job runner alerts**: The easiest place to start is with failure alerts
from the [scheduled job
runner](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot) (cron,
systemd, etc.) that's running borgmatic. But note that if the job doesn't even
get scheduled (e.g. due to the job runner not running), you probably won't get
an alert at all! Still, this is a decent first line of defense, especially
when combined with some of the other approaches below.
2. **borgmatic error hooks**: The `on_error` hook allows you to run an arbitrary
command or script when borgmatic itself encounters an error running your
backups. So for instance, you can run a script to send yourself a text message
alert. But note that if borgmatic doesn't actually run, this alert won't fire.
See [error
### Job runner alerts
The easiest place to start is with failure alerts from the [scheduled job
runner](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot)
(cron, systemd, etc.) that's running borgmatic. But note that if the job
doesn't even get scheduled (e.g. due to the job runner not running), you
probably won't get an alert at all! Still, this is a decent first line of
defense, especially when combined with some of the other approaches below.
### Commands run on error
The `on_error` hook allows you to run an arbitrary command or script when
borgmatic itself encounters an error running your backups. So for instance,
you can run a script to send yourself a text message alert. But note that if
borgmatic doesn't actually run, this alert won't fire. See [error
hooks](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#error-hooks)
below for how to configure this.
4. **borgmatic monitoring hooks**: This feature integrates with monitoring
services like [Healthchecks](https://healthchecks.io/),
[Cronitor](https://cronitor.io), [Cronhub](https://cronhub.io), and
[PagerDuty](https://www.pagerduty.com/) and pings these services whenever
borgmatic runs. That way, you'll receive an alert when something goes wrong or
(for certain hooks) the service doesn't hear from borgmatic for a configured
interval. See [Healthchecks
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#healthchecks-hook), [Cronitor
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronitor-hook), [Cronhub
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronhub-hook), and
[PagerDuty hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook)
### Third-party monitoring services
borgmatic integrates with monitoring services like
[Healthchecks](https://healthchecks.io/), [Cronitor](https://cronitor.io),
[Cronhub](https://cronhub.io), and [PagerDuty](https://www.pagerduty.com/) and
pings these services whenever borgmatic runs. That way, you'll receive an
alert when something goes wrong or (for certain hooks) the service doesn't
hear from borgmatic for a configured interval. See [Healthchecks
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#healthchecks-hook),
[Cronitor
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronitor-hook),
[Cronhub
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronhub-hook),
and [PagerDuty
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook)
below for how to configure this.
3. **Third-party monitoring software**: You can use traditional monitoring
software to consume borgmatic JSON output and track when the last
successful backup occurred. See [scripting
While these services offer different features, you probably only need to use
one of them at most.
### Third-party monitoring software
You can use traditional monitoring software to consume borgmatic JSON output
and track when the last successful backup occurred. See [scripting
borgmatic](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#scripting-borgmatic)
and [related software](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#related-software)
and [related
software](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#related-software)
below for how to configure this.
5. **Borg hosting providers**: Most [Borg hosting
### Borg hosting providers
Most [Borg hosting
providers](https://torsion.org/borgmatic/#hosting-providers) include
monitoring and alerting as part of their offering. This gives you a dashboard
to check on all of your backups, and can alert you if the service doesn't hear
from borgmatic for a configured interval.
6. **borgmatic consistency checks**: While not strictly part of monitoring, if you
really want confidence that your backups are not only running but are
restorable as well, you can configure particular [consistency
### Consistency checks
While not strictly part of monitoring, if you really want confidence that your
backups are not only running but are restorable as well, you can configure
particular [consistency
checks](https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#consistency-check-configuration)
or even script full [extract
tests](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/).
@@ -289,12 +313,3 @@ automated scripts. Here's an example:
```bash
borgmatic info --archive latest
```
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Extract a backup](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/)
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
+20 -17
View File
@@ -1,5 +1,9 @@
---
title: How to set up backups with borgmatic
title: How to set up backups
eleventyNavigation:
key: Set up backups
parent: How-to guides
order: 0
---
## Installation
@@ -65,8 +69,7 @@ sudo pip3 install --upgrade borgmatic
The main downside of a global install is that borgmatic is less cleanly
separated from the rest of your Python software, and there's the theoretical
possibility of libary conflicts. But if you're okay with that, for instance
on a relatively dedicated system, then a global install can work out just
fine.
on a relatively dedicated system, then a global install can work out fine.
### Other ways to install
@@ -98,6 +101,7 @@ links, but without any tracking scripts or cookies.)
<ul>
<li class="referral"><a href="https://www.rsync.net/cgi-bin/borg.cgi?campaign=borg&adgroup=borgmatic">rsync.net</a>: Cloud Storage provider with full support for borg and any other SSH/SFTP tool</li>
<li class="referral"><a href="https://www.borgbase.com/?utm_source=borgmatic">BorgBase</a>: Borg hosting service with support for monitoring, 2FA, and append-only repos</li>
<li class="referral"><a href="https://storage.lima-labs.com/special-pricing-offer-for-borgmatic-users/">Lima-Labs</a>: Affordable, reliable cloud data storage accessable via SSH/SCP/FTP for Borg backups or any other bulk storage needs</li>
</ul>
## Configuration
@@ -135,9 +139,8 @@ configuration](https://torsion.org/borgmatic/docs/how-to/upgrade/#upgrading-your
### Encryption
Note that if you plan to run borgmatic on a schedule with cron, and you
encrypt your Borg repository with a passphrase instead of a key file, you'll
either need to set the borgmatic `encryption_passphrase` configuration
If you encrypt your Borg repository with a passphrase instead of a key file,
you'll either need to set the borgmatic `encryption_passphrase` configuration
variable or set the `BORG_PASSPHRASE` environment variable. See the
[repository encryption
section](https://borgbackup.readthedocs.io/en/stable/quickstart.html#repository-encryption)
@@ -150,6 +153,13 @@ FAQ](http://borgbackup.readthedocs.io/en/stable/faq.html#how-can-i-specify-the-e
for more info.
### Redundancy
If you'd like to configure your backups to go to multiple different
repositories, see the documentation on how to [make backups
redundant](https://torsion.org/borgmatic/docs/how-to/make-backups-redundant/).
### Validation
If you'd like to validate that your borgmatic configuration is valid, the
@@ -259,6 +269,10 @@ sudo mv borgmatic.service borgmatic.timer /etc/systemd/system/
sudo systemctl enable --now borgmatic.timer
```
Review the security settings in the service file and update them as needed.
If `ProtectSystem=strict` is enabled and local repositories are used, then
the repository path must be added to the `ReadWritePaths` list.
Feel free to modify the timer file based on how frequently you'd like
borgmatic to run.
@@ -306,14 +320,3 @@ YAML library. If so, not to worry. borgmatic should install and function
correctly even without the C YAML library. And borgmatic won't be any faster
with the C library present, so you don't need to go out of your way to install
it.
## Related documentation
* [Make per-application backups](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/)
* [Deal with very large backups](https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Upgrade borgmatic](https://torsion.org/borgmatic/docs/how-to/upgrade/)
* [borgmatic configuration reference](https://torsion.org/borgmatic/docs/reference/configuration/)
* [borgmatic command-line reference](https://torsion.org/borgmatic/docs/reference/command-line/)
+4 -5
View File
@@ -1,5 +1,9 @@
---
title: How to upgrade borgmatic
eleventyNavigation:
key: Upgrade borgmatic
parent: How-to guides
order: 10
---
## Upgrading
@@ -111,8 +115,3 @@ sudo pip3 install --user borgmatic
That's it! borgmatic will continue using your /etc/borgmatic configuration
files.
## Related documentation
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
+5 -1
View File
@@ -1,5 +1,9 @@
---
title: borgmatic command-line reference
title: Command-line reference
eleventyNavigation:
key: Command-line reference
parent: Reference guides
order: 1
---
## borgmatic options
+5 -1
View File
@@ -1,5 +1,9 @@
---
title: borgmatic configuration reference
title: Configuration reference
eleventyNavigation:
key: Configuration reference
parent: Reference guides
order: 0
---
## Configuration file
+5
View File
@@ -0,0 +1,5 @@
---
eleventyNavigation:
key: Reference guides
permalink: false
---
+32
View File
@@ -7,6 +7,38 @@ ConditionACPower=true
[Service]
Type=oneshot
# Security settings for systemd running as root
# For more details about this settings check the systemd manuals
# https://www.freedesktop.org/software/systemd/man/systemd.exec.html
LockPersonality=true
# Certain borgmatic features like Healthchecks integration need MemoryDenyWriteExecute to be off.
# But you can try setting it to "yes" for improved security if you don't use those features.
MemoryDenyWriteExecute=no
NoNewPrivileges=yes
PrivateDevices=yes
PrivateTmp=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
# Restrict write access
# Change to 'ProtectSystem=strict' and uncomment 'ProtectHome' to make the whole file
# system read-only be default and uncomment 'ReadWritePaths' for the required write access.
# Add local repositroy paths to the list of 'ReadWritePaths' like '-/mnt/my_backup_drive'.
ProtectSystem=full
# ProtectHome=read-only
# ReadWritePaths=-/root/.config/borg -/root/.cache/borg -/root/.borgmatic
CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_NET_RAW
# Lower CPU and I/O priority.
Nice=19
CPUSchedulingPolicy=batch
+1 -1
View File
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup
VERSION = '1.5.7.dev0'
VERSION = '1.5.12'
setup(
+1
View File
@@ -19,6 +19,7 @@ def generate_configuration(config_path, repository_path):
open(config_path)
.read()
.replace('user@backupserver:sourcehostname.borg', repository_path)
.replace('- user@backupserver:{fqdn}', '')
.replace('- /home', '- {}'.format(config_path))
.replace('- /etc', '')
.replace('- /var/log/syslog*', '')
@@ -71,6 +71,35 @@ def test_parse_arguments_with_log_file_verbosity_overrides_default():
assert global_arguments.log_file_verbosity == -1
def test_parse_arguments_with_single_override_parses():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
arguments = module.parse_arguments('--override', 'foo.bar=baz')
global_arguments = arguments['global']
assert global_arguments.overrides == ['foo.bar=baz']
def test_parse_arguments_with_multiple_overrides_parses():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
arguments = module.parse_arguments('--override', 'foo.bar=baz', 'foo.quux=7')
global_arguments = arguments['global']
assert global_arguments.overrides == ['foo.bar=baz', 'foo.quux=7']
def test_parse_arguments_with_multiple_overrides_and_flags_parses():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
arguments = module.parse_arguments(
'--override', 'foo.bar=baz', '--override', 'foo.quux=7', 'this.that=8'
)
global_arguments = arguments['global']
assert global_arguments.overrides == ['foo.bar=baz', 'foo.quux=7', 'this.that=8']
def test_parse_arguments_with_list_json_overrides_default():
arguments = module.parse_arguments('list', '--json')
+4 -4
View File
@@ -87,8 +87,8 @@ location:
assert module._comment_out_optional_configuration(config.strip()) == expected_config.strip()
def test_render_configuration_converts_configuration_to_yaml_string():
yaml_string = module._render_configuration({'foo': 'bar'})
def testrender_configuration_converts_configuration_to_yaml_string():
yaml_string = module.render_configuration({'foo': 'bar'})
assert yaml_string == 'foo: bar\n'
@@ -194,7 +194,7 @@ def test_generate_sample_configuration_does_not_raise():
flexmock(module.yaml).should_receive('round_trip_load')
flexmock(module).should_receive('_schema_to_sample_configuration')
flexmock(module).should_receive('merge_source_configuration_into_destination')
flexmock(module).should_receive('_render_configuration')
flexmock(module).should_receive('render_configuration')
flexmock(module).should_receive('_comment_out_optional_configuration')
flexmock(module).should_receive('write_configuration')
@@ -208,7 +208,7 @@ def test_generate_sample_configuration_with_source_filename_does_not_raise():
flexmock(module.load).should_receive('load_configuration')
flexmock(module).should_receive('_schema_to_sample_configuration')
flexmock(module).should_receive('merge_source_configuration_into_destination')
flexmock(module).should_receive('_render_configuration')
flexmock(module).should_receive('render_configuration')
flexmock(module).should_receive('_comment_out_optional_configuration')
flexmock(module).should_receive('write_configuration')
+13
View File
@@ -143,3 +143,16 @@ def test_log_outputs_with_no_output_logs_nothing():
module.log_outputs(
(process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
)
def test_log_outputs_with_unfinished_process_re_polls():
flexmock(module.logger).should_receive('log').never()
flexmock(module).should_receive('exit_code_indicates_error').and_return(False)
process = subprocess.Popen(['true'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
flexmock(process).should_receive('poll').and_return(None).and_return(0).twice()
flexmock(module).should_receive('output_buffer_for_process').and_return(process.stdout)
module.log_outputs(
(process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
)
+28
View File
@@ -1326,6 +1326,34 @@ def test_create_archive_with_archive_name_format_accepts_borg_placeholders():
)
def test_create_archive_with_repository_accepts_borg_placeholders():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('_expand_directories').and_return(())
flexmock(module).should_receive('_expand_home_directories').and_return(())
flexmock(module).should_receive('_write_pattern_file').and_return(None)
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '{fqdn}::Documents_{hostname}-{now}', 'foo', 'bar'),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
)
module.create_archive(
dry_run=False,
repository='{fqdn}',
location_config={
'source_directories': ['foo', 'bar'],
'repositories': ['{fqdn}'],
'exclude_patterns': None,
},
storage_config={'archive_name_format': 'Documents_{hostname}-{now}'},
)
def test_create_archive_with_extra_borg_options_calls_borg_with_extra_options():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
+226
View File
@@ -0,0 +1,226 @@
import logging
from flexmock import flexmock
from borgmatic.borg import export_tar as module
from ..test_verbosity import insert_logging_mock
def insert_execute_command_mock(
command, output_log_level=logging.INFO, borg_local_path='borg', capture=True
):
flexmock(module).should_receive('execute_command').with_args(
command,
output_file=None if capture else module.DO_NOT_CAPTURE,
output_log_level=output_log_level,
borg_local_path=borg_local_path,
).once()
def test_export_tar_archive_calls_borg_with_path_parameters():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', 'repo::archive', 'test.tar', 'path1', 'path2')
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=['path1', 'path2'],
destination_path='test.tar',
storage_config={},
)
def test_export_tar_archive_calls_borg_with_local_path_parameters():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg1', 'export-tar', 'repo::archive', 'test.tar'), borg_local_path='borg1'
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
local_path='borg1',
)
def test_export_tar_archive_calls_borg_with_remote_path_parameters():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', '--remote-path', 'borg1', 'repo::archive', 'test.tar')
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
remote_path='borg1',
)
def test_export_tar_archive_calls_borg_with_umask_parameters():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', '--umask', '0770', 'repo::archive', 'test.tar')
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={'umask': '0770'},
)
def test_export_tar_archive_calls_borg_with_lock_wait_parameters():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', '--lock-wait', '5', 'repo::archive', 'test.tar')
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={'lock_wait': '5'},
)
def test_export_tar_archive_with_log_info_calls_borg_with_info_parameter():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(('borg', 'export-tar', '--info', 'repo::archive', 'test.tar'))
insert_logging_mock(logging.INFO)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
)
def test_export_tar_archive_with_log_debug_calls_borg_with_debug_parameters():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', '--debug', '--show-rc', 'repo::archive', 'test.tar')
)
insert_logging_mock(logging.DEBUG)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
)
def test_export_tar_archive_calls_borg_with_dry_run_parameter():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
flexmock(module).should_receive('execute_command').never()
module.export_tar_archive(
dry_run=True,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
)
def test_export_tar_archive_calls_borg_with_tar_filter_parameters():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', '--tar-filter', 'bzip2', 'repo::archive', 'test.tar')
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
tar_filter='bzip2',
)
def test_export_tar_archive_calls_borg_with_list_parameter():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', '--list', 'repo::archive', 'test.tar'),
output_log_level=logging.WARNING,
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
files=True,
)
def test_export_tar_archive_calls_borg_with_strip_components_parameter():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(
('borg', 'export-tar', '--strip-components', '5', 'repo::archive', 'test.tar')
)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
strip_components=5,
)
def test_export_tar_archive_skips_abspath_for_remote_repository_parameter():
flexmock(module.os.path).should_receive('abspath').never()
insert_execute_command_mock(('borg', 'export-tar', 'server:repo::archive', 'test.tar'))
module.export_tar_archive(
dry_run=False,
repository='server:repo',
archive='archive',
paths=None,
destination_path='test.tar',
storage_config={},
)
def test_export_tar_archive_calls_borg_with_stdout_destination_path():
flexmock(module.os.path).should_receive('abspath').and_return('repo')
insert_execute_command_mock(('borg', 'export-tar', 'repo::archive', '-'), capture=False)
module.export_tar_archive(
dry_run=False,
repository='repo',
archive='archive',
paths=None,
destination_path='-',
storage_config={},
)
+11
View File
@@ -54,6 +54,17 @@ def test_run_configuration_calls_hooks_for_check_action():
list(module.run_configuration('test.yaml', config, arguments))
def test_run_configuration_calls_hooks_for_extract_action():
flexmock(module.borg_environment).should_receive('initialize')
flexmock(module.command).should_receive('execute_hook').twice()
flexmock(module.dispatch).should_receive('call_hooks').never()
flexmock(module).should_receive('run_actions').and_return([])
config = {'location': {'repositories': ['foo']}}
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'extract': flexmock()}
list(module.run_configuration('test.yaml', config, arguments))
def test_run_configuration_does_not_trigger_hooks_for_list_action():
flexmock(module.borg_environment).should_receive('initialize')
flexmock(module.command).should_receive('execute_hook').never()
+18
View File
@@ -45,6 +45,7 @@ def test_collect_config_filenames_collects_yml_file_endings():
mock_path.should_receive('isdir').with_args('config.yaml').and_return(False)
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d').and_return(True)
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d/foo.yml').and_return(False)
flexmock(module.os).should_receive('access').and_return(True)
flexmock(module.os).should_receive('listdir')
flexmock(sys.modules['builtins']).should_receive('sorted').and_return(['foo.yml'])
@@ -62,6 +63,7 @@ def test_collect_config_filenames_collects_files_from_given_directories_and_igno
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d/foo.yaml').and_return(False)
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d/bar').and_return(True)
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d/baz.yaml').and_return(False)
flexmock(module.os).should_receive('access').and_return(True)
flexmock(module.os).should_receive('listdir')
flexmock(sys.modules['builtins']).should_receive('sorted').and_return(
['foo.yaml', 'bar', 'baz.yaml']
@@ -84,6 +86,7 @@ def test_collect_config_filenames_collects_files_from_given_directories_and_igno
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d/foo.yaml').and_return(False)
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d/bar.yaml~').and_return(False)
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d/baz.txt').and_return(False)
flexmock(module.os).should_receive('access').and_return(True)
flexmock(module.os).should_receive('listdir')
flexmock(sys.modules['builtins']).should_receive('sorted').and_return(
['foo.yaml', 'bar.yaml~', 'baz.txt']
@@ -94,6 +97,21 @@ def test_collect_config_filenames_collects_files_from_given_directories_and_igno
assert config_filenames == ('/etc/borgmatic.d/foo.yaml',)
def test_collect_config_filenames_skips_permission_denied_directories():
config_paths = ('config.yaml', '/etc/borgmatic.d')
mock_path = flexmock(module.os.path)
mock_path.should_receive('exists').and_return(True)
mock_path.should_receive('isdir').with_args('config.yaml').and_return(False)
mock_path.should_receive('isdir').with_args('/etc/borgmatic.d').and_return(True)
flexmock(module.os).should_receive('access').and_return(False)
flexmock(module.os).should_receive('listdir')
flexmock(sys.modules['builtins']).should_receive('sorted').and_return(['config.yaml'])
config_filenames = tuple(module.collect_config_filenames(config_paths))
assert config_filenames == ('config.yaml',)
def test_collect_config_filenames_skips_etc_borgmatic_config_dot_yaml_if_it_does_not_exist():
config_paths = ('config.yaml', '/etc/borgmatic/config.yaml')
mock_path = flexmock(module.os.path)
+11 -1
View File
@@ -58,7 +58,7 @@ def test_call_hooks_calls_each_hook_and_collects_return_values():
assert return_values == expected_return_values
def test_call_hooks_calls_skips_return_values_for_unconfigured_hooks():
def test_call_hooks_calls_skips_return_values_for_missing_hooks():
hooks = {'super_hook': flexmock()}
expected_return_values = {'super_hook': flexmock()}
flexmock(module).should_receive('call_hook').and_return(expected_return_values['super_hook'])
@@ -66,3 +66,13 @@ def test_call_hooks_calls_skips_return_values_for_unconfigured_hooks():
return_values = module.call_hooks('do_stuff', hooks, 'prefix', ('super_hook', 'other_hook'), 55)
assert return_values == expected_return_values
def test_call_hooks_calls_skips_return_values_for_null_hooks():
hooks = {'super_hook': flexmock(), 'other_hook': None}
expected_return_values = {'super_hook': flexmock()}
flexmock(module).should_receive('call_hook').and_return(expected_return_values['super_hook'])
return_values = module.call_hooks('do_stuff', hooks, 'prefix', ('super_hook', 'other_hook'), 55)
assert return_values == expected_return_values