mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-22 02:03:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79d4888e22 | ||
|
|
de61fdef48 | ||
|
|
93caeba200 | ||
|
|
3c723e8d99 | ||
|
|
c5776447b9 | ||
|
|
5356f487a5 | ||
|
|
72bd96c656 | ||
|
|
f611fe7be3 | ||
|
|
dd6ea40a36 | ||
|
|
ea1274d1c6 | ||
|
|
8526468975 | ||
|
|
95c415f416 | ||
|
|
06dc336481 | ||
|
|
893fca2816 | ||
|
|
99590cb6b6 | ||
|
|
b3fd1be5f6 | ||
|
|
a23083f737 | ||
|
|
8306b758e8 | ||
|
|
218cbd5289 | ||
|
|
2ac58670d5 | ||
|
|
6f82c9979b | ||
|
|
0a659a397f | ||
|
|
2781873faf | ||
|
|
3aaa89fb08 | ||
|
|
35d542a676 | ||
|
|
d0b9c436b1 | ||
|
|
37cc229749 | ||
|
|
17c2d109e5 | ||
|
|
c8d5de2179 | ||
|
|
32e15dc905 | ||
|
|
f5ebca4907 | ||
|
|
01db676d68 | ||
|
|
d2d92b1f1a | ||
|
|
27cbe9dfc0 | ||
|
|
8fb830099f | ||
|
|
463a133a63 | ||
|
|
a16fed8887 | ||
|
|
33113890f5 | ||
|
|
abd47fc14e | ||
|
|
7fb4061759 | ||
|
|
b320e74ad5 | ||
|
|
0ed8f67b9d | ||
|
|
a12a1121b6 | ||
|
|
795e18773b | ||
|
|
aa14449857 | ||
|
|
ed7b1cd3d7 | ||
|
|
a155eefa23 | ||
|
|
398665be9e | ||
|
|
6db232d4ac | ||
|
|
d7277893fb | ||
|
|
00033bf0a8 |
@@ -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");
|
||||
|
||||
@@ -1,3 +1,49 @@
|
||||
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.
|
||||
* #333: Fix for potential data loss (data not getting backed up) when borgmatic omitted configured
|
||||
source directories in certain situations. Specifically, this occurred when two source directories
|
||||
on different filesystems were related by parentage (e.g. "/foo" and "/foo/bar/baz") and the
|
||||
one_file_system option was enabled.
|
||||
* Update documentation code fragments theme to better match the rest of the page.
|
||||
* Improve configuration reference documentation readability via more aggressive word-wrapping in
|
||||
configuration schema descriptions.
|
||||
|
||||
1.5.6
|
||||
* #292: Allow before_backup and similiar hooks to exit with a soft failure without altering the
|
||||
monitoring status on Healthchecks or other providers. Support this by waiting to ping monitoring
|
||||
services with a "start" status until after before_* hooks finish. Failures in before_* hooks
|
||||
still trigger a monitoring "fail" status.
|
||||
* #316: Fix hang when a stale database dump named pipe from an aborted borgmatic run remains on
|
||||
disk.
|
||||
* #323: Fix for certain configuration options like ssh_command impacting Borg invocations for
|
||||
separate configuration files.
|
||||
* #324: Add "borgmatic extract --strip-components" flag to remove leading path components when
|
||||
extracting an archive.
|
||||
* Tweak comment indentation in generated configuration file for clarity.
|
||||
* Link to Borgmacator GNOME AppIndicator from monitoring documentation.
|
||||
|
||||
1.5.5
|
||||
* #314: Fix regression in support for PostgreSQL's "directory" dump format. Unlike other dump
|
||||
formats, the "directory" dump format does not stream directly to/from Borg.
|
||||
|
||||
@@ -22,8 +22,8 @@ location:
|
||||
|
||||
# Paths of local or remote repositories to backup to.
|
||||
repositories:
|
||||
- 1234@usw-s001.rsync.net:backups.borg
|
||||
- k8pDxu32@k8pDxu32.repo.borgbase.com:repo
|
||||
- 1234@usw-s001.rsync.net:backups.borg
|
||||
- /var/lib/backups/local.borg
|
||||
|
||||
retention:
|
||||
@@ -66,30 +66,19 @@ borgmatic is powered by [Borg Backup](https://www.borgbackup.org/).
|
||||
<a href="https://healthchecks.io/"><img src="docs/static/healthchecks.png" alt="Healthchecks" height="60px" style="margin-bottom:20px;"></a>
|
||||
<a href="https://cronitor.io/"><img src="docs/static/cronitor.png" alt="Cronitor" height="60px" style="margin-bottom:20px;"></a>
|
||||
<a href="https://cronhub.io/"><img src="docs/static/cronhub.png" alt="Cronhub" height="60px" style="margin-bottom:20px;"></a>
|
||||
<a href="https://www.pagerduty.com/"><img src="docs/static/pagerduty.png" alt="PagerDuty" height="60px" style="margin-bottom:20px;"></a>
|
||||
<a href="https://www.rsync.net/cgi-bin/borg.cgi?campaign=borg&adgroup=borgmatic"><img src="docs/static/rsyncnet.png" alt="rsync.net" height="60px" style="margin-bottom:20px;"></a>
|
||||
<a href="https://www.pagerduty.com/"><img src="docs/static/pagerduty.png" alt="PagerDuty" height="60px" style="margin-bottom:20px;"></a>
|
||||
<a href="https://www.borgbase.com/?utm_source=borgmatic"><img src="docs/static/borgbase.png" alt="BorgBase" height="60px" style="margin-bottom:20px;"></a>
|
||||
<img src="docs/static/rsyncnet.png" alt="rsync.net" height="60px" style="margin-bottom:20px;">
|
||||
|
||||
|
||||
## 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
|
||||
@@ -100,8 +89,8 @@ services helps support borgmatic development and hosting. (These are referral
|
||||
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>rsync.net: Cloud Storage provider with full support for borg and any other SSH/SFTP tool</li>
|
||||
</ul>
|
||||
|
||||
## Support and contributing
|
||||
|
||||
+37
-17
@@ -44,10 +44,24 @@ def _expand_home_directories(directories):
|
||||
return tuple(os.path.expanduser(directory) for directory in directories)
|
||||
|
||||
|
||||
def deduplicate_directories(directories):
|
||||
def map_directories_to_devices(directories): # pragma: no cover
|
||||
'''
|
||||
Given a sequence of directories, return them as a sorted tuple with all duplicate child
|
||||
directories removed. For instance, if paths is ('/foo', '/foo/bar'), return just: ('/foo',)
|
||||
Given a sequence of directories, return a map from directory to an identifier for the device on
|
||||
which that directory resides. This is handy for determining whether two different directories
|
||||
are on the same filesystem (have the same device identifier).
|
||||
'''
|
||||
return {directory: os.stat(directory).st_dev for directory in directories}
|
||||
|
||||
|
||||
def deduplicate_directories(directory_devices):
|
||||
'''
|
||||
Given a map from directory to the identifier for the device on which that directory resides,
|
||||
return the directories as a sorted tuple with all duplicate child directories removed. For
|
||||
instance, if paths is ('/foo', '/foo/bar'), return just: ('/foo',)
|
||||
|
||||
The one exception to this rule is if two paths are on different filesystems (devices). In that
|
||||
case, they won't get de-duplicated in case they both need to be passed to Borg (e.g. the
|
||||
location.one_file_system option is true).
|
||||
|
||||
The idea is that if Borg is given a parent directory, then it doesn't also need to be given
|
||||
child directories, because it will naturally spider the contents of the parent directory. And
|
||||
@@ -56,19 +70,23 @@ def deduplicate_directories(directories):
|
||||
Borg.
|
||||
'''
|
||||
deduplicated = set()
|
||||
directories = sorted(directory_devices.keys())
|
||||
|
||||
for directory in sorted(directories):
|
||||
# If the directory is "/", that contains all child directories, so we can early out.
|
||||
if directory == os.path.sep:
|
||||
return (os.path.sep,)
|
||||
for directory in directories:
|
||||
deduplicated.add(directory)
|
||||
parents = pathlib.PurePath(directory).parents
|
||||
|
||||
# If no other directories are parents of current directory (even n levels up), then the
|
||||
# current directory isn't a duplicate.
|
||||
if not any(
|
||||
pathlib.PurePath(other_directory) in pathlib.PurePath(directory).parents
|
||||
for other_directory in directories
|
||||
):
|
||||
deduplicated.add(directory)
|
||||
# If another directory in the given list is a parent of current directory (even n levels
|
||||
# up) and both are on the same filesystem, then the current directory is a duplicate.
|
||||
for other_directory in directories:
|
||||
for parent in parents:
|
||||
if (
|
||||
pathlib.PurePath(other_directory) == parent
|
||||
and directory_devices[other_directory] == directory_devices[directory]
|
||||
):
|
||||
if directory in deduplicated:
|
||||
deduplicated.remove(directory)
|
||||
break
|
||||
|
||||
return tuple(sorted(deduplicated))
|
||||
|
||||
@@ -179,9 +197,11 @@ def create_archive(
|
||||
create command while also triggering the given processes to produce output.
|
||||
'''
|
||||
sources = deduplicate_directories(
|
||||
_expand_directories(
|
||||
location_config['source_directories']
|
||||
+ borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
|
||||
map_directories_to_devices(
|
||||
_expand_directories(
|
||||
location_config['source_directories']
|
||||
+ borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -19,9 +19,15 @@ DEFAULT_BOOL_OPTION_TO_ENVIRONMENT_VARIABLE = {
|
||||
|
||||
def initialize(storage_config):
|
||||
for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
|
||||
value = storage_config.get(option_name)
|
||||
|
||||
# Options from borgmatic configuration take precedence over already set BORG_* environment
|
||||
# variables.
|
||||
value = storage_config.get(option_name) or os.environ.get(environment_variable_name)
|
||||
|
||||
if value:
|
||||
os.environ[environment_variable_name] = value
|
||||
else:
|
||||
os.environ.pop(environment_variable_name, None)
|
||||
|
||||
for (
|
||||
option_name,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -64,6 +64,7 @@ def extract_archive(
|
||||
local_path='borg',
|
||||
remote_path=None,
|
||||
destination_path=None,
|
||||
strip_components=None,
|
||||
progress=False,
|
||||
extract_to_stdout=False,
|
||||
):
|
||||
@@ -91,6 +92,7 @@ def extract_archive(
|
||||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
+ (('--strip-components', str(strip_components)) if strip_components else ())
|
||||
+ (('--progress',) if progress else ())
|
||||
+ (('--stdout',) if extract_to_stdout else ())
|
||||
+ ('::'.join((repository if ':' in repository else os.path.abspath(repository), archive)),)
|
||||
|
||||
@@ -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'],
|
||||
@@ -340,6 +341,13 @@ def parse_arguments(*unparsed_arguments):
|
||||
dest='destination',
|
||||
help='Directory to extract files into, defaults to the current directory',
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'--strip-components',
|
||||
type=int,
|
||||
metavar='NUMBER',
|
||||
dest='strip_components',
|
||||
help='Number of leading path components to remove from each extracted path. Skip paths with fewer elements',
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
@@ -351,6 +359,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'],
|
||||
|
||||
@@ -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
|
||||
@@ -59,11 +60,10 @@ def run_configuration(config_filename, config, arguments):
|
||||
try:
|
||||
if prune_create_or_check:
|
||||
dispatch.call_hooks(
|
||||
'ping_monitor',
|
||||
'initialize_monitor',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitor.State.START,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
@@ -91,6 +91,24 @@ 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',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitor.State.START,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
except (OSError, CalledProcessError) as error:
|
||||
if command.considered_soft_failure(config_filename, error):
|
||||
return
|
||||
@@ -155,7 +173,15 @@ def run_configuration(config_filename, config, arguments):
|
||||
'post-check',
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
if {'prune', 'create', 'check'}.intersection(arguments):
|
||||
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',
|
||||
hooks,
|
||||
@@ -165,6 +191,14 @@ def run_configuration(config_filename, config, arguments):
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
dispatch.call_hooks(
|
||||
'destroy_monitor',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
except (OSError, CalledProcessError) as error:
|
||||
if command.considered_soft_failure(config_filename, error):
|
||||
return
|
||||
@@ -195,6 +229,14 @@ def run_configuration(config_filename, config, arguments):
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
dispatch.call_hooks(
|
||||
'destroy_monitor',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
except (OSError, CalledProcessError) as error:
|
||||
if command.considered_soft_failure(config_filename, error):
|
||||
return
|
||||
@@ -319,8 +361,33 @@ def run_actions(
|
||||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
destination_path=arguments['extract'].destination,
|
||||
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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -37,9 +37,7 @@ def _schema_to_sample_configuration(schema, level=0, parent_is_sequence=False):
|
||||
for item_schema in schema['seq']
|
||||
]
|
||||
)
|
||||
add_comments_to_configuration_sequence(
|
||||
config, schema, indent=(level * INDENT) + SEQUENCE_INDENT
|
||||
)
|
||||
add_comments_to_configuration_sequence(config, schema, indent=(level * INDENT))
|
||||
elif 'map' in schema:
|
||||
config = yaml.comments.CommentedMap(
|
||||
[
|
||||
@@ -86,8 +84,8 @@ def _comment_out_optional_configuration(rendered_config):
|
||||
optional = False
|
||||
|
||||
for line in rendered_config.split('\n'):
|
||||
# Upon encountering an optional configuration option, commenting out lines until the next
|
||||
# blank line.
|
||||
# Upon encountering an optional configuration option, comment out lines until the next blank
|
||||
# line.
|
||||
if line.strip().startswith('# {}'.format(COMMENTED_OUT_SENTINEL)):
|
||||
optional = True
|
||||
continue
|
||||
@@ -142,7 +140,7 @@ def add_comments_to_configuration_sequence(config, schema, indent=0):
|
||||
|
||||
```
|
||||
things:
|
||||
# First key description. Added by this function.
|
||||
# First key description. Added by this function.
|
||||
- key: foo
|
||||
# Second key description. Added by add_comments_to_configuration_map().
|
||||
other: bar
|
||||
|
||||
+325
-206
@@ -3,9 +3,10 @@ version: 1
|
||||
map:
|
||||
location:
|
||||
desc: |
|
||||
Where to look for files to backup, and where to store those backups. See
|
||||
https://borgbackup.readthedocs.io/en/stable/quickstart.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create for details.
|
||||
Where to look for files to backup, and where to store those backups.
|
||||
See https://borgbackup.readthedocs.io/en/stable/quickstart.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/create.html
|
||||
for details.
|
||||
required: true
|
||||
map:
|
||||
source_directories:
|
||||
@@ -13,7 +14,8 @@ map:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of source directories to backup (required). Globs and tildes are expanded.
|
||||
List of source directories to backup (required). Globs and
|
||||
tildes are expanded.
|
||||
example:
|
||||
- /home
|
||||
- /etc
|
||||
@@ -23,21 +25,25 @@ map:
|
||||
seq:
|
||||
- type: str
|
||||
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.
|
||||
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.
|
||||
example:
|
||||
- user@backupserver:sourcehostname.borg
|
||||
one_file_system:
|
||||
type: bool
|
||||
desc: |
|
||||
Stay in same file system (do not cross mount points). Defaults to false. But when
|
||||
a database hook is used, the setting here is ignored and one_file_system is
|
||||
considered true.
|
||||
Stay in same file system (do not cross mount points).
|
||||
Defaults to false. But when a database hook is used, the
|
||||
setting here is ignored and one_file_system is considered
|
||||
true.
|
||||
example: true
|
||||
numeric_owner:
|
||||
type: bool
|
||||
desc: Only store/extract numeric user and group identifiers. Defaults to false.
|
||||
desc: |
|
||||
Only store/extract numeric user and group identifiers.
|
||||
Defaults to false.
|
||||
example: true
|
||||
atime:
|
||||
type: bool
|
||||
@@ -49,27 +55,32 @@ map:
|
||||
example: false
|
||||
birthtime:
|
||||
type: bool
|
||||
desc: Store birthtime (creation date) into archive. Defaults to true.
|
||||
desc: |
|
||||
Store birthtime (creation date) into archive. Defaults to
|
||||
true.
|
||||
example: false
|
||||
read_special:
|
||||
type: bool
|
||||
desc: |
|
||||
Use Borg's --read-special flag to allow backup of block and other special
|
||||
devices. Use with caution, as it will lead to problems if used when
|
||||
backing up special devices such as /dev/zero. Defaults to false. But when a
|
||||
database hook is used, the setting here is ignored and read_special is
|
||||
Use Borg's --read-special flag to allow backup of block and
|
||||
other special devices. Use with caution, as it will lead to
|
||||
problems if used when backing up special devices such as
|
||||
/dev/zero. Defaults to false. But when a database hook is
|
||||
used, the setting here is ignored and read_special is
|
||||
considered true.
|
||||
example: false
|
||||
bsd_flags:
|
||||
type: bool
|
||||
desc: Record bsdflags (e.g. NODUMP, IMMUTABLE) in archive. Defaults to true.
|
||||
desc: |
|
||||
Record bsdflags (e.g. NODUMP, IMMUTABLE) in archive.
|
||||
Defaults to true.
|
||||
example: true
|
||||
files_cache:
|
||||
type: str
|
||||
desc: |
|
||||
Mode in which to operate the files cache. See
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/create.html#description for
|
||||
details. Defaults to "ctime,size,inode".
|
||||
http://borgbackup.readthedocs.io/en/stable/usage/create.html
|
||||
for details. Defaults to "ctime,size,inode".
|
||||
example: ctime,size,inode
|
||||
local_path:
|
||||
type: str
|
||||
@@ -83,9 +94,10 @@ map:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Any paths matching these patterns are included/excluded from backups. Globs are
|
||||
expanded. (Tildes are not.) Note that Borg considers this option experimental.
|
||||
See the output of "borg help patterns" for more details. Quote any value if it
|
||||
Any paths matching these patterns are included/excluded from
|
||||
backups. Globs are expanded. (Tildes are not.) Note that
|
||||
Borg considers this option experimental. See the output of
|
||||
"borg help patterns" for more details. Quote any value if it
|
||||
contains leading punctuation, so it parses correctly.
|
||||
example:
|
||||
- 'R /'
|
||||
@@ -96,48 +108,53 @@ map:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Read include/exclude patterns from one or more separate named files, one pattern
|
||||
per line. Note that Borg considers this option experimental. See the output of
|
||||
"borg help patterns" for more details.
|
||||
Read include/exclude patterns from one or more separate
|
||||
named files, one pattern per line. Note that Borg considers
|
||||
this option experimental. See the output of "borg help
|
||||
patterns" for more details.
|
||||
example:
|
||||
- /etc/borgmatic/patterns
|
||||
exclude_patterns:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Any paths matching these patterns are excluded from backups. Globs and tildes
|
||||
are expanded. See the output of "borg help patterns" for more details.
|
||||
Any paths matching these patterns are excluded from backups.
|
||||
Globs and tildes are expanded. See the output of "borg help
|
||||
patterns" for more details.
|
||||
example:
|
||||
- '*.pyc'
|
||||
- ~/*/.cache
|
||||
- /home/*/.cache
|
||||
- /etc/ssl
|
||||
exclude_from:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Read exclude patterns from one or more separate named files, one pattern per
|
||||
line. See the output of "borg help patterns" for more details.
|
||||
Read exclude patterns from one or more separate named files,
|
||||
one pattern per line. See the output of "borg help patterns"
|
||||
for more details.
|
||||
example:
|
||||
- /etc/borgmatic/excludes
|
||||
exclude_caches:
|
||||
type: bool
|
||||
desc: |
|
||||
Exclude directories that contain a CACHEDIR.TAG file. See
|
||||
http://www.brynosaurus.com/cachedir/spec.html for details. Defaults to false.
|
||||
http://www.brynosaurus.com/cachedir/spec.html for details.
|
||||
Defaults to false.
|
||||
example: true
|
||||
exclude_if_present:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Exclude directories that contain a file with the given filenames. Defaults to not
|
||||
set.
|
||||
Exclude directories that contain a file with the given
|
||||
filenames. Defaults to not set.
|
||||
example:
|
||||
- .nobackup
|
||||
keep_exclude_tags:
|
||||
type: bool
|
||||
desc: |
|
||||
If true, the exclude_if_present filename is included in backups. Defaults to
|
||||
false, meaning that the exclude_if_present filename is omitted from backups.
|
||||
If true, the exclude_if_present filename is included in
|
||||
backups. Defaults to false, meaning that the
|
||||
exclude_if_present filename is omitted from backups.
|
||||
example: true
|
||||
exclude_nodump:
|
||||
type: bool
|
||||
@@ -147,90 +164,103 @@ map:
|
||||
borgmatic_source_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for additional source files used for temporary internal state like
|
||||
borgmatic database dumps. Note that changing this path prevents "borgmatic
|
||||
restore" from finding any database dumps created before the change. Defaults
|
||||
to ~/.borgmatic
|
||||
Path for additional source files used for temporary internal
|
||||
state like borgmatic database dumps. Note that changing this
|
||||
path prevents "borgmatic restore" from finding any database
|
||||
dumps created before the change. Defaults to ~/.borgmatic
|
||||
example: /tmp/borgmatic
|
||||
storage:
|
||||
desc: |
|
||||
Repository storage options. See
|
||||
https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/create.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/general.html for
|
||||
details.
|
||||
map:
|
||||
encryption_passcommand:
|
||||
type: str
|
||||
desc: |
|
||||
The standard output of this command is used to unlock the encryption key. Only
|
||||
use on repositories that were initialized with passcommand/repokey encryption.
|
||||
Note that if both encryption_passcommand and encryption_passphrase are set,
|
||||
then encryption_passphrase takes precedence. Defaults to not set.
|
||||
The standard output of this command is used to unlock the
|
||||
encryption key. Only use on repositories that were
|
||||
initialized with passcommand/repokey encryption. Note that
|
||||
if both encryption_passcommand and encryption_passphrase are
|
||||
set, then encryption_passphrase takes precedence. Defaults
|
||||
to not set.
|
||||
example: "secret-tool lookup borg-repository repo-name"
|
||||
encryption_passphrase:
|
||||
type: str
|
||||
desc: |
|
||||
Passphrase to unlock the encryption key with. Only use on repositories that were
|
||||
initialized with passphrase/repokey encryption. Quote the value if it contains
|
||||
punctuation, so it parses correctly. And backslash any quote or backslash
|
||||
Passphrase to unlock the encryption key with. Only use on
|
||||
repositories that were initialized with passphrase/repokey
|
||||
encryption. Quote the value if it contains punctuation, so
|
||||
it parses correctly. And backslash any quote or backslash
|
||||
literals as well. Defaults to not set.
|
||||
example: "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
checkpoint_interval:
|
||||
type: int
|
||||
desc: |
|
||||
Number of seconds between each checkpoint during a long-running backup. See
|
||||
https://borgbackup.readthedocs.io/en/stable/faq.html#if-a-backup-stops-mid-way-does-the-already-backed-up-data-stay-there
|
||||
for details. Defaults to checkpoints every 1800 seconds (30 minutes).
|
||||
Number of seconds between each checkpoint during a
|
||||
long-running backup. See
|
||||
https://borgbackup.readthedocs.io/en/stable/faq.html
|
||||
for details. Defaults to checkpoints every 1800 seconds (30
|
||||
minutes).
|
||||
example: 1800
|
||||
chunker_params:
|
||||
type: str
|
||||
desc: |
|
||||
Specify the parameters passed to then chunker (CHUNK_MIN_EXP, CHUNK_MAX_EXP,
|
||||
HASH_MASK_BITS, HASH_WINDOW_SIZE). See https://borgbackup.readthedocs.io/en/stable/internals.html
|
||||
Specify the parameters passed to then chunker
|
||||
(CHUNK_MIN_EXP, CHUNK_MAX_EXP, HASH_MASK_BITS,
|
||||
HASH_WINDOW_SIZE). See
|
||||
https://borgbackup.readthedocs.io/en/stable/internals.html
|
||||
for details. Defaults to "19,23,21,4095".
|
||||
example: 19,23,21,4095
|
||||
compression:
|
||||
type: str
|
||||
desc: |
|
||||
Type of compression to use when creating archives. See
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-create for details.
|
||||
Defaults to "lz4".
|
||||
http://borgbackup.readthedocs.io/en/stable/usage/create.html
|
||||
for details. Defaults to "lz4".
|
||||
example: lz4
|
||||
remote_rate_limit:
|
||||
type: int
|
||||
desc: Remote network upload rate limit in kiBytes/second. Defaults to unlimited.
|
||||
desc: |
|
||||
Remote network upload rate limit in kiBytes/second. Defaults
|
||||
to unlimited.
|
||||
example: 100
|
||||
ssh_command:
|
||||
type: str
|
||||
desc: |
|
||||
Command to use instead of "ssh". This can be used to specify ssh options.
|
||||
Defaults to not set.
|
||||
Command to use instead of "ssh". This can be used to specify
|
||||
ssh options. Defaults to not set.
|
||||
example: ssh -i /path/to/private/key
|
||||
borg_base_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Base path used for various Borg directories. Defaults to $HOME, ~$USER, or ~.
|
||||
See https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for details.
|
||||
Base path used for various Borg directories. Defaults to
|
||||
$HOME, ~$USER, or ~.
|
||||
example: /path/to/base
|
||||
borg_config_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg configuration files. Defaults to $borg_base_directory/.config/borg
|
||||
Path for Borg configuration files. Defaults to
|
||||
$borg_base_directory/.config/borg
|
||||
example: /path/to/base/config
|
||||
borg_cache_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg cache files. Defaults to $borg_base_directory/.cache/borg
|
||||
Path for Borg cache files. Defaults to
|
||||
$borg_base_directory/.cache/borg
|
||||
example: /path/to/base/cache
|
||||
borg_security_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg security and encryption nonce files. Defaults to $borg_base_directory/.config/borg/security
|
||||
Path for Borg security and encryption nonce files. Defaults
|
||||
to $borg_base_directory/.config/borg/security
|
||||
example: /path/to/base/config/security
|
||||
borg_keys_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg encryption key files. Defaults to $borg_base_directory/.config/borg/keys
|
||||
Path for Borg encryption key files. Defaults to
|
||||
$borg_base_directory/.config/borg/keys
|
||||
example: /path/to/base/config/keys
|
||||
umask:
|
||||
type: scalar
|
||||
@@ -238,58 +268,69 @@ map:
|
||||
example: 0077
|
||||
lock_wait:
|
||||
type: int
|
||||
desc: Maximum seconds to wait for acquiring a repository/cache lock. Defaults to 1.
|
||||
desc: |
|
||||
Maximum seconds to wait for acquiring a repository/cache
|
||||
lock. Defaults to 1.
|
||||
example: 5
|
||||
archive_name_format:
|
||||
type: str
|
||||
desc: |
|
||||
Name of the archive. Borg placeholders can be used. See the output of
|
||||
"borg help placeholders" for details. Defaults to
|
||||
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this option, you must
|
||||
also specify a prefix in the retention section to avoid accidental pruning of
|
||||
archives with a different archive name format. And you should also specify a
|
||||
Name of the archive. Borg placeholders can be used. See the
|
||||
output of "borg help placeholders" for details. Defaults to
|
||||
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this
|
||||
option, you must also specify a prefix in the retention
|
||||
section to avoid accidental pruning of archives with a
|
||||
different archive name format. And you should also specify a
|
||||
prefix in the consistency section as well.
|
||||
example: "{hostname}-documents-{now}"
|
||||
relocated_repo_access_is_ok:
|
||||
type: bool
|
||||
desc: Bypass Borg error about a repository that has been moved. Defaults to false.
|
||||
desc: |
|
||||
Bypass Borg error about a repository that has been moved.
|
||||
Defaults to false.
|
||||
example: true
|
||||
unknown_unencrypted_repo_access_is_ok:
|
||||
type: bool
|
||||
desc: |
|
||||
Bypass Borg error about a previously unknown unencrypted repository. Defaults to
|
||||
false.
|
||||
Bypass Borg error about a previously unknown unencrypted
|
||||
repository. Defaults to false.
|
||||
example: true
|
||||
extra_borg_options:
|
||||
map:
|
||||
init:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg init".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg init".
|
||||
example: "--make-parent-dirs"
|
||||
prune:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg prune".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg prune".
|
||||
example: "--save-space"
|
||||
create:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg create".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg create".
|
||||
example: "--no-files-cache"
|
||||
check:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg check".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg check".
|
||||
example: "--save-space"
|
||||
desc: |
|
||||
Additional options to pass directly to particular Borg commands, handy for Borg
|
||||
options that borgmatic does not yet support natively. Note that borgmatic does
|
||||
not perform any validation on these options. Running borgmatic with
|
||||
"--verbosity 2" shows the exact Borg command-line invocation.
|
||||
Additional options to pass directly to particular Borg
|
||||
commands, handy for Borg options that borgmatic does not yet
|
||||
support natively. Note that borgmatic does not perform any
|
||||
validation on these options. Running borgmatic with
|
||||
"--verbosity 2" shows the exact Borg command-line
|
||||
invocation.
|
||||
retention:
|
||||
desc: |
|
||||
Retention policy for how many backups to keep in each category. See
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-prune for details.
|
||||
At least one of the "keep" options is required for pruning to work. See
|
||||
https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/
|
||||
if you'd like to skip pruning entirely.
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/prune.html for
|
||||
details. At least one of the "keep" options is required for pruning
|
||||
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
|
||||
@@ -326,27 +367,37 @@ map:
|
||||
prefix:
|
||||
type: str
|
||||
desc: |
|
||||
When pruning, only consider archive names starting with this prefix.
|
||||
Borg placeholders can be used. See the output of "borg help placeholders" for
|
||||
details. Defaults to "{hostname}-". Use an empty value to disable the default.
|
||||
When pruning, only consider archive names starting with this
|
||||
prefix. Borg placeholders can be used. See the output of
|
||||
"borg help placeholders" for details. Defaults to
|
||||
"{hostname}-". Use an empty value to disable the default.
|
||||
example: sourcehostname
|
||||
consistency:
|
||||
desc: |
|
||||
Consistency checks to run after backups. See
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-check and
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-extract for details.
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/check.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/extract.html for
|
||||
details.
|
||||
map:
|
||||
checks:
|
||||
seq:
|
||||
- type: str
|
||||
enum: ['repository', 'archives', 'data', 'extract', 'disabled']
|
||||
enum: [
|
||||
'repository',
|
||||
'archives',
|
||||
'data',
|
||||
'extract',
|
||||
'disabled'
|
||||
]
|
||||
unique: true
|
||||
desc: |
|
||||
List of one or more consistency checks to run: "repository", "archives", "data",
|
||||
and/or "extract". Defaults to "repository" and "archives". Set to "disabled" to
|
||||
disable all consistency checks. "repository" checks the consistency of the
|
||||
repository, "archives" checks all of the archives, "data" verifies the integrity
|
||||
of the data within the archives, and "extract" does an extraction dry-run of the
|
||||
List of one or more consistency checks to run: "repository",
|
||||
"archives", "data", and/or "extract". Defaults to
|
||||
"repository" and "archives". Set to "disabled" to disable
|
||||
all consistency checks. "repository" checks the consistency
|
||||
of the repository, "archives" checks all of the archives,
|
||||
"data" verifies the integrity of the data within the
|
||||
archives, and "extract" does an extraction dry-run of the
|
||||
most recent archive. Note that "data" implies "archives".
|
||||
example:
|
||||
- repository
|
||||
@@ -355,24 +406,29 @@ map:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Paths to a subset of the repositories in the location section on which to run
|
||||
consistency checks. Handy in case some of your repositories are very large, and
|
||||
so running consistency checks on them would take too long. Defaults to running
|
||||
consistency checks on all repositories configured in the location section.
|
||||
Paths to a subset of the repositories in the location
|
||||
section on which to run consistency checks. Handy in case
|
||||
some of your repositories are very large, and so running
|
||||
consistency checks on them would take too long. Defaults to
|
||||
running consistency checks on all repositories configured in
|
||||
the location section.
|
||||
example:
|
||||
- user@backupserver:sourcehostname.borg
|
||||
check_last:
|
||||
type: int
|
||||
desc: Restrict the number of checked archives to the last n. Applies only to the
|
||||
"archives" check. Defaults to checking all archives.
|
||||
desc: |
|
||||
Restrict the number of checked archives to the last n.
|
||||
Applies only to the "archives" check. Defaults to checking
|
||||
all archives.
|
||||
example: 3
|
||||
prefix:
|
||||
type: str
|
||||
desc: |
|
||||
When performing the "archives" check, only consider archive names starting with
|
||||
this prefix. Borg placeholders can be used. See the output of
|
||||
"borg help placeholders" for details. Defaults to "{hostname}-". Use an empty
|
||||
value to disable the default.
|
||||
When performing the "archives" check, only consider archive
|
||||
names starting with this prefix. Borg placeholders can be
|
||||
used. See the output of "borg help placeholders" for
|
||||
details. Defaults to "{hostname}-". Use an empty value to
|
||||
disable the default.
|
||||
example: sourcehostname
|
||||
output:
|
||||
desc: |
|
||||
@@ -381,74 +437,111 @@ map:
|
||||
color:
|
||||
type: bool
|
||||
desc: |
|
||||
Apply color to console output. Can be overridden with --no-color command-line
|
||||
flag. Defaults to true.
|
||||
Apply color to console output. Can be overridden with
|
||||
--no-color command-line flag. Defaults to true.
|
||||
example: false
|
||||
hooks:
|
||||
desc: |
|
||||
Shell commands, scripts, or integrations to execute at various points during a borgmatic
|
||||
run. IMPORTANT: All provided commands and scripts are executed with user permissions of
|
||||
borgmatic. Do not forget to set secure permissions on this configuration file (chmod
|
||||
0600) as well as on any script called from a hook (chmod 0700) to prevent potential
|
||||
shell injection or privilege escalation.
|
||||
Shell commands, scripts, or integrations to execute at various
|
||||
points during a borgmatic run. IMPORTANT: All provided commands and
|
||||
scripts are executed with user permissions of borgmatic. Do not
|
||||
forget to set secure permissions on this configuration file (chmod
|
||||
0600) as well as on any script called from a hook (chmod 0700) to
|
||||
prevent potential shell injection or privilege escalation.
|
||||
map:
|
||||
before_backup:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute before creating a
|
||||
backup, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
before creating a backup, run once per configuration file.
|
||||
example:
|
||||
- echo "Starting a backup."
|
||||
before_prune:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute before pruning, run
|
||||
once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
before pruning, run once per configuration file.
|
||||
example:
|
||||
- echo "Starting pruning."
|
||||
before_check:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute before consistency
|
||||
checks, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
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
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute after creating a
|
||||
backup, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
after creating a backup, run once per configuration file.
|
||||
example:
|
||||
- echo "Finished a backup."
|
||||
after_prune:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute after pruning, run once
|
||||
per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
after pruning, run once per configuration file.
|
||||
example:
|
||||
- echo "Finished pruning."
|
||||
after_check:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute after consistency
|
||||
checks, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
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
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute when an exception
|
||||
occurs during a "prune", "create", or "check" action or an associated
|
||||
before/after hook.
|
||||
List of one or more shell commands or scripts to execute
|
||||
when an exception occurs during a "prune", "create", or
|
||||
"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:
|
||||
@@ -456,16 +549,17 @@ map:
|
||||
required: true
|
||||
type: str
|
||||
desc: |
|
||||
Database name (required if using this hook). Or "all" to dump all
|
||||
databases on the host. Note that using this database hook implicitly
|
||||
enables both read_special and one_file_system (see above) to support
|
||||
dump and restore streaming.
|
||||
Database name (required if using this hook). Or
|
||||
"all" to dump all databases on the host. Note
|
||||
that using this database hook implicitly enables
|
||||
both read_special and one_file_system (see
|
||||
above) to support dump and restore streaming.
|
||||
example: users
|
||||
hostname:
|
||||
type: str
|
||||
desc: |
|
||||
Database hostname to connect to. Defaults to connecting via local
|
||||
Unix socket.
|
||||
Database hostname to connect to. Defaults to
|
||||
connecting via local Unix socket.
|
||||
example: database.example.org
|
||||
port:
|
||||
type: int
|
||||
@@ -474,39 +568,78 @@ map:
|
||||
username:
|
||||
type: str
|
||||
desc: |
|
||||
Username with which to connect to the database. Defaults to the
|
||||
username of the current user. You probably want to specify the
|
||||
"postgres" superuser here when the database name is "all".
|
||||
Username with which to connect to the database.
|
||||
Defaults to the username of the current user.
|
||||
You probably want to specify the "postgres"
|
||||
superuser here when the database name is "all".
|
||||
example: dbuser
|
||||
password:
|
||||
type: str
|
||||
desc: |
|
||||
Password with which to connect to the database. Omitting a password
|
||||
will only work if PostgreSQL is configured to trust the configured
|
||||
username without a password, or you create a ~/.pgpass file.
|
||||
Password with which to connect to the database.
|
||||
Omitting a password will only work if PostgreSQL
|
||||
is configured to trust the configured username
|
||||
without a password, or you create a ~/.pgpass
|
||||
file.
|
||||
example: trustsome1
|
||||
format:
|
||||
type: str
|
||||
enum: ['plain', 'custom', 'directory', 'tar']
|
||||
desc: |
|
||||
Database dump output format. One of "plain", "custom", "directory",
|
||||
or "tar". Defaults to "custom" (unlike raw pg_dump). See
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html for details.
|
||||
Note that format is ignored when the database name is "all".
|
||||
Database dump output format. One of "plain",
|
||||
"custom", "directory", or "tar". Defaults to
|
||||
"custom" (unlike raw pg_dump). See pg_dump
|
||||
documentation for details. Note that format is
|
||||
ignored when the database name is "all".
|
||||
example: directory
|
||||
ssl_mode:
|
||||
type: str
|
||||
enum: ['disable', 'allow', 'prefer',
|
||||
'require', 'verify-ca', 'verify-full']
|
||||
desc: |
|
||||
SSL mode to use to connect to the database
|
||||
server. One of "disable", "allow", "prefer",
|
||||
"require", "verify-ca" or "verify-full".
|
||||
Defaults to "disable".
|
||||
example: require
|
||||
ssl_cert:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a client certificate.
|
||||
example: "/root/.postgresql/postgresql.crt"
|
||||
ssl_key:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a private client key.
|
||||
example: "/root/.postgresql/postgresql.key"
|
||||
ssl_root_cert:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a root certificate containing a list of
|
||||
trusted certificate authorities.
|
||||
example: "/root/.postgresql/root.crt"
|
||||
ssl_crl:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a certificate revocation list.
|
||||
example: "/root/.postgresql/root.crl"
|
||||
options:
|
||||
type: str
|
||||
desc: |
|
||||
Additional pg_dump/pg_dumpall options to pass directly to the dump
|
||||
command, without performing any validation on them. See
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html for details.
|
||||
Additional pg_dump/pg_dumpall options to pass
|
||||
directly to the dump command, without performing
|
||||
any validation on them. See pg_dump
|
||||
documentation for details.
|
||||
example: --role=someone
|
||||
desc: |
|
||||
List of one or more PostgreSQL databases to dump before creating a backup,
|
||||
run once per configuration file. The database dumps are added to your source
|
||||
directories at runtime, backed up, and then removed afterwards. Requires
|
||||
List of one or more PostgreSQL databases to dump before
|
||||
creating a backup, run once per configuration file. The
|
||||
database dumps are added to your source directories at
|
||||
runtime, backed up, and removed afterwards. Requires
|
||||
pg_dump/pg_dumpall/pg_restore commands. See
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html for details.
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html and
|
||||
https://www.postgresql.org/docs/current/libpq-ssl.html for
|
||||
details.
|
||||
mysql_databases:
|
||||
seq:
|
||||
- map:
|
||||
@@ -514,16 +647,17 @@ map:
|
||||
required: true
|
||||
type: str
|
||||
desc: |
|
||||
Database name (required if using this hook). Or "all" to dump all
|
||||
databases on the host. Note that using this database hook implicitly
|
||||
enables both read_special and one_file_system (see above) to support
|
||||
dump and restore streaming.
|
||||
Database name (required if using this hook). Or
|
||||
"all" to dump all databases on the host. Note
|
||||
that using this database hook implicitly enables
|
||||
both read_special and one_file_system (see
|
||||
above) to support dump and restore streaming.
|
||||
example: users
|
||||
hostname:
|
||||
type: str
|
||||
desc: |
|
||||
Database hostname to connect to. Defaults to connecting via local
|
||||
Unix socket.
|
||||
Database hostname to connect to. Defaults to
|
||||
connecting via local Unix socket.
|
||||
example: database.example.org
|
||||
port:
|
||||
type: int
|
||||
@@ -532,87 +666,72 @@ map:
|
||||
username:
|
||||
type: str
|
||||
desc: |
|
||||
Username with which to connect to the database. Defaults to the
|
||||
username of the current user.
|
||||
Username with which to connect to the database.
|
||||
Defaults to the username of the current user.
|
||||
example: dbuser
|
||||
password:
|
||||
type: str
|
||||
desc: |
|
||||
Password with which to connect to the database. Omitting a password
|
||||
will only work if MySQL is configured to trust the configured
|
||||
username without a password.
|
||||
Password with which to connect to the database.
|
||||
Omitting a password will only work if MySQL is
|
||||
configured to trust the configured username
|
||||
without a password.
|
||||
example: trustsome1
|
||||
options:
|
||||
type: str
|
||||
desc: |
|
||||
Additional mysqldump options to pass directly to the dump command,
|
||||
without performing any validation on them. See
|
||||
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html or
|
||||
https://mariadb.com/kb/en/library/mysqldump/ for details.
|
||||
Additional mysqldump options to pass directly to
|
||||
the dump command, without performing any
|
||||
validation on them. See mysqldump documentation
|
||||
for details.
|
||||
example: --skip-comments
|
||||
desc: |
|
||||
List of one or more MySQL/MariaDB databases to dump before creating a backup,
|
||||
run once per configuration file. The database dumps are added to your source
|
||||
directories at runtime, backed up, and then removed afterwards. Requires
|
||||
List of one or more MySQL/MariaDB databases to dump before
|
||||
creating a backup, run once per configuration file. The
|
||||
database dumps are added to your source directories at
|
||||
runtime, backed up, and removed afterwards. Requires
|
||||
mysqldump/mysql commands (from either MySQL or MariaDB). See
|
||||
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html or
|
||||
https://mariadb.com/kb/en/library/mysqldump/ for details.
|
||||
healthchecks:
|
||||
type: str
|
||||
desc: |
|
||||
Healthchecks ping URL or UUID to notify when a backup begins, ends, or errors.
|
||||
Create an account at https://healthchecks.io if you'd like to use this service.
|
||||
See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#healthchecks-hook
|
||||
for details.
|
||||
Healthchecks ping URL or UUID to notify when a backup
|
||||
begins, ends, or errors. Create an account at
|
||||
https://healthchecks.io if you'd like to use this service.
|
||||
See borgmatic monitoring documentation for details.
|
||||
example:
|
||||
https://hc-ping.com/your-uuid-here
|
||||
cronitor:
|
||||
type: str
|
||||
desc: |
|
||||
Cronitor ping URL to notify when a backup begins, ends, or errors. Create an
|
||||
account at https://cronitor.io if you'd like to use this service. See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronitor-hook
|
||||
for details.
|
||||
Cronitor ping URL to notify when a backup begins, ends, or
|
||||
errors. Create an account at https://cronitor.io if you'd
|
||||
like to use this service. See borgmatic monitoring
|
||||
documentation for details.
|
||||
example:
|
||||
https://cronitor.link/d3x0c1
|
||||
pagerduty:
|
||||
type: str
|
||||
desc: |
|
||||
PagerDuty integration key used to notify PagerDuty when a backup errors. Create
|
||||
an account at https://www.pagerduty.com/ if you'd like to use this service. See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook
|
||||
for details.
|
||||
PagerDuty integration key used to notify PagerDuty when a
|
||||
backup errors. Create an account at
|
||||
https://www.pagerduty.com/ if you'd like to use this
|
||||
service. See borgmatic monitoring documentation for details.
|
||||
example:
|
||||
a177cad45bd374409f78906a810a3074
|
||||
cronhub:
|
||||
type: str
|
||||
desc: |
|
||||
Cronhub ping URL to notify when a backup begins, ends, or errors. Create an
|
||||
account at https://cronhub.io if you'd like to use this service. See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronhub-hook for
|
||||
details.
|
||||
Cronhub ping URL to notify when a backup begins, ends, or
|
||||
errors. Create an account at https://cronhub.io if you'd
|
||||
like to use this service. See borgmatic monitoring
|
||||
documentation for details.
|
||||
example:
|
||||
https://cronhub.io/start/1f5e3410-254c-11e8-b61d-55875966d031
|
||||
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."
|
||||
https://cronhub.io/start/1f5e3410-254c-11e8-b61d-55875966d01
|
||||
umask:
|
||||
type: scalar
|
||||
desc: Umask used when executing hooks. Defaults to the umask that borgmatic is run with.
|
||||
desc: |
|
||||
Umask used when executing hooks. Defaults to the umask that
|
||||
borgmatic is run with.
|
||||
example: 0077
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -13,6 +13,15 @@ MONITOR_STATE_TO_CRONHUB = {
|
||||
}
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
ping_url, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
No initialization is necessary for this monitor.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
def ping_monitor(ping_url, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Ping the given Cronhub URL, modified with the monitor.State. Use the given configuration
|
||||
@@ -30,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
|
||||
|
||||
@@ -13,6 +13,15 @@ MONITOR_STATE_TO_CRONITOR = {
|
||||
}
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
ping_url, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
No initialization is necessary for this monitor.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
def ping_monitor(ping_url, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Ping the given Cronitor URL, modified with the monitor.State. Use the given configuration
|
||||
@@ -29,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
|
||||
|
||||
+8
-30
@@ -48,46 +48,24 @@ def create_named_pipe_for_dump(dump_path):
|
||||
os.mkfifo(dump_path, mode=0o600)
|
||||
|
||||
|
||||
def remove_database_dumps(dump_path, databases, database_type_name, log_prefix, dry_run):
|
||||
def remove_database_dumps(dump_path, database_type_name, log_prefix, dry_run):
|
||||
'''
|
||||
Remove the database dumps for the given databases in the dump directory path. The databases are
|
||||
supplied as a sequence of dicts, one dict describing each database as per the configuration
|
||||
schema. Use the name of the database type and the log prefix in any log entries. If this is a
|
||||
dry run, then don't actually remove anything.
|
||||
Remove all database dumps in the given dump directory path (including the directory itself). If
|
||||
this is a dry run, then don't actually remove anything.
|
||||
'''
|
||||
if not databases:
|
||||
logger.debug('{}: No {} databases configured'.format(log_prefix, database_type_name))
|
||||
return
|
||||
|
||||
dry_run_label = ' (dry run; not actually removing anything)' if dry_run else ''
|
||||
|
||||
logger.info(
|
||||
'{}: Removing {} database dumps{}'.format(log_prefix, database_type_name, dry_run_label)
|
||||
)
|
||||
|
||||
for database in databases:
|
||||
dump_filename = make_database_dump_filename(
|
||||
dump_path, database['name'], database.get('hostname')
|
||||
)
|
||||
expanded_path = os.path.expanduser(dump_path)
|
||||
|
||||
logger.debug(
|
||||
'{}: Removing {} database dump {} from {}{}'.format(
|
||||
log_prefix, database_type_name, database['name'], dump_filename, dry_run_label
|
||||
)
|
||||
)
|
||||
if dry_run:
|
||||
continue
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
if os.path.exists(dump_filename):
|
||||
if os.path.isdir(dump_filename):
|
||||
shutil.rmtree(dump_filename)
|
||||
else:
|
||||
os.remove(dump_filename)
|
||||
|
||||
dump_file_dir = os.path.dirname(dump_filename)
|
||||
|
||||
if os.path.exists(dump_file_dir) and len(os.listdir(dump_file_dir)) == 0:
|
||||
os.rmdir(dump_file_dir)
|
||||
if os.path.exists(expanded_path):
|
||||
shutil.rmtree(expanded_path)
|
||||
|
||||
|
||||
def convert_glob_patterns_to_borg_patterns(patterns):
|
||||
|
||||
@@ -65,20 +65,24 @@ def format_buffered_logs_for_payload():
|
||||
return payload
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
ping_url_or_uuid, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
Add a handler to the root logger that stores in memory the most recent logs emitted. That
|
||||
way, we can send them all to Healthchecks upon a finish or failure state.
|
||||
'''
|
||||
logging.getLogger().addHandler(
|
||||
Forgetful_buffering_handler(PAYLOAD_LIMIT_BYTES, monitoring_log_level)
|
||||
)
|
||||
|
||||
|
||||
def ping_monitor(ping_url_or_uuid, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Ping the given Healthchecks URL or UUID, modified with the monitor.State. Use the given
|
||||
configuration filename in any log entries, and log to Healthchecks with the giving log level.
|
||||
If this is a dry run, then don't actually ping anything.
|
||||
'''
|
||||
if state is monitor.State.START:
|
||||
# Add a handler to the root logger that stores in memory the most recent logs emitted. That
|
||||
# way, we can send them all to Healthchecks upon a finish or failure state.
|
||||
logging.getLogger().addHandler(
|
||||
Forgetful_buffering_handler(PAYLOAD_LIMIT_BYTES, monitoring_log_level)
|
||||
)
|
||||
payload = ''
|
||||
|
||||
ping_url = (
|
||||
ping_url_or_uuid
|
||||
if ping_url_or_uuid.startswith('http')
|
||||
@@ -97,7 +101,21 @@ def ping_monitor(ping_url_or_uuid, config_filename, state, monitoring_log_level,
|
||||
|
||||
if state in (monitor.State.FINISH, monitor.State.FAIL):
|
||||
payload = format_buffered_logs_for_payload()
|
||||
else:
|
||||
payload = ''
|
||||
|
||||
if not dry_run:
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
requests.post(ping_url, data=payload.encode('utf-8'))
|
||||
|
||||
|
||||
def destroy_monitor(ping_url_or_uuid, config_filename, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Remove the monitor handler that was added to the root logger. This prevents the handler from
|
||||
getting reused by other instances of this monitor.
|
||||
'''
|
||||
logger = logging.getLogger()
|
||||
|
||||
for handler in tuple(logger.handlers):
|
||||
if isinstance(handler, Forgetful_buffering_handler):
|
||||
logger.removeHandler(handler)
|
||||
|
||||
@@ -118,14 +118,11 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
||||
|
||||
def remove_database_dumps(databases, log_prefix, location_config, dry_run): # pragma: no cover
|
||||
'''
|
||||
Remove the database dumps for the given databases. The databases are supplied as a sequence of
|
||||
dicts, one dict describing each database as per the configuration schema. Use the log prefix in
|
||||
any log entries. Use the given location configuration dict to construct the destination path. If
|
||||
this is a dry run, then don't actually remove anything.
|
||||
Remove all database dump files for this hook regardless of the given databases. Use the log
|
||||
prefix in any log entries. Use the given location configuration dict to construct the
|
||||
destination path. If this is a dry run, then don't actually remove anything.
|
||||
'''
|
||||
dump.remove_database_dumps(
|
||||
make_dump_path(location_config), databases, 'MySQL', log_prefix, dry_run
|
||||
)
|
||||
dump.remove_database_dumps(make_dump_path(location_config), 'MySQL', log_prefix, dry_run)
|
||||
|
||||
|
||||
def make_database_dump_pattern(
|
||||
|
||||
@@ -12,6 +12,15 @@ logger = logging.getLogger(__name__)
|
||||
EVENTS_API_URL = 'https://events.pagerduty.com/v2/enqueue'
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
integration_key, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
No initialization is necessary for this monitor.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
def ping_monitor(integration_key, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
If this is an error state, create a PagerDuty event with the given integration key. Use the
|
||||
@@ -60,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
|
||||
|
||||
@@ -15,6 +15,25 @@ def make_dump_path(location_config): # pragma: no cover
|
||||
)
|
||||
|
||||
|
||||
def make_extra_environment(database):
|
||||
'''
|
||||
Make the extra_environment dict from the given database configuration.
|
||||
'''
|
||||
extra = dict()
|
||||
if 'password' in database:
|
||||
extra['PGPASSWORD'] = database['password']
|
||||
extra['PGSSLMODE'] = database.get('ssl_mode', 'disable')
|
||||
if 'ssl_cert' in database:
|
||||
extra['PGSSLCERT'] = database['ssl_cert']
|
||||
if 'ssl_key' in database:
|
||||
extra['PGSSLKEY'] = database['ssl_key']
|
||||
if 'ssl_root_cert' in database:
|
||||
extra['PGSSLROOTCERT'] = database['ssl_root_cert']
|
||||
if 'ssl_crl' in database:
|
||||
extra['PGSSLCRL'] = database['ssl_crl']
|
||||
return extra
|
||||
|
||||
|
||||
def dump_databases(databases, log_prefix, location_config, dry_run):
|
||||
'''
|
||||
Dump the given PostgreSQL databases to a named pipe. The databases are supplied as a sequence of
|
||||
@@ -56,7 +75,7 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
||||
# format in a particular, a named destination is required, and redirection doesn't work.
|
||||
+ (('>', dump_filename) if dump_format != 'directory' else ())
|
||||
)
|
||||
extra_environment = {'PGPASSWORD': database['password']} if 'password' in database else None
|
||||
extra_environment = make_extra_environment(database)
|
||||
|
||||
logger.debug(
|
||||
'{}: Dumping PostgreSQL database {} to {}{}'.format(
|
||||
@@ -82,14 +101,11 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
||||
|
||||
def remove_database_dumps(databases, log_prefix, location_config, dry_run): # pragma: no cover
|
||||
'''
|
||||
Remove the database dumps for the given databases. The databases are supplied as a sequence of
|
||||
dicts, one dict describing each database as per the configuration schema. Use the log prefix in
|
||||
any log entries. Use the given location configuration dict to construct the destination path. If
|
||||
this is a dry run, then don't actually remove anything.
|
||||
Remove all database dump files for this hook regardless of the given databases. Use the log
|
||||
prefix in any log entries. Use the given location configuration dict to construct the
|
||||
destination path. If this is a dry run, then don't actually remove anything.
|
||||
'''
|
||||
dump.remove_database_dumps(
|
||||
make_dump_path(location_config), databases, 'PostgreSQL', log_prefix, dry_run
|
||||
)
|
||||
dump.remove_database_dumps(make_dump_path(location_config), 'PostgreSQL', log_prefix, dry_run)
|
||||
|
||||
|
||||
def make_database_dump_pattern(
|
||||
@@ -144,7 +160,7 @@ def restore_database_dump(database_config, log_prefix, location_config, dry_run,
|
||||
+ (('--username', database['username']) if 'username' in database else ())
|
||||
+ (() if extract_process else (dump_filename,))
|
||||
)
|
||||
extra_environment = {'PGPASSWORD': database['password']} if 'password' in database else None
|
||||
extra_environment = make_extra_environment(database)
|
||||
|
||||
logger.debug(
|
||||
'{}: Restoring PostgreSQL database {}{}'.format(log_prefix, database['name'], dry_run_label)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 don’t 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+79
-34
@@ -181,7 +181,7 @@ pre {
|
||||
padding: .5em;
|
||||
margin: 1em -.5em 2em -.5em;
|
||||
overflow-x: auto;
|
||||
background-color: #eee;
|
||||
background-color: #fafafa;
|
||||
font-size: 0.75em; /* 12px /16 */
|
||||
}
|
||||
pre,
|
||||
@@ -194,7 +194,7 @@ code {
|
||||
-webkit-hyphens: manual;
|
||||
-moz-hyphens: manual;
|
||||
hyphens: manual;
|
||||
background-color: #efefef;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
pre + pre[class*="language-"] {
|
||||
margin-top: 1em;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
/*
|
||||
* Modified with an approximation of the One Light syntax highlighting theme.
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #ABB2BF;
|
||||
color: #494b53;
|
||||
background: none;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
@@ -26,13 +29,15 @@ pre[class*="language-"] {
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #383e49;
|
||||
color: #232324;
|
||||
background: #dbdbdc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #9aa2b1;
|
||||
color: #232324;
|
||||
background: #dbdbdc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@@ -50,7 +55,7 @@ pre[class*="language-"] {
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #282c34;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
@@ -64,16 +69,16 @@ pre[class*="language-"] {
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #5C6370;
|
||||
color: #505157;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #abb2bf;
|
||||
color: #526fff;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.tag {
|
||||
color: #e06c75;
|
||||
color: none;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
@@ -83,7 +88,7 @@ pre[class*="language-"] {
|
||||
.token.symbol,
|
||||
.token.attr-name,
|
||||
.token.deleted {
|
||||
color: #d19a66;
|
||||
color: #986801;
|
||||
}
|
||||
|
||||
.token.string,
|
||||
@@ -91,7 +96,7 @@ pre[class*="language-"] {
|
||||
.token.attr-value,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #98c379;
|
||||
color: #50a14f;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
@@ -99,22 +104,22 @@ pre[class*="language-"] {
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #56b6c2;
|
||||
color: #526fff;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.keyword {
|
||||
color: #e06c75;
|
||||
color: #e45649;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #61afef;
|
||||
color: #4078f2;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #c678dd;
|
||||
color: #e45649;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -167,6 +172,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 +216,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
eleventyNavigation:
|
||||
key: How-to guides
|
||||
permalink: false
|
||||
---
|
||||
@@ -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/)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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:
|
||||
- k8pDxu32@k8pDxu32.repo.borgbase.com:repo
|
||||
- 1234@usw-s001.rsync.net:backups.borg
|
||||
- /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:
|
||||
|
||||
2. Backup `/home` and `/etc` to `k8pDxu32@k8pDxu32.repo.borgbase.com:repo`
|
||||
1. Backup `/home` and `/etc` to `1234@usw-s001.rsync.net:backups.borg`
|
||||
3. 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.
|
||||
@@ -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,12 +133,12 @@ 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
|
||||
list values by using brackets. For instance:
|
||||
@@ -143,13 +147,21 @@ list values by using brackets. For instance:
|
||||
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/)
|
||||
|
||||
@@ -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,47 +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)
|
||||
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/).
|
||||
@@ -116,7 +141,7 @@ hooks:
|
||||
```
|
||||
|
||||
With this hook in place, borgmatic pings your Healthchecks project when a
|
||||
backup begins, ends, or errors. Specifically, before the <a
|
||||
backup begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Healthchecks know that it has started if any of
|
||||
the `prune`, `create`, or `check` actions are run.
|
||||
@@ -127,10 +152,10 @@ the payload data sent to Healthchecks. This means that borgmatic logs show up
|
||||
in the Healthchecks UI, although be aware that Healthchecks currently has a
|
||||
10-kilobyte limit for the logs in each ping.
|
||||
|
||||
If an error occurs during any action, borgmatic notifies Healthchecks after
|
||||
the `on_error` hooks run, also tacking on logs including the error itself. But
|
||||
the logs are only included for errors that occur when a `prune`, `create`, or
|
||||
`check` action is run.
|
||||
If an error occurs during any action or hook, borgmatic notifies Healthchecks
|
||||
after the `on_error` hooks run, also tacking on logs including the error
|
||||
itself. But the logs are only included for errors that occur when a `prune`,
|
||||
`create`, or `check` action is run.
|
||||
|
||||
You can customize the verbosity of the logs that are sent to Healthchecks with
|
||||
borgmatic's `--monitoring-verbosity` flag. The `--files` and `--stats` flags
|
||||
@@ -156,13 +181,13 @@ hooks:
|
||||
```
|
||||
|
||||
With this hook in place, borgmatic pings your Cronitor monitor when a backup
|
||||
begins, ends, or errors. Specifically, before the <a
|
||||
begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Cronitor know that it has started if any of the
|
||||
`prune`, `create`, or `check` actions are run. Then, if the actions complete
|
||||
successfully, borgmatic notifies Cronitor of the success after the
|
||||
`after_backup` hooks run. And if an error occurs during any action, borgmatic
|
||||
notifies Cronitor after the `on_error` hooks run.
|
||||
`after_backup` hooks run. And if an error occurs during any action or hook,
|
||||
borgmatic notifies Cronitor after the `on_error` hooks run.
|
||||
|
||||
You can configure Cronitor to notify you by a [variety of
|
||||
mechanisms](https://cronitor.io/docs/cron-job-notifications) when backups fail
|
||||
@@ -184,13 +209,13 @@ hooks:
|
||||
```
|
||||
|
||||
With this hook in place, borgmatic pings your Cronhub monitor when a backup
|
||||
begins, ends, or errors. Specifically, before the <a
|
||||
begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Cronhub know that it has started if any of the
|
||||
`prune`, `create`, or `check` actions are run. Then, if the actions complete
|
||||
successfully, borgmatic notifies Cronhub of the success after the
|
||||
`after_backup` hooks run. And if an error occurs during any action, borgmatic
|
||||
notifies Cronhub after the `on_error` hooks run.
|
||||
`after_backup` hooks run. And if an error occurs during any action or hook,
|
||||
borgmatic notifies Cronhub after the `on_error` hooks run.
|
||||
|
||||
Note that even though you configure borgmatic with the "start" variant of the
|
||||
ping URL, borgmatic substitutes the correct state into the URL when pinging
|
||||
@@ -227,7 +252,7 @@ hooks:
|
||||
|
||||
With this hook in place, borgmatic creates a PagerDuty event for your service
|
||||
whenever backups fail. Specifically, if an error occurs during a `create`,
|
||||
`prune`, or `check` action, borgmatic sends an event to PagerDuty after the
|
||||
`prune`, or `check` action, borgmatic sends an event to PagerDuty before the
|
||||
`on_error` hooks run. Note that borgmatic does not contact PagerDuty when a
|
||||
backup starts or ends without error.
|
||||
|
||||
@@ -250,6 +275,11 @@ suppressed so as not to interfere with the captured JSON. Also note that JSON
|
||||
output only shows up at the console, and not in syslog.
|
||||
|
||||
|
||||
## Related software
|
||||
|
||||
* [Borgmacator GNOME AppIndicator](https://github.com/N-Coder/borgmacator/)
|
||||
|
||||
|
||||
### Successful backups
|
||||
|
||||
`borgmatic list` includes support for a `--successful` flag that only lists
|
||||
@@ -283,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/)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,9 +12,10 @@ these instructions install and run borgmatic as root. If you don't need to
|
||||
backup such files, then you are welcome to install and run borgmatic as a
|
||||
non-root user.
|
||||
|
||||
First, [install
|
||||
First, manually [install
|
||||
Borg](https://borgbackup.readthedocs.io/en/stable/installation.html), at least
|
||||
version 1.1.
|
||||
version 1.1. borgmatic does not install Borg automatically so as to avoid
|
||||
conflicts with existing Borg installations.
|
||||
|
||||
Then, download and install borgmatic as a [user site
|
||||
installation](https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site)
|
||||
@@ -64,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
|
||||
@@ -95,8 +99,8 @@ services helps support borgmatic development and hosting. (These are referral
|
||||
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>rsync.net: Cloud Storage provider with full support for borg and any other SSH/SFTP tool</li>
|
||||
</ul>
|
||||
|
||||
## Configuration
|
||||
@@ -134,9 +138,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)
|
||||
@@ -149,6 +152,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
|
||||
@@ -268,6 +278,7 @@ issues when reading files to backup. If that happens to you, you may be
|
||||
interested in an [unofficial work-around for Full Disk
|
||||
Access](https://projects.torsion.org/witten/borgmatic/issues/293).
|
||||
|
||||
|
||||
## Colored output
|
||||
|
||||
Borgmatic produces colored terminal output by default. It is disabled when a
|
||||
@@ -276,6 +287,7 @@ non-interactive terminal is detected (like a cron job), or when you use the
|
||||
setting the environment variable `PY_COLORS=False`, or setting the `color`
|
||||
option to `false` in the `output` section of configuration.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "found character that cannot start any token" error
|
||||
@@ -303,14 +315,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/)
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
---
|
||||
title: borgmatic configuration reference
|
||||
title: Configuration reference
|
||||
eleventyNavigation:
|
||||
key: Configuration reference
|
||||
parent: Reference guides
|
||||
order: 0
|
||||
---
|
||||
## Configuration file
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
eleventyNavigation:
|
||||
key: Reference guides
|
||||
permalink: false
|
||||
---
|
||||
@@ -1,6 +1,6 @@
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
VERSION = '1.5.5'
|
||||
VERSION = '1.5.10'
|
||||
|
||||
|
||||
setup(
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
MAXIMUM_LINE_LENGTH = 80
|
||||
|
||||
|
||||
def test_schema_line_length_stays_under_limit():
|
||||
schema_file = open('borgmatic/config/schema.yaml')
|
||||
|
||||
for line in schema_file.readlines():
|
||||
assert len(line.rstrip('\n')) <= MAXIMUM_LINE_LENGTH
|
||||
@@ -0,0 +1,24 @@
|
||||
import logging
|
||||
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks import healthchecks as module
|
||||
|
||||
|
||||
def test_destroy_monitor_removes_healthchecks_handler():
|
||||
logger = logging.getLogger()
|
||||
original_handlers = list(logger.handlers)
|
||||
logger.addHandler(module.Forgetful_buffering_handler(byte_capacity=100, log_level=1))
|
||||
|
||||
module.destroy_monitor(flexmock(), flexmock(), flexmock(), flexmock())
|
||||
|
||||
assert logger.handlers == original_handlers
|
||||
|
||||
|
||||
def test_destroy_monitor_without_healthchecks_handler_does_not_raise():
|
||||
logger = logging.getLogger()
|
||||
original_handlers = list(logger.handlers)
|
||||
|
||||
module.destroy_monitor(flexmock(), flexmock(), flexmock(), flexmock())
|
||||
|
||||
assert logger.handlers == original_handlers
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -63,20 +63,25 @@ def test_expand_home_directories_considers_none_as_no_directories():
|
||||
@pytest.mark.parametrize(
|
||||
'directories,expected_directories',
|
||||
(
|
||||
(('/', '/root'), ('/',)),
|
||||
(('/', '/root/'), ('/',)),
|
||||
(('/root', '/'), ('/',)),
|
||||
(('/root', '/root/foo'), ('/root',)),
|
||||
(('/root/', '/root/foo'), ('/root/',)),
|
||||
(('/root', '/root/foo/'), ('/root',)),
|
||||
(('/root/foo', '/root'), ('/root',)),
|
||||
(('/root', '/etc', '/root/foo/bar'), ('/etc', '/root')),
|
||||
(('/root', '/root/foo', '/root/foo/bar'), ('/root',)),
|
||||
(('/dup', '/dup'), ('/dup',)),
|
||||
(('/foo', '/bar'), ('/bar', '/foo')),
|
||||
({'/': 1, '/root': 1}, ('/',)),
|
||||
({'/': 1, '/root/': 1}, ('/',)),
|
||||
({'/': 1, '/root': 2}, ('/', '/root')),
|
||||
({'/root': 1, '/': 1}, ('/',)),
|
||||
({'/root': 1, '/root/foo': 1}, ('/root',)),
|
||||
({'/root/': 1, '/root/foo': 1}, ('/root/',)),
|
||||
({'/root': 1, '/root/foo/': 1}, ('/root',)),
|
||||
({'/root': 1, '/root/foo': 2}, ('/root', '/root/foo')),
|
||||
({'/root/foo': 1, '/root': 1}, ('/root',)),
|
||||
({'/root': 1, '/etc': 1, '/root/foo/bar': 1}, ('/etc', '/root')),
|
||||
({'/root': 1, '/root/foo': 1, '/root/foo/bar': 1}, ('/root',)),
|
||||
({'/dup': 1, '/dup': 1}, ('/dup',)),
|
||||
({'/foo': 1, '/bar': 1}, ('/bar', '/foo')),
|
||||
({'/foo': 1, '/bar': 2}, ('/bar', '/foo')),
|
||||
),
|
||||
)
|
||||
def test_deduplicate_directories_removes_child_paths(directories, expected_directories):
|
||||
def test_deduplicate_directories_removes_child_paths_on_the_same_filesystem(
|
||||
directories, expected_directories
|
||||
):
|
||||
assert module.deduplicate_directories(directories) == expected_directories
|
||||
|
||||
|
||||
@@ -235,6 +240,7 @@ ARCHIVE_WITH_PATHS = ('repo::{}'.format(DEFAULT_ARCHIVE_NAME), 'foo', 'bar')
|
||||
def test_create_archive_calls_borg_with_parameters():
|
||||
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)
|
||||
@@ -263,6 +269,7 @@ def test_create_archive_with_patterns_calls_borg_with_patterns():
|
||||
pattern_flags = ('--patterns-from', 'patterns')
|
||||
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(
|
||||
@@ -293,6 +300,7 @@ def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
|
||||
exclude_flags = ('--exclude-from', 'excludes')
|
||||
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(('exclude',))
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None).and_return(
|
||||
@@ -322,6 +330,7 @@ def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
|
||||
def test_create_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
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)
|
||||
@@ -351,6 +360,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
|
||||
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)
|
||||
@@ -381,6 +391,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
|
||||
def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
|
||||
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)
|
||||
@@ -409,6 +420,7 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
|
||||
def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
|
||||
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)
|
||||
@@ -438,6 +450,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
|
||||
def test_create_archive_with_dry_run_calls_borg_with_dry_run_parameter():
|
||||
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)
|
||||
@@ -468,6 +481,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_paramete
|
||||
# https://borgbackup.readthedocs.io/en/stable/usage/create.html#description
|
||||
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)
|
||||
@@ -498,6 +512,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_paramete
|
||||
def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_interval_parameters():
|
||||
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)
|
||||
@@ -525,6 +540,7 @@ def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_inte
|
||||
def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_parameters():
|
||||
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)
|
||||
@@ -552,6 +568,7 @@ def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_param
|
||||
def test_create_archive_with_compression_calls_borg_with_compression_parameters():
|
||||
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)
|
||||
@@ -579,6 +596,7 @@ def test_create_archive_with_compression_calls_borg_with_compression_parameters(
|
||||
def test_create_archive_with_remote_rate_limit_calls_borg_with_remote_ratelimit_parameters():
|
||||
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)
|
||||
@@ -606,6 +624,7 @@ def test_create_archive_with_remote_rate_limit_calls_borg_with_remote_ratelimit_
|
||||
def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_parameter():
|
||||
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)
|
||||
@@ -634,6 +653,7 @@ def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_par
|
||||
def test_create_archive_with_numeric_owner_calls_borg_with_numeric_owner_parameter():
|
||||
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)
|
||||
@@ -662,6 +682,7 @@ def test_create_archive_with_numeric_owner_calls_borg_with_numeric_owner_paramet
|
||||
def test_create_archive_with_read_special_calls_borg_with_read_special_parameter():
|
||||
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)
|
||||
@@ -691,6 +712,7 @@ def test_create_archive_with_read_special_calls_borg_with_read_special_parameter
|
||||
def test_create_archive_with_option_true_calls_borg_without_corresponding_parameter(option_name):
|
||||
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)
|
||||
@@ -720,6 +742,7 @@ def test_create_archive_with_option_true_calls_borg_without_corresponding_parame
|
||||
def test_create_archive_with_option_false_calls_borg_with_corresponding_parameter(option_name):
|
||||
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)
|
||||
@@ -748,6 +771,7 @@ def test_create_archive_with_option_false_calls_borg_with_corresponding_paramete
|
||||
def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters():
|
||||
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)
|
||||
@@ -776,6 +800,7 @@ def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters(
|
||||
def test_create_archive_with_local_path_calls_borg_via_local_path():
|
||||
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)
|
||||
@@ -804,6 +829,7 @@ def test_create_archive_with_local_path_calls_borg_via_local_path():
|
||||
def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters():
|
||||
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)
|
||||
@@ -832,6 +858,7 @@ def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters(
|
||||
def test_create_archive_with_umask_calls_borg_with_umask_parameters():
|
||||
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)
|
||||
@@ -859,6 +886,7 @@ def test_create_archive_with_umask_calls_borg_with_umask_parameters():
|
||||
def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
|
||||
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)
|
||||
@@ -886,6 +914,7 @@ def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
|
||||
def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_output_log_level():
|
||||
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)
|
||||
@@ -914,6 +943,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_o
|
||||
def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_and_info_output_log_level():
|
||||
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)
|
||||
@@ -943,6 +973,7 @@ def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_
|
||||
def test_create_archive_with_files_calls_borg_with_list_parameter_and_warning_output_log_level():
|
||||
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)
|
||||
@@ -971,6 +1002,7 @@ def test_create_archive_with_files_calls_borg_with_list_parameter_and_warning_ou
|
||||
def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_and_info_output_log_level():
|
||||
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)
|
||||
@@ -1000,6 +1032,7 @@ def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_a
|
||||
def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_parameter_and_no_list():
|
||||
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)
|
||||
@@ -1029,6 +1062,7 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
|
||||
def test_create_archive_with_progress_calls_borg_with_progress_parameter():
|
||||
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)
|
||||
@@ -1058,6 +1092,7 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
|
||||
processes = flexmock()
|
||||
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)
|
||||
@@ -1089,6 +1124,7 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
|
||||
def test_create_archive_with_json_calls_borg_with_json_parameter():
|
||||
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)
|
||||
@@ -1119,6 +1155,7 @@ def test_create_archive_with_json_calls_borg_with_json_parameter():
|
||||
def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter():
|
||||
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)
|
||||
@@ -1150,6 +1187,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter()
|
||||
def test_create_archive_with_source_directories_glob_expands():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
|
||||
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)
|
||||
@@ -1178,6 +1216,7 @@ def test_create_archive_with_source_directories_glob_expands():
|
||||
def test_create_archive_with_non_matching_source_directories_glob_passes_through():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo*',))
|
||||
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)
|
||||
@@ -1206,6 +1245,7 @@ def test_create_archive_with_non_matching_source_directories_glob_passes_through
|
||||
def test_create_archive_with_glob_calls_borg_with_expanded_directories():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
|
||||
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)
|
||||
@@ -1233,6 +1273,7 @@ def test_create_archive_with_glob_calls_borg_with_expanded_directories():
|
||||
def test_create_archive_with_archive_name_format_calls_borg_with_archive_name():
|
||||
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)
|
||||
@@ -1260,6 +1301,7 @@ def test_create_archive_with_archive_name_format_calls_borg_with_archive_name():
|
||||
def test_create_archive_with_archive_name_format_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)
|
||||
@@ -1287,6 +1329,7 @@ def test_create_archive_with_archive_name_format_accepts_borg_placeholders():
|
||||
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'))
|
||||
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)
|
||||
@@ -1315,6 +1358,7 @@ def test_create_archive_with_stream_processes_calls_borg_with_processes():
|
||||
processes = flexmock()
|
||||
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)
|
||||
|
||||
@@ -60,3 +60,25 @@ def test_initialize_with_relocated_repo_access_should_override_default():
|
||||
assert os.environ.get('BORG_RELOCATED_REPO_ACCESS_IS_OK') == 'yes'
|
||||
finally:
|
||||
os.environ = orig_environ
|
||||
|
||||
|
||||
def test_initialize_prefers_configuration_option_over_borg_environment_variable():
|
||||
orig_environ = os.environ
|
||||
|
||||
try:
|
||||
os.environ = {'BORG_SSH': 'mosh'}
|
||||
module.initialize({'ssh_command': 'ssh -C'})
|
||||
assert os.environ.get('BORG_RSH') == 'ssh -C'
|
||||
finally:
|
||||
os.environ = orig_environ
|
||||
|
||||
|
||||
def test_initialize_passes_through_existing_borg_environment_variable():
|
||||
orig_environ = os.environ
|
||||
|
||||
try:
|
||||
os.environ = {'BORG_PASSPHRASE': 'pass'}
|
||||
module.initialize({'ssh_command': 'ssh -C'})
|
||||
assert os.environ.get('BORG_PASSPHRASE') == 'pass'
|
||||
finally:
|
||||
os.environ = orig_environ
|
||||
|
||||
@@ -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={},
|
||||
)
|
||||
@@ -220,6 +220,21 @@ def test_extract_archive_calls_borg_with_destination_path():
|
||||
)
|
||||
|
||||
|
||||
def test_extract_archive_calls_borg_with_strip_components():
|
||||
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||
insert_execute_command_mock(('borg', 'extract', '--strip-components', '5', 'repo::archive'))
|
||||
|
||||
module.extract_archive(
|
||||
dry_run=False,
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
paths=None,
|
||||
location_config={},
|
||||
storage_config={},
|
||||
strip_components=5,
|
||||
)
|
||||
|
||||
|
||||
def test_extract_archive_calls_borg_with_progress_parameter():
|
||||
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -47,69 +47,28 @@ def test_create_named_pipe_for_dump_does_not_raise():
|
||||
module.create_named_pipe_for_dump('/path/to/pipe')
|
||||
|
||||
|
||||
def test_remove_database_dumps_removes_dump_for_each_database():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module).should_receive('make_database_dump_filename').with_args(
|
||||
'databases', 'foo', None
|
||||
).and_return('databases/localhost/foo')
|
||||
flexmock(module).should_receive('make_database_dump_filename').with_args(
|
||||
'databases', 'bar', None
|
||||
).and_return('databases/localhost/bar')
|
||||
|
||||
def test_remove_database_dumps_removes_dump_path():
|
||||
flexmock(module.os.path).should_receive('expanduser').and_return('databases/localhost')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(False)
|
||||
flexmock(module.os).should_receive('remove').with_args('databases/localhost/foo').once()
|
||||
flexmock(module.os).should_receive('remove').with_args('databases/localhost/bar').once()
|
||||
flexmock(module.os).should_receive('listdir').with_args('databases/localhost').and_return(
|
||||
['bar']
|
||||
).and_return([])
|
||||
flexmock(module.shutil).should_receive('rmtree').with_args('databases/localhost').once()
|
||||
|
||||
flexmock(module.os).should_receive('rmdir').with_args('databases/localhost').once()
|
||||
|
||||
module.remove_database_dumps('databases', databases, 'SuperDB', 'test.yaml', dry_run=False)
|
||||
|
||||
|
||||
def test_remove_database_dumps_removes_dump_in_directory_format():
|
||||
databases = [{'name': 'foo'}]
|
||||
flexmock(module).should_receive('make_database_dump_filename').with_args(
|
||||
'databases', 'foo', None
|
||||
).and_return('databases/localhost/foo')
|
||||
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('remove').never()
|
||||
flexmock(module.shutil).should_receive('rmtree').with_args('databases/localhost/foo').once()
|
||||
flexmock(module.os).should_receive('listdir').with_args('databases/localhost').and_return([])
|
||||
flexmock(module.os).should_receive('rmdir').with_args('databases/localhost').once()
|
||||
|
||||
module.remove_database_dumps('databases', databases, 'SuperDB', 'test.yaml', dry_run=False)
|
||||
module.remove_database_dumps('databases', 'SuperDB', 'test.yaml', dry_run=False)
|
||||
|
||||
|
||||
def test_remove_database_dumps_with_dry_run_skips_removal():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module.os).should_receive('rmdir').never()
|
||||
flexmock(module.os).should_receive('remove').never()
|
||||
|
||||
module.remove_database_dumps('databases', databases, 'SuperDB', 'test.yaml', dry_run=True)
|
||||
|
||||
|
||||
def test_remove_database_dumps_without_dump_present_skips_removal():
|
||||
databases = [{'name': 'foo'}]
|
||||
flexmock(module).should_receive('make_database_dump_filename').with_args(
|
||||
'databases', 'foo', None
|
||||
).and_return('databases/localhost/foo')
|
||||
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.os.path).should_receive('isdir').never()
|
||||
flexmock(module.os).should_receive('remove').never()
|
||||
flexmock(module.os.path).should_receive('expanduser').and_return('databases/localhost')
|
||||
flexmock(module.os.path).should_receive('exists').never()
|
||||
flexmock(module.shutil).should_receive('rmtree').never()
|
||||
flexmock(module.os).should_receive('rmdir').never()
|
||||
|
||||
module.remove_database_dumps('databases', databases, 'SuperDB', 'test.yaml', dry_run=False)
|
||||
module.remove_database_dumps('databases', 'SuperDB', 'test.yaml', dry_run=True)
|
||||
|
||||
|
||||
def test_remove_database_dumps_without_databases_does_not_raise():
|
||||
module.remove_database_dumps('databases', [], 'SuperDB', 'test.yaml', dry_run=False)
|
||||
def test_remove_database_dumps_without_dump_path_present_skips_removal():
|
||||
flexmock(module.os.path).should_receive('expanduser').and_return('databases/localhost')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.shutil).should_receive('rmtree').never()
|
||||
|
||||
module.remove_database_dumps('databases', 'SuperDB', 'test.yaml', dry_run=False)
|
||||
|
||||
|
||||
def test_convert_glob_patterns_to_borg_patterns_removes_leading_slash():
|
||||
|
||||
@@ -14,6 +14,7 @@ def test_dump_databases_runs_pg_dump_for_each_database():
|
||||
'databases/localhost/foo'
|
||||
).and_return('databases/localhost/bar')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -29,7 +30,7 @@ def test_dump_databases_runs_pg_dump_for_each_database():
|
||||
'databases/localhost/{}'.format(name),
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -43,6 +44,7 @@ def test_dump_databases_with_dry_run_skips_pg_dump():
|
||||
'databases/localhost/foo'
|
||||
).and_return('databases/localhost/bar')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
assert module.dump_databases(databases, 'test.yaml', {}, dry_run=True) == []
|
||||
@@ -56,6 +58,7 @@ def test_dump_databases_runs_pg_dump_with_hostname_and_port():
|
||||
'databases/database.example.org/foo'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -74,7 +77,7 @@ def test_dump_databases_runs_pg_dump_with_hostname_and_port():
|
||||
'databases/database.example.org/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -89,6 +92,9 @@ def test_dump_databases_runs_pg_dump_with_username_and_password():
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -105,13 +111,36 @@ def test_dump_databases_runs_pg_dump_with_username_and_password():
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGPASSWORD': 'trustsome1'},
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == [process]
|
||||
|
||||
|
||||
def test_make_extra_environment_maps_options_to_environment():
|
||||
database = {
|
||||
'name': 'foo',
|
||||
'password': 'pass',
|
||||
'ssl_mode': 'require',
|
||||
'ssl_cert': 'cert.crt',
|
||||
'ssl_key': 'key.key',
|
||||
'ssl_root_cert': 'root.crt',
|
||||
'ssl_crl': 'crl.crl',
|
||||
}
|
||||
expected = {
|
||||
'PGPASSWORD': 'pass',
|
||||
'PGSSLMODE': 'require',
|
||||
'PGSSLCERT': 'cert.crt',
|
||||
'PGSSLKEY': 'key.key',
|
||||
'PGSSLROOTCERT': 'root.crt',
|
||||
'PGSSLCRL': 'crl.crl',
|
||||
}
|
||||
|
||||
extra_env = module.make_extra_environment(database)
|
||||
assert extra_env == expected
|
||||
|
||||
|
||||
def test_dump_databases_runs_pg_dump_with_directory_format():
|
||||
databases = [{'name': 'foo', 'format': 'directory'}]
|
||||
process = flexmock()
|
||||
@@ -121,6 +150,7 @@ def test_dump_databases_runs_pg_dump_with_directory_format():
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_parent_directory_for_dump')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -135,7 +165,7 @@ def test_dump_databases_runs_pg_dump_with_directory_format():
|
||||
'foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -150,6 +180,7 @@ def test_dump_databases_runs_pg_dump_with_options():
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
@@ -165,7 +196,7 @@ def test_dump_databases_runs_pg_dump_with_options():
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -180,11 +211,12 @@ def test_dump_databases_runs_pg_dumpall_for_all_databases():
|
||||
'databases/localhost/all'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('pg_dumpall', '--no-password', '--clean', '--if-exists', '>', 'databases/localhost/all'),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -197,6 +229,7 @@ def test_restore_database_dump_runs_pg_restore():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
@@ -210,12 +243,12 @@ def test_restore_database_dump_runs_pg_restore():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('psql', '--no-password', '--quiet', '--dbname', 'foo', '--command', 'ANALYZE'),
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
@@ -228,6 +261,7 @@ def test_restore_database_dump_errors_on_multiple_database_config():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
@@ -243,6 +277,7 @@ def test_restore_database_dump_runs_pg_restore_with_hostname_and_port():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
@@ -260,7 +295,7 @@ def test_restore_database_dump_runs_pg_restore_with_hostname_and_port():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -277,7 +312,7 @@ def test_restore_database_dump_runs_pg_restore_with_hostname_and_port():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
@@ -291,6 +326,9 @@ def test_restore_database_dump_runs_pg_restore_with_username_and_password():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
@@ -306,7 +344,7 @@ def test_restore_database_dump_runs_pg_restore_with_username_and_password():
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGPASSWORD': 'trustsome1'},
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -321,7 +359,7 @@ def test_restore_database_dump_runs_pg_restore_with_username_and_password():
|
||||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGPASSWORD': 'trustsome1'},
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
@@ -335,16 +373,18 @@ def test_restore_database_dump_runs_psql_for_all_database_dump():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('psql', '--no-password'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('psql', '--no-password', '--quiet', '--command', 'ANALYZE'), extra_environment=None
|
||||
('psql', '--no-password', '--quiet', '--command', 'ANALYZE'),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
@@ -357,6 +397,7 @@ def test_restore_database_dump_with_dry_run_skips_restore():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_database_dump(
|
||||
@@ -369,6 +410,7 @@ def test_restore_database_dump_without_extract_process_restores_from_disk():
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename').and_return('/dump/path')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
@@ -383,12 +425,12 @@ def test_restore_database_dump_without_extract_process_restores_from_disk():
|
||||
processes=[],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=None,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('psql', '--no-password', '--quiet', '--dbname', 'foo', '--command', 'ANALYZE'),
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
|
||||
Reference in New Issue
Block a user