Compare commits

...
49 Commits
Author SHA1 Message Date
Dan Helfman 5e7d14e419 Bump version for release. 2025-11-26 15:31:04 -08:00
Dan Helfman 8f2ae5e9a1 Fix a regression in the Btrfs hook in which subvolume snapshots didn't get cleaned up until the start of the next borgmatic run (#1186). 2025-11-26 13:59:23 -08:00
Dan Helfman 928bb3fb8c Fix an unreleased regression in the KeePassXC hook in which it raised when it was used without explicit "keepassxc:" configuration (#1181). 2025-11-25 22:48:05 -08:00
Dan Helfman 31623f9c91 Add borgmatic release artifacts to each release on the releases page. 2025-11-25 20:41:25 -08:00
Dan Helfman 47ce12e4b6 Explicitly note Borg v1 caching doesn't work with Btrfs (#1187).
Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/1187
2025-11-21 06:55:39 +00:00
Jackson e72ace5294 remove lingering ) 2025-11-21 05:17:44 +01:00
Jackson 72c3e524be docs: btrfs: note that btrfs hook doesn't cache even with runtime dir 2025-11-21 05:17:44 +01:00
Dan Helfman 399576e7c9 Add data hooks working directory fix to NEWS (#1127). 2025-11-19 10:42:11 -08:00
Dan Helfman a7356f2360 Fix for the database dumps hooks not respecting the "working_directory" option (#1127).
Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/1129
2025-11-19 18:40:50 +00:00
Dan Helfman 1f8c39fb17 Add fish shell completion fix to NEWS (#1184). 2025-11-18 22:29:19 -08:00
Dan Helfman 66e567f78c Hard-code default configuration paths in command-line flag help (#1184).
Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/1184
2025-11-19 06:25:56 +00:00
Tim Ruffing ea45a37db8 Omit "-c" when describing default value for -c 2025-11-18 09:55:54 +01:00
Dan Helfman a229fc7f92 Give the borgmatic logo a white circle background so it actually shows up properly in dark mode. 2025-11-17 22:20:27 -08:00
Tim Ruffing 573405bb88 Hard-code default config paths in help strings
This fixes a bug with fish completions. Before this commit,
`borgmatic --fish-completion` would generate a fish completion script
with an expanded XDG_CONFIG_HOME (if this variable is set).

This is problematic for package maintainers if the variable is set
during the creation of a package.

Moreover, it is problematic if the user has set this variable because
the fish completion scripts checks its own consistency with `borg
--fish-completion` during startup to detect version mismatches. If the
user has a packaged fish completion file with "~/.config/$HOME" in it,
this won't match the output of `borg --fish-completion` because the
latter contains the expansion of $XDG_CONFIG_HOME. As a result, the
script will incorrectly conclude that it is outdated.
2025-11-17 15:42:30 +01:00
Dan Helfman 94dc4c497a Note in the KeePassXC documentation which features are in which versions of borgmatic (#1181). 2025-11-14 21:27:07 -08:00
Dan Helfman 0e9193c46b Add an "ask_for_password" option to the KeePassXC credential hook for disabling KeePassXC's password prompt (#1181). 2025-11-14 21:20:21 -08:00
Dan Helfman 151becbaea Move shell autocompletion documentation out to reference docs. 2025-11-11 10:21:01 -08:00
bockulator ce7f0226be Mock get_working_directory() calls in data source hook tests 2025-11-11 15:36:51 +01:00
bockulator 51ef9c7708 Fix database dumps hooks not respecting working_directory 2025-11-11 14:22:34 +01:00
Dan Helfman 14a8055e71 In the SQLite database hook, run SQLite such that it exits upon encountering an error instead of, you know, not doing that. 2025-11-07 23:22:44 -08:00
Dan Helfman fcfc7ee726 Emojis everywhere! 2025-11-04 23:04:16 -08:00
Dan Helfman 859c338d06 Improve documentation search results for individual configuration options. 2025-11-04 22:34:15 -08:00
Dan Helfman d1f2d8a061 Add documentation on repositories, including SSH, Rclone, S3, and B2. 2025-11-03 13:56:15 -08:00
Dan Helfman bddc82951e Bump version for release. 2025-11-02 19:41:38 -08:00
Dan Helfman 3d15c51039 Fix the "--repository" flag not applying to command hooks (#1176). 2025-11-02 19:32:08 -08:00
Dan Helfman f9206c15e7 Fix documentation typos. 2025-11-02 09:36:08 -08:00
Dan Helfman 0da4b64838 Drop support for Python 3.9, which has been end-of-lifed. (Also hopefully fix the build.) 2025-11-01 23:16:13 -07:00
Dan Helfman 85f06a900a Revert "Attempt to fix broken build (rpds module not found error)."
This reverts commit bae8f983e0.
2025-11-01 21:48:59 -07:00
Dan Helfman bae8f983e0 Attempt to fix broken build (rpds module not found error). 2025-11-01 21:39:10 -07:00
Dan Helfman 89c0cd0af6 Add documentation on patterns and excludes. 2025-11-01 21:18:22 -07:00
Dan Helfman 292d9cb2c7 Add documentation on patterns and excludes. 2025-11-01 21:17:35 -07:00
Dan Helfman 181030c5d1 Add an "environment" option to the Sentry monitoring hook (#1172). 2025-10-30 15:39:49 -07:00
Dan Helfman 2600598359 Add environment option to Sentry monitoring hook (#1172).
Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/1172
Reviewed-by: Dan Helfman <witten@torsion.org>
2025-10-30 22:37:53 +00:00
Tom Janssen 9e649af2a3 fix typo 2025-10-29 10:13:45 +01:00
Tom Janssen 370bc7e7f0 fix schema length 2025-10-29 09:55:03 +01:00
Tom Janssen 7cca1358fb incorporate feedback 2025-10-29 09:50:55 +01:00
Dan Helfman 67406b60b3 Cleanup "other ways to install" section and boost "Docker" there so it shows up in search. 2025-10-28 23:01:51 -07:00
Dan Helfman 72fde728a4 Fix typo (#1173).
Reviewed-on: https://projects.torsion.org/borgmatic-collective/borgmatic/pulls/1173
2025-10-28 18:16:36 +00:00
StefanSchoof d4433eeec1 Fix typo 2025-10-28 16:43:40 +00:00
Tom Janssen d0b5244888 add environment option 2025-10-28 16:52:50 +01:00
Dan Helfman 1ed207c6b1 Document borgmatic's limitations around parallelism—both its own and Borg's (#957). 2025-10-27 15:58:12 -07:00
Dan Helfman 5ef257ebb6 Attempt to clarify recursion root patterns in the schema comments. 2025-10-27 14:34:20 -07:00
Dan Helfman eb2b0b35c1 Fix for an inconsistent log level for Borg's last output line before exiting (#1170). 2025-10-27 14:20:27 -07:00
Dan Helfman 0f7ebcb4b7 Fix for a regression in the ZFS, LVM, and Btrfs hooks in which partial excludes of snapshot paths were ignored (#1169). 2025-10-26 14:12:52 -07:00
Dan Helfman 51d2ce7963 Add a "rename" option to "extra_borg_options" to support passing arbitrary flags to "borg rename". 2025-10-24 23:02:46 -07:00
Dan Helfman 0c4222037d Fix for the "list", "info", and "delete" options in "extra_borg_options" being ignored when "--archive" is omitted with Borg 1.x (#1168). 2025-10-24 22:57:23 -07:00
Dan Helfman 25e8aad517 Add systemd multiple service directories fix to NEWS (#1165). 2025-10-24 09:36:52 -07:00
Dan Helfman 11ffd76cc6 Support multiple configured systemd service directories (RuntimeDirectory, StateDirectory) (#1165). 2025-10-24 16:31:59 +00:00
Simon Pilkington 68864395b5 Support multiple configured systemd service directories (RuntimeDirectory, StateDirectory) 2025-10-24 08:20:59 +02:00
136 changed files with 1972 additions and 960 deletions
-2
View File
@@ -29,5 +29,3 @@ jobs:
PASSWORD: "${{ secrets.REGISTRY_PASSWORD }}"
- run: podman build --tag "$IMAGE_NAME" --file docs/Dockerfile --storage-opt "overlay.mount_program=/usr/bin/fuse-overlayfs" .
- run: podman push "$IMAGE_NAME"
- run: scripts/export-docs-from-image
- run: curl --user "${{ secrets.REGISTRY_USERNAME }}:${{ secrets.REGISTRY_PASSWORD }}" --upload-file borgmatic-docs.tar.gz https://projects.torsion.org/api/packages/borgmatic-collective/generic/borgmatic-docs/$(head --lines=1 NEWS)/borgmatic-docs.tar.gz
+36 -1
View File
@@ -1,5 +1,40 @@
2.0.12
* #1127: Fix for the database hooks not respecting the "working_directory" option.
* #1181: Add an "ask_for_password" option to the KeePassXC credential hook for disabling
KeePassXC's password prompt, e.g. if you're only using a key file to decrypt your database.
* #1184: Fix the fish shell completion's detection of version mismatches.
* #1186: Fix a regression in the Btrfs hook in which subvolume snapshots didn't get cleaned up
until the start of the next borgmatic run.
* In the SQLite database hook, run SQLite such that it exits upon encountering an error instead of,
you know, not doing that.
* Add documentation on repositories, including SSH, Rclone, S3, and B2:
https://torsion.org/borgmatic/reference/configuration/repositories/
* Improve documentation search results for individual configuration options.
* Add borgmatic release artifacts (wheel and tarball) to each release on the releases page:
https://projects.torsion.org/borgmatic-collective/borgmatic/releases
* Move the tarball of borgmatic's HTML documentation from the packages page to the releases page.
2.0.11
* #957: Document borgmatic's limitations around parallelism—both its own and Borg's. See the
documentation for more information:
https://torsion.org/borgmatic/how-to/make-per-application-backups/#limitations
* #1165: Fix for when the systemd service directories (RuntimeDirectory and StateDirectory) each
contain multiple paths.
* #1168: Fix for the "list", "info", and "delete" options in "extra_borg_options" being ignored
when "--archive" is omitted with Borg 1.x.
* #1169: Fix for a regression in the ZFS, LVM, and Btrfs hooks in which partial excludes of
snapshot paths were ignored.
* #1170: Fix for an inconsistent log level for Borg's last output line before exiting.
* #1172: Add an "environment" option to the Sentry monitoring hook.
* #1176: Fix the "--repository" flag not applying to command hooks.
* Add a "rename" option to "extra_borg_options" to support passing arbitrary flags to "borg
rename".
* Add documentation on patterns and excludes:
https://torsion.org/borgmatic/reference/configuration/patterns-and-excludes/
* Drop support for Python 3.9, which has been end-of-lifed.
2.0.10
* #427: Expand the "borg_extra_options" option to support passing arbitrary Borg flags to every
* #427: Expand the "extra_borg_options" option to support passing arbitrary Borg flags to every
Borg sub-command that borgmatic uses. As part of this, deprecate the "init" option under
"borg_extra_options" in favor of "repo_create".
* #942: Factor reference material out of the documentation how-to guides. This means there's now a
+19 -24
View File
@@ -2,7 +2,6 @@ import logging
import borgmatic.borg.borg
import borgmatic.borg.repo_list
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -19,26 +18,22 @@ def run_borg(
'''
Run the "borg" action for the given repository.
'''
if borg_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
borg_arguments.repository,
):
logger.info('Running arbitrary Borg command')
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
borg_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
borgmatic.borg.borg.run_arbitrary_borg(
repository['path'],
config,
local_borg_version,
options=borg_arguments.options,
archive=archive_name,
local_path=local_path,
remote_path=remote_path,
)
logger.info('Running arbitrary Borg command')
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
borg_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
borgmatic.borg.borg.run_arbitrary_borg(
repository['path'],
config,
local_borg_version,
options=borg_arguments.options,
archive=archive_name,
local_path=local_path,
remote_path=remote_path,
)
+9 -14
View File
@@ -1,7 +1,6 @@
import logging
import borgmatic.borg.break_lock
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -18,16 +17,12 @@ def run_break_lock(
'''
Run the "break-lock" action for the given repository.
'''
if break_lock_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
break_lock_arguments.repository,
):
logger.info('Breaking repository and cache locks')
borgmatic.borg.break_lock.break_lock(
repository['path'],
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
logger.info('Breaking repository and cache locks')
borgmatic.borg.break_lock.break_lock(
repository['path'],
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
+10 -18
View File
@@ -1,7 +1,6 @@
import logging
import borgmatic.borg.change_passphrase
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -18,20 +17,13 @@ def run_change_passphrase(
'''
Run the "key change-passphrase" action for the given repository.
'''
if (
change_passphrase_arguments.repository is None
or borgmatic.config.validate.repositories_match(
repository,
change_passphrase_arguments.repository,
)
):
logger.info('Changing repository passphrase')
borgmatic.borg.change_passphrase.change_passphrase(
repository['path'],
config,
local_borg_version,
change_passphrase_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
logger.info('Changing repository passphrase')
borgmatic.borg.change_passphrase.change_passphrase(
repository['path'],
config,
local_borg_version,
change_passphrase_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
-7
View File
@@ -22,7 +22,6 @@ import borgmatic.borg.pattern
import borgmatic.borg.repo_list
import borgmatic.borg.state
import borgmatic.config.paths
import borgmatic.config.validate
import borgmatic.execute
import borgmatic.hooks.command
@@ -750,12 +749,6 @@ def run_check(
Raise ValueError if the Borg repository ID cannot be determined.
'''
if check_arguments.repository and not borgmatic.config.validate.repositories_match(
repository,
check_arguments.repository,
):
return
logger.info('Running consistency checks')
repository_id = borgmatic.borg.check.get_repository_id(
-7
View File
@@ -2,7 +2,6 @@ import logging
import borgmatic.borg.compact
import borgmatic.borg.feature
import borgmatic.config.validate
import borgmatic.hooks.command
logger = logging.getLogger(__name__)
@@ -22,12 +21,6 @@ def run_compact(
'''
Run the "compact" action for the given repository.
'''
if compact_arguments.repository and not borgmatic.config.validate.repositories_match(
repository,
compact_arguments.repository,
):
return
if borgmatic.borg.feature.available(borgmatic.borg.feature.Feature.COMPACT, local_borg_version):
logger.info(f'Compacting segments{dry_run_label}')
borgmatic.borg.compact.compact_segments(
-1
View File
@@ -5,7 +5,6 @@ import os
import borgmatic.borg.extract
import borgmatic.borg.repo_list
import borgmatic.config.paths
import borgmatic.config.validate
import borgmatic.hooks.command
logger = logging.getLogger(__name__)
+5 -9
View File
@@ -6,7 +6,6 @@ import borgmatic.borg.feature
import borgmatic.borg.rename
import borgmatic.borg.repo_list
import borgmatic.config.paths
import borgmatic.config.validate
import borgmatic.hooks.dispatch
from borgmatic.actions import pattern
@@ -30,12 +29,6 @@ def run_create(
If create_arguments.json is True, yield the JSON output from creating the archive.
'''
if create_arguments.repository and not borgmatic.config.validate.repositories_match(
repository,
create_arguments.repository,
):
return
if config.get('list_details') and config.get('progress'):
raise ValueError(
'With the create action, only one of --list/--files/list_details and --progress/progress can be used.',
@@ -56,12 +49,13 @@ def run_create(
working_directory,
borgmatic_runtime_directory,
)
original_patterns = list(patterns)
borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
'remove_data_source_dumps',
config,
borgmatic.hooks.dispatch.Hook_type.DATA_SOURCE,
borgmatic_runtime_directory,
patterns,
original_patterns,
global_arguments.dry_run,
)
active_dumps = borgmatic.hooks.dispatch.call_hooks(
@@ -136,12 +130,14 @@ def run_create(
yield output
# Use the original patterns so as to disregard any modifications made by any data source
# hooks, e.g. via dump_data_sources() above.
borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
'remove_data_source_dumps',
config,
borgmatic.hooks.dispatch.Hook_type.DATA_SOURCE,
borgmatic_runtime_directory,
patterns,
original_patterns,
global_arguments.dry_run,
)
+18 -22
View File
@@ -20,32 +20,28 @@ def run_delete(
'''
Run the "delete" action for the given repository and archive(s).
'''
if delete_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
delete_arguments.repository,
):
logger.answer('Deleting archives')
logger.answer('Deleting archives')
archive_name = (
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
delete_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
if delete_arguments.archive
else None
)
borgmatic.borg.delete.delete_archives(
repository,
archive_name = (
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
delete_arguments.archive,
config,
local_borg_version,
borgmatic.actions.arguments.update_arguments(delete_arguments, archive=archive_name),
global_arguments,
local_path,
remote_path,
)
if delete_arguments.archive
else None
)
borgmatic.borg.delete.delete_archives(
repository,
config,
local_borg_version,
borgmatic.actions.arguments.update_arguments(delete_arguments, archive=archive_name),
global_arguments,
local_path,
remote_path,
)
+10 -15
View File
@@ -1,7 +1,6 @@
import logging
import borgmatic.borg.export_key
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -18,17 +17,13 @@ def run_export_key(
'''
Run the "key export" action for the given repository.
'''
if export_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
export_arguments.repository,
):
logger.info('Exporting repository key')
borgmatic.borg.export_key.export_key(
repository['path'],
config,
local_borg_version,
export_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
logger.info('Exporting repository key')
borgmatic.borg.export_key.export_key(
repository['path'],
config,
local_borg_version,
export_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
+19 -24
View File
@@ -2,7 +2,6 @@ import logging
import borgmatic.borg.export_tar
import borgmatic.borg.repo_list
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -19,30 +18,26 @@ def run_export_tar(
'''
Run the "export-tar" action for the given repository.
'''
if export_tar_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
export_tar_arguments.repository,
):
logger.info(f'Exporting archive {export_tar_arguments.archive} as tar file')
borgmatic.borg.export_tar.export_tar_archive(
global_arguments.dry_run,
logger.info(f'Exporting archive {export_tar_arguments.archive} as tar file')
borgmatic.borg.export_tar.export_tar_archive(
global_arguments.dry_run,
repository['path'],
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
export_tar_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
),
export_tar_arguments.paths,
export_tar_arguments.destination,
export_tar_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
tar_filter=export_tar_arguments.tar_filter,
strip_components=export_tar_arguments.strip_components,
)
local_path,
remote_path,
),
export_tar_arguments.paths,
export_tar_arguments.destination,
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
tar_filter=export_tar_arguments.tar_filter,
strip_components=export_tar_arguments.strip_components,
)
+18 -23
View File
@@ -2,7 +2,6 @@ import logging
import borgmatic.borg.extract
import borgmatic.borg.repo_list
import borgmatic.config.validate
import borgmatic.hooks.command
logger = logging.getLogger(__name__)
@@ -21,29 +20,25 @@ def run_extract(
'''
Run the "extract" action for the given repository.
'''
if extract_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
extract_arguments.repository,
):
logger.info(f'Extracting archive {extract_arguments.archive}')
borgmatic.borg.extract.extract_archive(
global_arguments.dry_run,
logger.info(f'Extracting archive {extract_arguments.archive}')
borgmatic.borg.extract.extract_archive(
global_arguments.dry_run,
repository['path'],
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
extract_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
),
extract_arguments.paths,
extract_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
destination_path=extract_arguments.destination,
strip_components=extract_arguments.strip_components,
)
local_path,
remote_path,
),
extract_arguments.paths,
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
destination_path=extract_arguments.destination,
strip_components=extract_arguments.strip_components,
)
+10 -15
View File
@@ -1,7 +1,6 @@
import logging
import borgmatic.borg.import_key
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -18,17 +17,13 @@ def run_import_key(
'''
Run the "key import" action for the given repository.
'''
if import_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
import_arguments.repository,
):
logger.info('Importing repository key')
borgmatic.borg.import_key.import_key(
repository['path'],
config,
local_borg_version,
import_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
logger.info('Importing repository key')
borgmatic.borg.import_key.import_key(
repository['path'],
config,
local_borg_version,
import_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
+22 -27
View File
@@ -4,7 +4,6 @@ import borgmatic.actions.arguments
import borgmatic.actions.json
import borgmatic.borg.info
import borgmatic.borg.repo_list
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -23,30 +22,26 @@ def run_info(
If info_arguments.json is True, yield the JSON output from the info for the archive.
'''
if info_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
info_arguments.repository,
):
if not info_arguments.json:
logger.answer('Displaying archive summary information')
if not info_arguments.json:
logger.answer('Displaying archive summary information')
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
info_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
json_output = borgmatic.borg.info.display_archives_info(
repository['path'],
config,
local_borg_version,
borgmatic.actions.arguments.update_arguments(info_arguments, archive=archive_name),
global_arguments,
local_path,
remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
info_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
json_output = borgmatic.borg.info.display_archives_info(
repository['path'],
config,
local_borg_version,
borgmatic.actions.arguments.update_arguments(info_arguments, archive=archive_name),
global_arguments,
local_path,
remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+25 -30
View File
@@ -3,7 +3,6 @@ import logging
import borgmatic.actions.arguments
import borgmatic.actions.json
import borgmatic.borg.list
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -22,33 +21,29 @@ def run_list(
If list_arguments.json is True, yield the JSON output from listing the archive.
'''
if list_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
list_arguments.repository,
):
if not list_arguments.json:
if list_arguments.find_paths: # pragma: no cover
logger.answer('Searching archives')
elif not list_arguments.archive: # pragma: no cover
logger.answer('Listing archives')
if not list_arguments.json:
if list_arguments.find_paths: # pragma: no cover
logger.answer('Searching archives')
elif not list_arguments.archive: # pragma: no cover
logger.answer('Listing archives')
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
list_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
json_output = borgmatic.borg.list.list_archive(
repository['path'],
config,
local_borg_version,
borgmatic.actions.arguments.update_arguments(list_arguments, archive=archive_name),
global_arguments,
local_path,
remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
list_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
json_output = borgmatic.borg.list.list_archive(
repository['path'],
config,
local_borg_version,
borgmatic.actions.arguments.update_arguments(list_arguments, archive=archive_name),
global_arguments,
local_path,
remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+18 -23
View File
@@ -2,7 +2,6 @@ import logging
import borgmatic.borg.mount
import borgmatic.borg.repo_list
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -19,30 +18,26 @@ def run_mount(
'''
Run the "mount" action for the given repository.
'''
if mount_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
mount_arguments.repository,
):
if mount_arguments.archive:
logger.info(f'Mounting archive {mount_arguments.archive}')
else: # pragma: nocover
logger.info('Mounting repository')
if mount_arguments.archive:
logger.info(f'Mounting archive {mount_arguments.archive}')
else: # pragma: nocover
logger.info('Mounting repository')
borgmatic.borg.mount.mount_archive(
borgmatic.borg.mount.mount_archive(
repository['path'],
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
mount_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
),
mount_arguments,
mount_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
local_path,
remote_path,
),
mount_arguments,
config,
local_borg_version,
global_arguments,
local_path=local_path,
remote_path=remote_path,
)
-7
View File
@@ -1,7 +1,6 @@
import logging
import borgmatic.borg.prune
import borgmatic.config.validate
import borgmatic.hooks.command
logger = logging.getLogger(__name__)
@@ -21,12 +20,6 @@ def run_prune(
'''
Run the "prune" action for the given repository.
'''
if prune_arguments.repository and not borgmatic.config.validate.repositories_match(
repository,
prune_arguments.repository,
):
return
logger.info(f'Pruning archives{dry_run_label}')
borgmatic.borg.prune.prune_archives(
global_arguments.dry_run,
+45 -50
View File
@@ -4,7 +4,6 @@ import subprocess
import borgmatic.borg.info
import borgmatic.borg.recreate
import borgmatic.borg.repo_list
import borgmatic.config.validate
from borgmatic.actions.pattern import collect_patterns, process_patterns
logger = logging.getLogger(__name__)
@@ -25,64 +24,60 @@ def run_recreate(
'''
Run the "recreate" action for the given repository.
'''
if recreate_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
recreate_arguments.repository,
):
if recreate_arguments.archive:
logger.answer(f'Recreating archive {recreate_arguments.archive}')
else:
logger.answer('Recreating repository')
if recreate_arguments.archive:
logger.answer(f'Recreating archive {recreate_arguments.archive}')
else:
logger.answer('Recreating repository')
# Collect and process patterns.
processed_patterns = process_patterns(
collect_patterns(config),
config,
borgmatic.config.paths.get_working_directory(config),
# Collect and process patterns.
processed_patterns = process_patterns(
collect_patterns(config),
config,
borgmatic.config.paths.get_working_directory(config),
)
archive = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
recreate_arguments.archive,
config,
local_borg_version,
global_arguments,
local_path,
remote_path,
)
if archive and archive.endswith('.recreate'):
if recreate_arguments.archive == 'latest':
raise ValueError(
f'The latest archive "{archive}" is leftover from a prior recreate. Delete it first or select a different archive.',
)
raise ValueError(
f'The archive "{recreate_arguments.archive}" is leftover from a prior recreate. Select a different archive.',
)
archive = borgmatic.borg.repo_list.resolve_archive_name(
try:
borgmatic.borg.recreate.recreate_archive(
repository['path'],
recreate_arguments.archive,
archive,
config,
local_borg_version,
recreate_arguments,
global_arguments,
local_path,
remote_path,
local_path=local_path,
remote_path=remote_path,
patterns=processed_patterns,
)
if archive and archive.endswith('.recreate'):
if recreate_arguments.archive == 'latest':
except subprocess.CalledProcessError as error:
if error.returncode == BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS:
if recreate_arguments.target:
raise ValueError(
f'The latest archive "{archive}" is leftover from a prior recreate. Delete it first or select a different archive.',
f'The archive "{recreate_arguments.target}" already exists. Delete it first or set a different target archive name.',
)
raise ValueError(
f'The archive "{recreate_arguments.archive}" is leftover from a prior recreate. Select a different archive.',
)
if archive:
raise ValueError(
f'The archive "{archive}.recreate" is leftover from a prior recreate. Delete it first or select a different archive.',
)
try:
borgmatic.borg.recreate.recreate_archive(
repository['path'],
archive,
config,
local_borg_version,
recreate_arguments,
global_arguments,
local_path=local_path,
remote_path=remote_path,
patterns=processed_patterns,
)
except subprocess.CalledProcessError as error:
if error.returncode == BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS:
if recreate_arguments.target:
raise ValueError(
f'The archive "{recreate_arguments.target}" already exists. Delete it first or set a different target archive name.',
)
if archive:
raise ValueError(
f'The archive "{archive}.recreate" is leftover from a prior recreate. Delete it first or select a different archive.',
)
raise
raise
-7
View File
@@ -1,7 +1,6 @@
import logging
import borgmatic.borg.repo_create
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -18,12 +17,6 @@ def run_repo_create(
'''
Run the "repo-create" action for the given repository.
'''
if repo_create_arguments.repository and not borgmatic.config.validate.repositories_match(
repository,
repo_create_arguments.repository,
):
return
logger.info('Creating repository')
encryption_mode = repo_create_arguments.encryption_mode or repository.get('encryption')
+12 -16
View File
@@ -17,20 +17,16 @@ def run_repo_delete(
'''
Run the "repo-delete" action for the given repository.
'''
if repo_delete_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
repo_delete_arguments.repository,
):
logger.answer(
'Deleting repository' + (' cache' if repo_delete_arguments.cache_only else ''),
)
logger.answer(
'Deleting repository' + (' cache' if repo_delete_arguments.cache_only else ''),
)
borgmatic.borg.repo_delete.delete_repository(
repository,
config,
local_borg_version,
repo_delete_arguments,
global_arguments,
local_path,
remote_path,
)
borgmatic.borg.repo_delete.delete_repository(
repository,
config,
local_borg_version,
repo_delete_arguments,
global_arguments,
local_path,
remote_path,
)
+13 -18
View File
@@ -2,7 +2,6 @@ import logging
import borgmatic.actions.json
import borgmatic.borg.repo_info
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -21,21 +20,17 @@ def run_repo_info(
If repo_info_arguments.json is True, yield the JSON output from the info for the repository.
'''
if repo_info_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
repo_info_arguments.repository,
):
if not repo_info_arguments.json:
logger.answer('Displaying repository summary information')
if not repo_info_arguments.json:
logger.answer('Displaying repository summary information')
json_output = borgmatic.borg.repo_info.display_repository_info(
repository['path'],
config,
local_borg_version,
repo_info_arguments=repo_info_arguments,
global_arguments=global_arguments,
local_path=local_path,
remote_path=remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
json_output = borgmatic.borg.repo_info.display_repository_info(
repository['path'],
config,
local_borg_version,
repo_info_arguments=repo_info_arguments,
global_arguments=global_arguments,
local_path=local_path,
remote_path=remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+13 -18
View File
@@ -2,7 +2,6 @@ import logging
import borgmatic.actions.json
import borgmatic.borg.repo_list
import borgmatic.config.validate
logger = logging.getLogger(__name__)
@@ -21,21 +20,17 @@ def run_repo_list(
If repo_list_arguments.json is True, yield the JSON output from listing the repository.
'''
if repo_list_arguments.repository is None or borgmatic.config.validate.repositories_match(
repository,
repo_list_arguments.repository,
):
if not repo_list_arguments.json:
logger.answer('Listing repository')
if not repo_list_arguments.json:
logger.answer('Listing repository')
json_output = borgmatic.borg.repo_list.list_repository(
repository['path'],
config,
local_borg_version,
repo_list_arguments=repo_list_arguments,
global_arguments=global_arguments,
local_path=local_path,
remote_path=remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
json_output = borgmatic.borg.repo_list.list_repository(
repository['path'],
config,
local_borg_version,
repo_list_arguments=repo_list_arguments,
global_arguments=global_arguments,
local_path=local_path,
remote_path=remote_path,
)
if json_output:
yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+1 -9
View File
@@ -11,7 +11,6 @@ import borgmatic.borg.list
import borgmatic.borg.mount
import borgmatic.borg.repo_list
import borgmatic.config.paths
import borgmatic.config.validate
import borgmatic.hooks.data_source.dump
import borgmatic.hooks.dispatch
@@ -524,18 +523,11 @@ def run_restore(
remote_path,
):
'''
Run the "restore" action for the given repository, but only if the repository matches the
requested repository in restore arguments.
Run the "restore" action for the given repository.
Raise ValueError if a configured data source could not be found to restore or there's no
matching dump in the archive.
'''
if restore_arguments.repository and not borgmatic.config.validate.repositories_match(
repository,
restore_arguments.repository,
):
return
logger.info(f'Restoring data sources from archive {restore_arguments.archive}')
working_directory = borgmatic.config.paths.get_working_directory(config)
+8 -1
View File
@@ -27,7 +27,14 @@ def make_repo_delete_command(
arguments to the repo_delete action as an argparse.Namespace, and global arguments, return a command
as a tuple to repo_delete the entire repository.
'''
extra_borg_options = config.get('extra_borg_options', {}).get('repo_delete', '')
extra_borg_options = config.get('extra_borg_options', {}).get(
'repo_delete'
if borgmatic.borg.feature.available(
borgmatic.borg.feature.Feature.REPO_DELETE, local_borg_version
)
else 'delete',
'',
)
return (
(local_path,)
+4 -1
View File
@@ -25,7 +25,10 @@ def display_repository_info(
'''
borgmatic.logger.add_custom_log_levels()
lock_wait = config.get('lock_wait', None)
extra_borg_options = config.get('extra_borg_options', {}).get('repo_info', '')
extra_borg_options = config.get('extra_borg_options', {}).get(
'repo_info' if feature.available(feature.Feature.REPO_INFO, local_borg_version) else 'info',
'',
)
full_command = (
(local_path,)
+8 -2
View File
@@ -63,7 +63,10 @@ def get_latest_archive(
Raises ValueError if there are no archives in the repository.
'''
extra_borg_options = config.get('extra_borg_options', {}).get('repo_list', '')
extra_borg_options = config.get('extra_borg_options', {}).get(
'repo_list' if feature.available(feature.Feature.REPO_LIST, local_borg_version) else 'list',
'',
)
full_command = (
local_path,
@@ -124,7 +127,10 @@ def make_repo_list_command(
arguments to the repo_list action, global arguments as an argparse.Namespace instance, and local and
remote Borg paths, return a command as a tuple to list archives with a repository.
'''
extra_borg_options = config.get('extra_borg_options', {}).get('repo_list', '')
extra_borg_options = config.get('extra_borg_options', {}).get(
'repo_list' if feature.available(feature.Feature.REPO_LIST, local_borg_version) else 'list',
'',
)
return (
(
+4 -5
View File
@@ -568,8 +568,7 @@ def make_parsers(schema, unparsed_arguments): # noqa: PLR0915
ignoring actions, and the combined parser is handy for displaying help that includes everything:
global flags, a list of actions, etc.
'''
config_paths = collect.get_default_config_paths(expand_home=True)
unexpanded_config_paths = collect.get_default_config_paths(expand_home=False)
config_paths = collect.get_default_config_paths()
# Using allow_abbrev=False here prevents the global parser from erroring about "ambiguous"
# options like --encryption. Such options are intended for an action parser rather than the
@@ -582,7 +581,7 @@ def make_parsers(schema, unparsed_arguments): # noqa: PLR0915
'--config',
dest='config_paths',
action='append',
help=f"Configuration filename or directory, can specify flag multiple times, defaults to: -c {' -c '.join(unexpanded_config_paths)}",
help='Configuration filename or directory, can specify flag multiple times, defaults to /etc/borgmatic/config.yaml, /etc/borgmatic.d, $XDG_CONFIG_HOME/borgmatic/config.yaml, and $XDG_CONFIG_HOME/borgmatic.d, where $XDG_CONFIG_HOME defaults to $HOME/.config',
)
global_group.add_argument(
'-n',
@@ -1215,7 +1214,7 @@ def make_parsers(schema, unparsed_arguments): # noqa: PLR0915
'--destination',
dest='destination_path',
default=config_paths[0],
help=f'Destination configuration file (or directory if using --split), default: {unexpanded_config_paths[0]}',
help='Destination configuration file (or directory if using --split), default: /etc/borgmatic/config.yaml',
)
config_generate_group.add_argument(
'--overwrite',
@@ -2015,7 +2014,7 @@ def parse_arguments(schema, *unparsed_arguments):
)
if not arguments['global'].config_paths:
arguments['global'].config_paths = collect.get_default_config_paths(expand_home=True)
arguments['global'].config_paths = collect.get_default_config_paths()
for action_name in ('bootstrap', 'generate', 'validate'):
if action_name in arguments and len(arguments) > HIGHLANDER_ACTION_ARGUMENTS_COUNT:
+17 -1
View File
@@ -340,7 +340,7 @@ def run_actions( # noqa: PLR0912, PLR0915
'''
Given parsed command-line arguments as an argparse.ArgumentParser instance, the configuration
filename, a configuration dict, a sequence of loaded configuration paths, local and remote paths
to Borg, a local Borg version string, and a repository name, run all actions from the
to Borg, a local Borg version string, and a repository dict, run all actions from the
command-line arguments on the given repository.
Yield JSON output strings from executing any actions that produce JSON.
@@ -362,6 +362,22 @@ def run_actions( # noqa: PLR0912, PLR0915
'repository': repository_path,
}
skip_actions = set(get_skip_actions(config, arguments))
requested_repository = next(
(
repository
for action_arguments in arguments.values()
for repository in (getattr(action_arguments, 'repository', None),)
if repository is not None
),
None,
)
if requested_repository and not borgmatic.config.validate.repositories_match(
repository,
requested_repository,
):
logger.debug('Skipping actions because the requested --repository does not match')
return
with borgmatic.hooks.command.Before_after_hooks(
command_hooks=config.get('commands'),
+6 -9
View File
@@ -1,17 +1,14 @@
import os
def get_default_config_paths(expand_home=True):
def get_default_config_paths():
'''
Based on the value of the XDG_CONFIG_HOME and HOME environment variables, return a list of
default configuration paths. This includes both system-wide configuration and configuration in
the current user's home directory.
Don't expand the home directory ($HOME) if the expand home flag is False.
Return a list of default configuration paths. This includes both system-wide
configuration and configuration in the current user's home directory.
'''
user_config_directory = os.getenv('XDG_CONFIG_HOME') or os.path.join('$HOME', '.config')
if expand_home:
user_config_directory = os.path.expandvars(user_config_directory)
user_config_directory = os.path.expandvars(
os.getenv('XDG_CONFIG_HOME') or os.path.join('$HOME', '.config')
)
return [
'/etc/borgmatic/config.yaml',
+28 -3
View File
@@ -1,6 +1,7 @@
import collections
import contextlib
import io
import json
import os
import re
@@ -316,6 +317,24 @@ def merge_source_configuration_into_destination(destination_config, source_confi
return destination_config
def get_configuration_subset(config, option_name): # pragma: no cover
'''
Given configuration as a ruamel.yaml.CommentedMap and an option name found within it at the top
level, return a new CommentedMap containing a subset of the configuration with only the given
option and no other top-level options.
This is useful when generating the sample configuration for a single option instead of a whole
configuration file.
'''
option_config = ruamel.yaml.CommentedMap({option_name: config[option_name]})
# Due to a quirk of ruamel.yaml, the comment right before a top-level key is not on that key and
# needs to get copied separately.
option_config.ca.items[option_name] = config.ca.items[option_name]
return option_config
def generate_sample_configuration(
dry_run,
source_filename,
@@ -359,16 +378,22 @@ def generate_sample_configuration(
os.makedirs(destination_path, exist_ok=True)
for option_name, option_config in destination_config.items():
for option_name in destination_config:
write_configuration(
os.path.join(destination_path, f'{option_name}.yaml'),
transform_optional_configuration(
render_configuration({option_name: option_config}),
render_configuration(get_configuration_subset(destination_config, option_name)),
comment_out=False,
),
).strip(),
overwrite=overwrite,
)
# Also dump a manifest listing all the options we've written.
json.dump(
{'option_names': list(destination_config.keys())},
open(os.path.join(destination_path, 'options.json'), 'w', encoding='utf-8'),
)
return
if os.path.exists(destination_path) and not os.path.isfile(destination_path):
+26 -2
View File
@@ -2,10 +2,19 @@ import contextlib
import logging
import os
import tempfile
from enum import Enum
logger = logging.getLogger(__name__)
class Systemd_directories(Enum):
RUNTIME_DIRECTORY = 0
STATE_DIRECTORY = 1
CACHE_DIRECTORY = 2
LOGS_DIRECTORY = 3
CONFIGURATION_DIRECTORY = 4
def expand_user_in_path(path):
'''
Given a directory path, expand any tildes in it.
@@ -16,6 +25,17 @@ def expand_user_in_path(path):
return None
def resolve_systemd_directory(directory):
'''
Given a systemd directory environment variable enum, read the value if set and return the first
configured directory.
'''
separator = ':'
paths = os.environ.get(directory.name)
return paths.split(separator)[0] if paths else None
def get_working_directory(config): # pragma: no cover
'''
Given a configuration dict, get the working directory from it, expanding any tildes.
@@ -96,7 +116,9 @@ class Runtime_directory:
runtime_directory = (
config.get('user_runtime_directory')
or os.environ.get('XDG_RUNTIME_DIR') # Set by PAM on Linux.
or os.environ.get('RUNTIME_DIRECTORY') # Set by systemd if configured.
or resolve_systemd_directory(
Systemd_directories.RUNTIME_DIRECTORY
) # Set by systemd if configured.
)
if runtime_directory:
@@ -174,7 +196,9 @@ def get_borgmatic_state_directory(config):
os.path.join(
config.get('user_state_directory')
or os.environ.get('XDG_STATE_HOME')
or os.environ.get('STATE_DIRECTORY') # Set by systemd if configured.
or resolve_systemd_directory(
Systemd_directories.STATE_DIRECTORY
) # Set by systemd if configured.
or '~/.local/state',
'borgmatic',
),
+25 -4
View File
@@ -173,9 +173,11 @@ properties:
type: string
description: |
Any paths matching these patterns are included/excluded from
backups. Globs are expanded. (Tildes are not.) See the output of
"borg help patterns" for more details. Quote any value if it
contains leading punctuation, so it parses correctly.
backups. Recursion root patterns ("R ...") are effectively the same
as "source_directories"; they tell Borg which paths to backup
(modulo any excludes). Globs are expanded. (Tildes are not.) See
the output of "borg help patterns" for more details. Quote any value
if it contains leading punctuation, so it parses correctly.
example:
- 'R /'
- '- /home/*/.cache'
@@ -259,7 +261,7 @@ properties:
Path for storing temporary runtime data like streaming database
dumps and bootstrap metadata. borgmatic automatically creates and
uses a "borgmatic" subdirectory here. Defaults to $XDG_RUNTIME_DIR
or or $TMPDIR or $TEMP or /run/user/$UID.
or $TMPDIR or $TEMP or /run/user/$UID.
example: /run/user/1001
user_state_directory:
type: string
@@ -607,6 +609,11 @@ properties:
description: |
Extra command-line options to pass to "borg recreate".
example: "--extra-option"
rename:
type: string
description: |
Extra command-line options to pass to "borg rename".
example: "--extra-option"
repo_create:
type: string
description: |
@@ -3015,6 +3022,12 @@ properties:
project monitor. Used along with the data source name URL to
construct a cron URL.
example: mymonitor
environment:
type: string
description: |
Sentry monitor environment used in the call to Sentry. If
not set, the Sentry default is used.
example: production
states:
type: array
items:
@@ -3159,6 +3172,14 @@ properties:
description: |
Command to use instead of "keepassxc-cli".
example: /usr/local/bin/keepassxc-cli
ask_for_password:
type: boolean
description: |
Whether keepassxc-cli should prompt the user for a password.
Disabling this is only really useful if you're unlocking
your KeePassXC database with a key file instead of a
password. Defaults to true.
example: false
key_file:
type: string
description: |
+3 -3
View File
@@ -158,8 +158,8 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path, b
if not line or not ready_process:
break
# Keep the last few lines of output in case the process errors, and we need the output for
# the exception below.
# Keep the last few lines of output in case the process errors, and we need the
# output for the exception below.
append_last_lines(
buffer_last_lines[ready_buffer],
captured_outputs[ready_process],
@@ -199,7 +199,7 @@ def log_outputs(processes, exclude_stdouts, output_log_level, borg_local_path, b
last_lines,
captured_outputs[process],
line,
output_log_level=logging.ERROR,
output_log_level,
)
if len(last_lines) == ERROR_OUTPUT_MAX_LINE_COUNT:
+1
View File
@@ -29,6 +29,7 @@ def load_credential(hook_config, config, credential_parameters):
command = (
tuple(shlex.split((hook_config or {}).get('keepassxc_cli_command', 'keepassxc-cli')))
+ ('show', '--show-protected', '--attributes', 'Password')
+ (('--no-password',) if not (hook_config or {}).get('ask_for_password', True) else ())
+ (
('--key-file', hook_config['key_file'])
if hook_config and hook_config.get('key_file')
+9 -3
View File
@@ -146,8 +146,8 @@ Subvolume = collections.namedtuple('Subvolume', ('path', 'contained_patterns'),
def get_subvolumes(btrfs_command, patterns):
'''
Given a Btrfs command to run and a sequence of configured patterns, find the intersection
between the current Btrfs filesystem and subvolume paths and the paths of any patterns. The
idea is that these pattern paths represent the requested subvolumes to snapshot.
between the current Btrfs filesystem/subvolume paths and the paths of any patterns. The idea is
that these pattern paths represent the requested subvolumes to snapshot.
Only include subvolumes that contain at least one root pattern sourced from borgmatic
configuration (as opposed to generated elsewhere in borgmatic).
@@ -329,9 +329,15 @@ def dump_data_sources(
snapshot_subvolume(btrfs_command, subvolume.path, snapshot_path)
last_contained_pattern_index = borgmatic.hooks.data_source.config.get_last_pattern_index(
patterns, subvolume.contained_patterns
)
for pattern in subvolume.contained_patterns:
snapshot_pattern = make_borg_snapshot_pattern(subvolume.path, pattern)
borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern)
borgmatic.hooks.data_source.config.replace_pattern(
patterns, pattern, snapshot_pattern, last_contained_pattern_index
)
borgmatic.hooks.data_source.config.inject_pattern(
patterns, make_snapshot_exclude_pattern(subvolume.path)
+45 -4
View File
@@ -1,3 +1,4 @@
import contextlib
import json
import logging
import shutil
@@ -131,7 +132,23 @@ def inject_pattern(patterns, data_source_pattern):
patterns.insert(0, data_source_pattern)
def replace_pattern(patterns, pattern_to_replace, data_source_pattern):
def get_last_pattern_index(patterns, patterns_subset):
'''
Given a sequence of all patterns and a subset of those patterns, find each subset pattern in the
all patterns sequence and return the highest (last) index.
'''
last_pattern_index = 0
for pattern in patterns_subset:
with contextlib.suppress(ValueError):
last_pattern_index = max(patterns.index(pattern), last_pattern_index)
return last_pattern_index
def replace_pattern(
patterns, pattern_to_replace, data_source_pattern, last_contained_pattern_index
):
'''
Given a list of borgmatic.borg.pattern.Pattern instances representing the configured patterns,
replace the given pattern with the given data source pattern. The idea is that borgmatic is
@@ -139,8 +156,32 @@ def replace_pattern(patterns, pattern_to_replace, data_source_pattern):
that the hook's data gets included in the backup.
As part of this replacement, if the data source pattern is a root pattern, also insert an
"include" version of the given root pattern right after the replaced pattern, in an attempt to
preempt any of the user's configured exclude patterns that may follow.
"include" version of the given root pattern right after the given last contained pattern index
in an attempt to preempt any of the user's configured global exclude patterns that may follow.
But we don't want to preempt any intentional partial excludes of the data source pattern itself,
which is why the include goes after the last contained pattern index.
For instance, let's say that the patterns are effectively:
R /foo
R /bar
- /bar/.cache
R /baz
- **
... and "R /bar" is the pattern to replace, data source pattern is "R /bar/snapshot", and the
last contained pattern index is 2 (corresponding to "- /bar/.cache"). The resulting patterns
after calling this function would be:
R /foo
R /bar/snapshot
- /bar/snapshot/.cache
+ /bar/snapshot
R /baz
- **
Note that the positioning of "+ /bar/snapshot" means that it overrides the "- **" global exclude
but not the "- /bar/snapshot/.cache" contained pattern exclude.
If the pattern to replace can't be found in the given patterns, then just inject the data source
pattern at the start of the list.
@@ -156,7 +197,7 @@ def replace_pattern(patterns, pattern_to_replace, data_source_pattern):
if data_source_pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT:
patterns.insert(
index + 1,
last_contained_pattern_index + 1,
borgmatic.borg.pattern.Pattern(
path=data_source_pattern.path,
type=borgmatic.borg.pattern.Pattern_type.INCLUDE,
+7 -1
View File
@@ -269,6 +269,10 @@ def dump_data_sources(
snapshot_mount_path,
)
last_contained_pattern_index = borgmatic.hooks.data_source.config.get_last_pattern_index(
patterns, logical_volume.contained_patterns
)
for pattern in logical_volume.contained_patterns:
snapshot_pattern = make_borg_snapshot_pattern(
pattern,
@@ -276,7 +280,9 @@ def dump_data_sources(
normalized_runtime_directory,
)
borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern)
borgmatic.hooks.data_source.config.replace_pattern(
patterns, pattern, snapshot_pattern, last_contained_pattern_index
)
return []
+7 -1
View File
@@ -155,7 +155,11 @@ def database_names_to_dump(database, config, username, password, environment, dr
if skip_names:
logger.debug(f'Skipping database names: {", ".join(skip_names)}')
show_output = execute_command_and_capture_output(show_command, environment=environment)
show_output = execute_command_and_capture_output(
show_command,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
return tuple(
show_name
@@ -239,6 +243,7 @@ def execute_dump_command(
dump_command,
environment=environment,
run_to_completion=False,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
@@ -495,4 +500,5 @@ def restore_data_source_dump(
output_log_level=logging.DEBUG,
input_file=extract_process.stdout,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
+12 -2
View File
@@ -90,11 +90,20 @@ def dump_data_sources(
if dump_format == 'directory':
dump.create_parent_directory_for_dump(dump_filename)
execute_command(command, shell=True) # noqa: S604
execute_command( # noqa: S604
command,
shell=True,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
else:
dump.create_named_pipe_for_dump(dump_filename)
processes.append(
execute_command(command, shell=True, run_to_completion=False), # noqa: S604
execute_command( # noqa: S604
command,
shell=True,
run_to_completion=False,
working_directory=borgmatic.config.paths.get_working_directory(config),
),
)
if not dry_run:
@@ -273,6 +282,7 @@ def restore_data_source_dump(
[extract_process] if extract_process else [],
output_log_level=logging.DEBUG,
input_file=extract_process.stdout if extract_process else None,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
+7 -1
View File
@@ -84,7 +84,11 @@ def database_names_to_dump(database, config, username, password, environment, dr
if skip_names:
logger.debug(f'Skipping database names: {", ".join(skip_names)}')
show_output = execute_command_and_capture_output(show_command, environment=environment)
show_output = execute_command_and_capture_output(
show_command,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
return tuple(
show_name
@@ -170,6 +174,7 @@ def execute_dump_command(
dump_command,
environment=environment,
run_to_completion=False,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
@@ -432,4 +437,5 @@ def restore_data_source_dump(
output_log_level=logging.DEBUG,
input_file=extract_process.stdout,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
+13 -2
View File
@@ -103,7 +103,11 @@ def database_names_to_dump(database, config, environment, dry_run):
+ (tuple(database['list_options'].split(' ')) if 'list_options' in database else ())
)
logger.debug('Querying for "all" PostgreSQL databases to dump')
list_output = execute_command_and_capture_output(list_command, environment=environment)
list_output = execute_command_and_capture_output(
list_command,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
return tuple(
row[0]
@@ -245,6 +249,7 @@ def dump_data_sources(
command,
shell=True,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
else:
dump.create_named_pipe_for_dump(dump_filename)
@@ -254,6 +259,7 @@ def dump_data_sources(
shell=True,
environment=environment,
run_to_completion=False,
working_directory=borgmatic.config.paths.get_working_directory(config),
),
)
@@ -422,5 +428,10 @@ def restore_data_source_dump(
output_log_level=logging.DEBUG,
input_file=extract_process.stdout if extract_process else None,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
execute_command(
analyze_command,
environment=environment,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
execute_command(analyze_command, environment=environment)
+9 -2
View File
@@ -86,6 +86,7 @@ def dump_data_sources(
)
command = (
*sqlite_command,
'-bail',
shlex.quote(database_path),
'.dump',
'>',
@@ -100,7 +101,12 @@ def dump_data_sources(
dump.create_named_pipe_for_dump(dump_filename)
processes.append(
execute_command(command, shell=True, run_to_completion=False), # noqa: S604
execute_command( # noqa: S604
command,
shell=True,
run_to_completion=False,
working_directory=borgmatic.config.paths.get_working_directory(config),
),
)
if not dry_run:
@@ -196,7 +202,7 @@ def restore_data_source_dump(
shlex.quote(part)
for part in shlex.split(data_source.get('sqlite_restore_command') or 'sqlite3')
)
restore_command = (*sqlite_restore_command, shlex.quote(database_path))
restore_command = (*sqlite_restore_command, '-bail', shlex.quote(database_path))
# Don't give Borg local path so as to error on warnings, as "borg extract" only gives a warning
# if the restore paths don't exist in the archive.
execute_command_with_processes(
@@ -204,4 +210,5 @@ def restore_data_source_dump(
[extract_process],
output_log_level=logging.DEBUG,
input_file=extract_process.stdout,
working_directory=borgmatic.config.paths.get_working_directory(config),
)
+7 -1
View File
@@ -300,6 +300,10 @@ def dump_data_sources(
snapshot_mount_path,
)
last_contained_pattern_index = borgmatic.hooks.data_source.config.get_last_pattern_index(
patterns, dataset.contained_patterns
)
for pattern in dataset.contained_patterns:
snapshot_pattern = make_borg_snapshot_pattern(
pattern,
@@ -307,7 +311,9 @@ def dump_data_sources(
normalized_runtime_directory,
)
borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern)
borgmatic.hooks.data_source.config.replace_pattern(
patterns, pattern, snapshot_pattern, last_contained_pattern_index
)
return []
+5 -1
View File
@@ -1,5 +1,6 @@
import logging
import re
import urllib
import requests
@@ -40,6 +41,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
dry_run_label = ' (dry run; not actually pinging)' if dry_run else ''
data_source_name_url = hook_config.get('data_source_name_url')
environment = hook_config.get('environment')
monitor_slug = hook_config.get('monitor_slug')
match = DATA_SOURCE_NAME_URL_PATTERN.match(data_source_name_url)
@@ -65,10 +67,12 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
if dry_run:
return
environment_query = f'&environment={urllib.parse.quote(environment)}' if environment else ''
logging.getLogger('urllib3').setLevel(logging.ERROR)
try:
response = requests.post(
f'{cron_url}?status={status}',
f'{cron_url}?status={status}{environment_query}',
timeout=TIMEOUT_SECONDS,
headers={'User-Agent': 'borgmatic'},
)
+1
View File
@@ -24,6 +24,7 @@ RUN npm install @11ty/eleventy \
markdown-it-anchor \
markdown-it-replace-link
COPY --from=borgmatic /etc/borgmatic/* /source/docs/_includes/borgmatic/
COPY --from=borgmatic /etc/borgmatic/options.json /source/docs/reference/configuration/index.json
COPY --from=borgmatic /command-line/* /source/docs/_includes/borgmatic/command-line/
COPY --from=borgmatic /contributors.html /source/docs/_includes/borgmatic/contributors.html
COPY . /source
@@ -1,5 +1,5 @@
---
title: How to add preparation and cleanup steps to backups
title: 🧹 How to add preparation and cleanup steps to backups
eleventyNavigation:
key: 🧹 Add preparation and cleanup steps
parent: How-to guides
@@ -1,5 +1,5 @@
---
title: How to backup to a removable drive or an intermittent server
title: 💾 How to backup to a removable drive or an intermittent server
eleventyNavigation:
key: 💾 Backup to a removable drive/server
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to backup your databases
title: 🗄️ How to backup your databases
eleventyNavigation:
key: 🗄️ Backup your databases
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to customize warnings and errors
title: 💥 How to customize warnings and errors
eleventyNavigation:
key: 💥 Customize warnings/errors
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to deal with very large backups
title: 📏 How to deal with very large backups
eleventyNavigation:
key: 📏 Deal with very large backups
parent: How-to guides
+5 -1
View File
@@ -1,10 +1,12 @@
---
title: How to develop on borgmatic
title: 🏗️ How to develop on borgmatic
eleventyNavigation:
key: 🏗️ Develop on borgmatic
parent: How-to guides
order: 15
---
<span data-pagefind-weight="0.25">
To get set up to develop on borgmatic, first [`install
uv`](https://docs.astral.sh/uv/) to make managing your borgmatic environment
easier without impacting other Python applications on your system.
@@ -205,3 +207,5 @@ borgmatic's developer build for documentation optionally supports using
Setting up Podman is outside the scope of this documentation. But once you
install and configure Podman, then `scripts/dev-docs` should automatically use
Podman instead of Docker.
</span>
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to extract a backup
title: 📤 How to extract a backup
eleventyNavigation:
key: 📤 Extract a backup
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to inspect your backups
title: 🔎 How to inspect your backups
eleventyNavigation:
key: 🔎 Inspect your backups
parent: How-to guides
+4 -3
View File
@@ -1,5 +1,5 @@
---
title: How to make backups redundant
title: ☁️ How to make backups redundant
eleventyNavigation:
key: ☁️ Make backups redundant
parent: How-to guides
@@ -40,10 +40,11 @@ Here's a way of visualizing what borgmatic does with the above configuration:
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)
See [repositories
documentation](https://torsion.org/borgmatic/reference/configuration/repositories/)
for more information on how to specify local and remote repository paths.
### Different options per repository
What if you want borgmatic to backup to multiple repositories—while also
+15 -1
View File
@@ -1,5 +1,5 @@
---
title: How to make per-application backups
title: 🗂️ How to make per-application backups
eleventyNavigation:
key: 🗂️ Make per-application backups
parent: How-to guides
@@ -59,6 +59,20 @@ entry using borgmatic's `--config` flag instead of relying on
`/etc/borgmatic.d`.
## Limitations
borgmatic does not currently support its own parallelism—being run multiple
times on the same machine simultaneously. In particular, many of the [data
source
hooks](https://torsion.org/borgmatic/reference/configuration/data-sources/) rely
on global borgmatic runtime files which can't be shared across processes, and
therefore multiple borgmatic instances on the same machine would interfere with
each other.
A single borgmatic instance also doesn't currently support running multiple Borg
instances in parallel on the same machine.
<a id="archive-naming"></a>
<a id="configuration-includes"></a>
<a id="configuration-overrides"></a>
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to monitor your backups
title: 🚨 How to monitor your backups
eleventyNavigation:
key: 🚨 Monitor your backups
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to provide your passwords
title: 🔒 How to provide your passwords
eleventyNavigation:
key: 🔒 Provide your passwords
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to run arbitrary Borg commands
title: 🔧 How to run arbitrary Borg commands
eleventyNavigation:
key: 🔧 Run arbitrary Borg commands
parent: How-to guides
+34 -50
View File
@@ -1,5 +1,5 @@
---
title: How to set up backups
title: 📥 How to set up backups
eleventyNavigation:
key: 📥 Set up backups
parent: How-to guides
@@ -75,12 +75,16 @@ with `sudo`. If that doesn't work, you may need to update your [sudoers
Besides the approaches described above, there are several other options for
installing borgmatic:
* [container image with scheduled backups](https://hub.docker.com/r/b3vis/borgmatic/) (+ Docker Compose files)
* [container image with multi-arch and Docker CLI support](https://hub.docker.com/r/modem7/borgmatic-docker/)
#### <span data-pagefind-weight="7.0">Docker / Podman</span>
* [container image with scheduled backups](https://github.com/borgmatic-collective/docker-borgmatic) (+ Docker Compose files)
* [container image with multi-arch and Docker CLI support](https://github.com/modem7/docker-borgmatic)
#### Operating system packages
* [Debian](https://tracker.debian.org/pkg/borgmatic)
* [Ubuntu](https://launchpad.net/ubuntu/+source/borgmatic)
* [Fedora official](https://bodhi.fedoraproject.org/updates/?search=borgmatic)
* [Fedora unofficial](https://copr.fedorainfracloud.org/coprs/heffer/borgmatic/)
* [Fedora](https://bodhi.fedoraproject.org/updates/?search=borgmatic)
* [Gentoo](https://packages.gentoo.org/packages/app-backup/borgmatic)
* [Arch Linux](https://archlinux.org/packages/extra/any/borgmatic/)
* [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=borgmatic)
@@ -88,9 +92,11 @@ installing borgmatic:
* [openSUSE](https://software.opensuse.org/package/borgmatic)
* [macOS (via Homebrew)](https://formulae.brew.sh/formula/borgmatic)
* [macOS (via MacPorts)](https://ports.macports.org/port/borgmatic/)
* [NixOS](https://search.nixos.org/packages?show=borgmatic&sort=relevance&type=packages&query=borgmatic)
* [NixOS](https://search.nixos.org/packages?channel=unstable&show=borgmatic&query=borgmatic)
#### Etc.
* [Ansible role](https://github.com/borgbase/ansible-role-borgbackup)
* [Unraid](https://unraid.net/community/apps?q=borgmatic#r)
* [uv tool install](https://docs.astral.sh/uv/)
@@ -150,6 +156,9 @@ added new options since you originally created your configuration file. Also
check out how to [upgrade your
configuration](https://torsion.org/borgmatic/how-to/upgrade/#upgrading-your-configuration).
And see the documentation on [patterns and
excludes](https://torsion.org/borgmatic/reference/configuration/patterns-and-excludes/).
### Encryption
@@ -205,15 +214,20 @@ edits are valid.
Before you can create backups with borgmatic, you first need to create a Borg
repository so you have a destination for your backup archives. (But skip this
step if you already have a Borg repository.) To create a repository, run a
command like the following with Borg 1.x:
step if you already have a Borg repository.) To create a repository, assuming
it's already [specified in borgmatic's
configuration](https://torsion.org/borgmatic/reference/configuration/repositories/),
run a command like the following with Borg 1.x:
```bash
sudo borgmatic init --encryption repokey
sudo borgmatic repo-create --encryption repokey
```
<span class="minilink minilink-addedin">New in borgmatic version 1.9.0</span>
Or, with Borg 2.x:
<span class="minilink minilink-addedin">Prior to borgmatic version 1.9.0</span>
The `repo-create` action was called `init`.
<span class="minilink minilink-addedin">With Borg version 2.x</span> Borg 2.x
uses more specific encryption modes like `repokey-aes-ocb`. For example:
```bash
sudo borgmatic repo-create --encryption repokey-aes-ocb
@@ -341,48 +355,18 @@ Access](https://projects.torsion.org/borgmatic-collective/borgmatic/issues/293).
## Niceties
<a id="shell-completion"></a>
<a id="bash"></a>
<a id="fish"></a>
### Shell completion
borgmatic includes a shell completion script (currently only for Bash and Fish) to
support tab-completing borgmatic command-line actions and flags. Depending on
how you installed borgmatic, this may be enabled by default.
### Command-line autocompletion
#### Bash
borgmatic includes autocompletion scripts for various command-line shells to
support tab-completing command-line actions and flags. See the [autocompletion
documentation](https://torsion.org/borgmatic/reference/command-line/autocompletion/)
for details.
If completions aren't enabled, start by installing the `bash-completion` Linux package or the
[`bash-completion@2`](https://formulae.brew.sh/formula/bash-completion@2)
macOS Homebrew formula. Then, install the shell completion script globally:
```bash
sudo su -c "borgmatic --bash-completion > $(pkg-config --variable=completionsdir bash-completion)/borgmatic"
```
If you don't have `pkg-config` installed, you can try the following path
instead:
```bash
sudo su -c "borgmatic --bash-completion > /usr/share/bash-completion/completions/borgmatic"
```
Or, if you'd like to install the script for only the current user:
```bash
mkdir --parents ~/.local/share/bash-completion/completions
borgmatic --bash-completion > ~/.local/share/bash-completion/completions/borgmatic
```
Finally, restart your shell (`exit` and open a new shell) so the completions
take effect.
#### fish
To add completions for fish, install the completions file globally:
```fish
borgmatic --fish-completion | sudo tee /usr/share/fish/vendor_completions.d/borgmatic.fish
source /usr/share/fish/vendor_completions.d/borgmatic.fish
```
### Colored output
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to snapshot your filesystems
title: 📸 How to snapshot your filesystems
eleventyNavigation:
key: 📸 Snapshot your filesystems
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: How to upgrade borgmatic and Borg
title: 📦 How to upgrade borgmatic and Borg
eleventyNavigation:
key: 📦 Upgrade borgmatic/Borg
parent: How-to guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Actions
title: 🎬 Actions
eleventyNavigation:
key: 🎬 Actions
parent: 💻 Command-line
@@ -0,0 +1,36 @@
---
title: bash
eleventyNavigation:
key: bash
parent: ➡️ Autocompletion
---
borgmatic includes an autocompletion script for
[Bash](https://www.gnu.org/software/bash/) to support tab-completing
command-line actions and flags. Depending on how you installed borgmatic, this
may be enabled by default.
If completions aren't enabled, start by installing the `bash-completion` Linux
package or the
[`bash-completion@2`](https://formulae.brew.sh/formula/bash-completion@2) macOS
Homebrew formula. Then, install the shell completion script globally:
```bash
sudo su -c "borgmatic --bash-completion > $(pkg-config --variable=completionsdir bash-completion)/borgmatic"
```
If you don't have `pkg-config` installed, you can try the following path
instead:
```bash
sudo su -c "borgmatic --bash-completion > /usr/share/bash-completion/completions/borgmatic"
```
Or, if you'd like to install the script for only the current user:
```bash
mkdir --parents ~/.local/share/bash-completion/completions
borgmatic --bash-completion > ~/.local/share/bash-completion/completions/borgmatic
```
Finally, restart your shell (`exit` and open a new shell) so the completions
take effect.
@@ -0,0 +1,17 @@
---
title: fish
eleventyNavigation:
key: fish
parent: ➡️ Autocompletion
---
borgmatic includes an autocompletion script for [fish
shell](https://fishshell.com/) to support tab-completing command-line actions
and flags. Depending on how you installed borgmatic, this may be enabled by
default.
If completions aren't enabled, install the completions file globally:
```shell
borgmatic --fish-completion | sudo tee /usr/share/fish/vendor_completions.d/borgmatic.fish
source /usr/share/fish/vendor_completions.d/borgmatic.fish
```
@@ -0,0 +1,13 @@
---
title: ➡️ Autocompletion
eleventyNavigation:
key: ➡️ Autocompletion
parent: 💻 Command-line
---
borgmatic includes autocompletion scripts for various command-line shells to
support tab-completing command-line actions and flags. Depending on how you
installed borgmatic, this may be enabled by default. Here are the supported
shells and how to configure borgmatic autocompletion for each:
{{ collections.all | eleventyNavigation: "➡️ Autocompletion" | eleventyNavigationToHtml | replace: 'href="/reference/', 'href="/borgmatic/reference/' }}
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Command-line
title: 💻 Command-line
eleventyNavigation:
key: 💻 Command-line
parent: Reference guides
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Logging
title: 🪵 Logging
eleventyNavigation:
key: 🪵 Logging
parent: 💻 Command-line
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Overrides
title: 🔄 Overrides
eleventyNavigation:
key: 🔄 Overrides
parent: 💻 Command-line
@@ -1,5 +1,5 @@
---
title: Archive name format
title: 📛 Archive name format
eleventyNavigation:
key: 📛 Archive name format
parent: ⚙️ Configuration
@@ -1,5 +1,5 @@
---
title: Command hooks
title: 🪝 Command hooks
eleventyNavigation:
key: 🪝 Command hooks
parent: ⚙️ Configuration
@@ -1,5 +1,5 @@
---
title: Consistency checks
title: Consistency checks
eleventyNavigation:
key: ✅ Consistency checks
parent: ⚙️ Configuration
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Constants
title: 🟰 Constants
eleventyNavigation:
key: 🟰 Constants
parent: ⚙️ Configuration
@@ -5,11 +5,14 @@ eleventyNavigation:
parent: 🔒 Credentials
---
<span class="minilink minilink-addedin">New in version 1.9.11</span> When
running inside a container, borgmatic can read [Docker
secrets](https://docs.docker.com/compose/how-tos/use-secrets/) and [Podman
running inside a container, borgmatic can read [<span
data-pagefind-weight="0.5">Docker</span>
secrets](https://docs.docker.com/compose/how-tos/use-secrets/) and [<span
data-pagefind-weight="0.5">Podman</span>
secrets](https://www.redhat.com/en/blog/new-podman-secrets-command). Creating
those secrets and passing them into your borgmatic container is outside the
scope of this documentation, but here's a simple example of that with [Docker
scope of this documentation, but here's a simple example of that with [<span
data-pagefind-weight="0.5">Docker</span>
Compose](https://docs.docker.com/compose/):
```yaml
@@ -25,9 +28,9 @@ secrets:
```
This assumes there's a file on the host at `/etc/borgmatic/passphrase.txt`
containing your passphrase. Docker or Podman mounts the contents of that file
into a secret named `borgmatic_passphrase` in the borgmatic container at
`/run/secrets/`.
containing your passphrase. <span data-pagefind-weight="0.5">Docker or
Podman</span> mounts the contents of that file into a secret named
`borgmatic_passphrase` in the borgmatic container at `/run/secrets/`.
Once your container secret is in place, you can consume it within your borgmatic
configuration file:
@@ -1,5 +1,5 @@
---
title: Credentials
title: 🔒 Credentials
eleventyNavigation:
key: 🔒 Credentials
parent: ⚙️ Configuration
@@ -42,6 +42,9 @@ postgresql_databases:
password: "{credential keepassxc /etc/keys.kdbx database}"
```
### Custom command
You can also optionally override the `keepassxc-cli` command that borgmatic calls to load
passwords:
@@ -49,3 +52,49 @@ passwords:
keepassxc:
keepassxc_cli_command: /usr/local/bin/keepassxc-cli
```
Another example:
```yaml
keepassxc:
keepassxc_cli_command: docker exec keepassxc keepassxc-cli
```
### Key file
<span class="minilink minilink-addedin">New in version 2.0.0</span>KeePassXC
supports unlocking a database with a separate [key
file](https://keepassxc.org/docs/#faq-keyfile-howto) instead of or in addition
to a password. To configure borgmatic for that, use the `key_file` option:
```yaml
keepassxc:
key_file: /path/to/keyfile
```
<span class="minilink minilink-addedin">New in version 2.0.12</span>By default,
keepassxc-cli prompts the user for the password to unlock a database. But if you
only want to provide a key file to unlock your database and not a password, for
instance to support unattended backups, use the `ask_for_password` option:
```yaml
keepassxc:
ask_for_password: false
key_file: /path/to/keyfile
```
### YubiKey
<span class="minilink minilink-addedin">New in version 2.0.0</span>KeePassXC
also supports unlocking a database with the help of [a
YubiKey](https://keepassxc.org/docs/#faq-yubikey-2fa). To configure borgmatic
for that, use the `yubikey` option:
```yaml
keepassxc:
yubikey: 1:7370001
```
The value here is the YubiKey slot number (e.g., `1` or `2`) and optional serial
number (e.g., `7370001`) used to access the KeePassXC database. Join the two
values with a colon, but omit the colon if you're leaving out the serial number.
@@ -76,6 +76,14 @@ next, the [Borg file
cache](https://borgbackup.readthedocs.io/en/stable/internals/data-structures.html#cache)
will never get cache hits on snapshotted files. This makes backing up Btrfs
snapshots a little slower than non-snapshotted files that have consistent paths.
**It is also not possible to mitigate cache misses**, as the Btrfs hook uses
snapshot paths which change between borgmatic invocations, and the snapshots
are located outside the [runtime
directory](https://torsion.org/borgmatic/reference/configuration/runtime-directory/),
contrary to
[ZFS](https://torsion.org/borgmatic/reference/configuration/data-sources/zfs/#performance)
and
[LVM](https://torsion.org/borgmatic/reference/configuration/data-sources/lvm/#performance).
<span class="minilink minilink-addedin">With Borg version 2.x</span> Even
snapshotted files should get cache hits, because Borg 2.x is smarter about how
@@ -1,5 +1,5 @@
---
title: Data sources
title: 🗄️ Data sources
eleventyNavigation:
key: 🗄️ Data sources
parent: ⚙️ Configuration
@@ -91,7 +91,7 @@ cache](https://borgbackup.readthedocs.io/en/stable/internals/data-structures.htm
may not get cache hits on snapshotted files. This makes backing up ZFS snapshots
a little slower than non-snapshotted files that have consistent paths. You can
mitigate this by setting a fixed [runtime
directory](https://torsion.org/borgmatic/reference/configuration/runtime-directory/))
directory](https://torsion.org/borgmatic/reference/configuration/runtime-directory/)
(that's not located in `/tmp`). This allows borgmatic to use a consistent
snapshot path from one run to the next, thereby resulting in Borg files cache
hits.
@@ -1,5 +1,5 @@
---
title: Environment variables
title: 💲 Environment variables
eleventyNavigation:
key: 💲 Environment variables
parent: ⚙️ Configuration
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Includes
title: ❗ Includes
eleventyNavigation:
key: ❗ Includes
parent: ⚙️ Configuration
+10 -5
View File
@@ -1,14 +1,14 @@
---
title: Configuration
title: ⚙️ Configuration
eleventyNavigation:
key: ⚙️ Configuration
parent: Reference guides
order: 0
---
Below is a sample borgmatic configuration file including all available options
for the [most recent version of
Below is a sample borgmatic configuration snippet for every available option in
the [most recent version of
borgmatic](https://projects.torsion.org/borgmatic-collective/borgmatic/releases).
This file is also [available for
A full example configuration file is also [available for
download](https://torsion.org/borgmatic/reference/config.yaml).
If you're using an older version of borgmatic, some of these options may not
@@ -16,6 +16,11 @@ work, and you should instead [generate a sample configuration file specific to
your borgmatic
version](https://torsion.org/borgmatic/how-to/set-up-backups/#configuration).
<span data-pagefind-weight="6.0">
{% for option_name in option_names %}
### {{ option_name }} option
```yaml
{% include borgmatic/config.yaml %}
{% include borgmatic/{{ option_name }}.yaml %}
```
{% endfor %}
</span>
@@ -1,5 +1,5 @@
---
title: Monitoring
title: 🚨 Monitoring
eleventyNavigation:
key: 🚨 Monitoring
parent: ⚙️ Configuration
@@ -26,6 +26,9 @@ sentry:
The `monitor_slug` value comes from the "Monitor Slug" under "Cron Details" on
the same Sentry monitor page.
The `environment` value optionally specifies the environment that is used in
Sentry.
With this configuration, borgmatic pings Sentry whenever borgmatic starts,
finishes, or fails, but only when any of the `create`, `prune`, `compact`, or
`check` actions are run. You can optionally override the start/finish/fail
@@ -36,6 +39,7 @@ Sentry on failure:
sentry:
data_source_name_url: https://5f80ec@o294220.ingest.us.sentry.io/203069
monitor_slug: mymonitor
environment: myenvironment
states:
- fail
```
@@ -0,0 +1,159 @@
---
title: ⛔ Patterns and excludes
eleventyNavigation:
key: ⛔ Patterns and excludes
parent: ⚙️ Configuration
---
borgmatic's configuration has multiple options for specifying the source files
to include in your backups. Which of these options you use depends on how
complex your file matching needs are.
## Source directories
The `source_directories` option is the simplest way to specify the files and
directories to include in your backups. Globs (`*`) allow you to match multiple
paths at once, and tildes (`~`) get expanded to the current user's home
directory. Here's an example:
```yaml
source_directories:
- /home
- /etc
- /var/log/syslog*
- /home/user/path with spaces
- ~/.config
```
## Excludes
The `exclude_patterns` options lists particular paths to exclude from your
backups, paths that would otherwise get included by `source_directories`. Globs
and tildes are also supported here. Use quotes as needed. For example:
```yaml
exclude_patterns:
- '*.pyc'
- '/home/*/.cache'
- '*/.vim*.tmp'
- /etc/ssl
- /home/user/path with spaces
```
See the [Borg patterns
documentation](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-patterns)
for more details about the specific "fnmatch"-style syntax used by excludes.
The `exclude_from` option is similar to `exclude_patterns`, but with your patterns
listed in an external file instead of directly within borgmatic's configuration.
Here's an example:
```yaml
exclude_from:
- /etc/borgmatic/excludes
```
Also see the [borgmatic configuration
reference](https://torsion.org/borgmatic/reference/configuration/) for
additional exclude-related options.
## Patterns
When you have more complex needs for including and excluding files to backup,
the `patterns` option is available. The definitive documentation on patterns is
the [Borg patterns
documentation](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-patterns),
but here are the basics.
First, unless you have `source_directories`, you need root patterns. In fact,
root patterns are just another way to specify the same paths as in
`source_directories`; they tell Borg the starting point for recursing into
directories to find files to backup. Root patterns are prefixed with "`R `". For
instance:
```yaml
patterns:
- R /home
- R /etc
```
You can also add excludes and includes to your patterns. Excludes are prefixed
with "`- `". If you're defining these directly in borgmatic's configuration
file, use quotes around the pattern. For example:
```yaml
patterns:
- R /home
- '- /home/user/.cache'
- R /etc
```
How this works is that when Borg discovers a particular file path as it's
recursing into root directories, it tries to match that path against any
excludes and includes in your patterns *in order*, one at a time. If the first
match is to an exclude, then Borg excludes the file from the backup. But if the
first match is to an include, Borg includes it—even if there's a subsequent
exclude.
The respective order of root patterns vs. exclude and include patterns doesn't
matter to Borg, so organize root patterns how you like.
Here's an example of an include, which is prefixed with "`+ `":
```yaml
patterns:
- R /home
- '+ /home/user/.cache/keep-me'
- '- /home/user/.cache'
- R /etc
```
This example excludes all of the `.cache` directory—except for the `keep-me`
subdirectory, which gets included since it's listed first.
There's also a different kind of exclude pattern—a no-recurse exclude. That's
prefixed with "`! `" and tells Borg to not only exclude any matching paths but
also to ignore any subdirectories, saving file processing time. Here's an
example:
```yaml
patterns:
- R /home
- '! /home/user/.cache'
- R /etc
```
The `patterns_from` option is similar to `patterns`, but with your patterns
listed in an external file instead of directly within borgmatic's configuration.
Here's an example:
```yaml
patterns_from:
- /etc/borgmatic/patterns
```
## Debugging
Under the hood, borgmatic actually converts `source_directories`,
`exclude_patterns`, and `exclude_from` values to Borg patterns and merges them
with any `patterns` and `patterns_from` values you've configured—passing the
resulting processed patterns to Borg.
To see the combined patterns that borgmatic passes to Borg, run borgmatic with
[`--verbosity 2`](https://torsion.org/borgmatic/reference/command-line/logging/)
(and optionally `--dry-run`) and look for "`Writing patterns to ...`" in the
output. For instance:
```
repo: Writing patterns to /tmp/borgmatic-xzwb6s07/borgmatic/tmp61shymp0:
R /tmp/borgmatic-xzwb6s07/./borgmatic/sqlite_databases
+ /tmp/borgmatic-xzwb6s07/./borgmatic/sqlite_databases
R /home
R /etc
! fm:/home/user/.cache
```
You'll notice that borgmatic prepends your patterns with its own to support
use cases like streaming database dumps to Borg, creating filesystem snapshots,
saving bootstrap metadata, and so on.
@@ -0,0 +1,138 @@
---
title: 🗃️ Repositories
eleventyNavigation:
key: 🗃️ Repositories
parent: ⚙️ Configuration
---
Borg repositories are where your backups get stored. You can define them in
borgmatic's configuration via the `repositories` option, something like:
```yaml
repositories:
- path: /path/to/first.borg
label: first
- path: /path/to/second.borg
label: second
```
Each repository has a `path` and an optional `label`. The [Borg repository URLs
documentation](https://borgbackup.readthedocs.io/en/stable/usage/general.html#repository-urls)
has examples of valid repositories paths, but see below for some
borgmatic-specific examples.
The `label` shows up in [logged
messages](https://torsion.org/borgmatic/reference/command-line/logging/) about
the repository and also serves as a way to refer to the repository via
the `--repository` flag on the command-line for supported
[actions](https://torsion.org/borgmatic/reference/command-line/actions/).
When you run borgmatic's [`create`
action](https://torsion.org/borgmatic/reference/command-line/actions/create/),
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](https://torsion.org/borgmatic/reference/configuration/patterns-and-excludes/).
## SSH
Backing up to a remote server via
[SSH](https://en.wikipedia.org/wiki/Secure_Shell) looks like:
```yaml
repositories:
- path: ssh://user@host:port/./absolute/path/to/repo
```
Or relative to the remote user's home directory:
```yaml
repositories:
- path: ssh://user@host:port/~/relative/path/to/repo
```
This assumes that you've already configured SSH access (e.g. public keys, known
hosts, authorized hosts, etc.) outside of borgmatic and that Borg is installed
on the server.
<span class="minilink minilink-addedin">With Borg version 2.x</span>The SSH
syntax is a little different:
```yaml
repositories:
- path: ssh://user@host:port//absolute/path/to/repo
```
Or relative to the remote user's home directory:
```yaml
repositories:
- path: ssh://user@host:port/relative/path/to/repo
```
Also see the [`ssh_command` configuration
option](https://torsion.org/borgmatic/reference/configuration/) for overriding
the path to the SSH binary or passing it custom flags. For example:
```yaml
ssh_command: ssh -i /path/to/private/key
```
### SFTP
[SFTP](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) repositories
work just like SSH repositories, but with `sftp://` substituted for `ssh://`.
## Rclone
<span class="minilink minilink-addedin">New in Borg version 2.x</span> If you're
using Borg 2, you can backup to repositories via [Rclone](https://rclone.org/),
which supports a large number of [cloud
providers](https://rclone.org/#providers). This means that Borg, via Rclone,
backs up directly to a cloud provider without having to create an intermediate
repository.
The borgmatic configuration for Rclone looks like:
```yaml
repositories:
- path: rclone:remote:path
```
Note the lack of "`//`" after `rclone:`.
This configuration assumes that you've already [configured a corresponding
Rclone remote](https://rclone.org/docs/).
## S3 / B2
<span class="minilink minilink-addedin">New in Borg version 2.x</span> Borg 2
supports storing repositories directly on [Amazon
S3](https://aws.amazon.com/s3/), [Backblaze
B2](https://www.backblaze.com/cloud-storage), or an S3-alike service, even
without the use of Rclone or an intermediate repository. The configuration for
that might look like one of the following:
```yaml
repositories:
- path: s3:access_key_id:access_key_secret@/bucket/path
- path: b2:access_key_id:access_key_secret@schema://hostname:port/bucket/path
```
Note the lack of "`//`" after `s3:` or `b2:`.
When selecting your cloud hosting provider, be aware that Amazon in particular
has [financially
supported](https://en.wikipedia.org/wiki/White_House_State_Ballroom) the Trump
regime.
## Related documentation
* [How to make backups redundant](https://torsion.org/borgmatic/how-to/make-backups-redundant/)
* [How to provide your passwords](https://torsion.org/borgmatic/how-to/provide-your-passwords/)
@@ -1,5 +1,5 @@
---
title: Runtime directory
title: 📁 Runtime directory
eleventyNavigation:
key: 📁 Runtime directory
parent: ⚙️ Configuration
+1 -1
View File
@@ -1,5 +1,5 @@
---
title: Source code
title: 🐍 Source code
eleventyNavigation:
key: 🐍 Source code
parent: Reference guides
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "borgmatic"
version = "2.0.10"
version = "2.0.12"
authors = [
{ name="Dan Helfman", email="witten@torsion.org" },
]
+13 -4
View File
@@ -2,10 +2,19 @@
set -e
docs_container_id=$(podman create "$IMAGE_NAME")
podman cp $docs_container_id:/usr/share/nginx/html - > borgmatic-docs-dump.tar
USER_PODMAN_SOCKET_PATH=/run/user/$UID/podman/podman.sock
if [ -e "$USER_PODMAN_SOCKET_PATH" ]; then
export DOCKER_HOST="unix://$USER_PODMAN_SOCKET_PATH"
export CONTAINER_SOCKET_PATH="$USER_PODMAN_SOCKET_PATH"
fi
podman build --tag borgmatic-release-docs --file docs/Dockerfile .
docs_container_id=$(podman create borgmatic-release-docs)
podman cp "$docs_container_id":/usr/share/nginx/html - > borgmatic-docs-dump.tar
tar xf borgmatic-docs-dump.tar
rm borgmatic-docs-dump.tar
mv html borgmatic-docs
tar cfz borgmatic-docs.tar.gz borgmatic-docs
podman rm --volumes $docs_container_id
rm -f dist/borgmatic-docs.tar.gz
tar cfz dist/borgmatic-docs.tar.gz borgmatic-docs
podman rm --volumes "$docs_container_id"
+25 -3
View File
@@ -34,18 +34,40 @@ git push github $version
# Build borgmatic and publish to pypi.
rm -fr dist
uv build
twine upload -r pypi --username __token__ dist/borgmatic-*.tar.gz
twine upload -r pypi --username __token__ dist/borgmatic-*-py3-none-any.whl
tarball_path=$(ls dist/borgmatic-*.tar.gz)
wheel_path=$(ls dist/borgmatic-*-py3-none-any.whl)
twine upload -r pypi --username __token__ "$tarball_path"
twine upload -r pypi --username __token__ "$wheel_path"
# Build docs and extract HTML.
scripts/export-docs-from-image
docs_path=dist/borgmatic-docs.tar.gz
# Set release changelogs on projects.torsion.org and GitHub.
release_changelog="$(cat NEWS | sed '/^$/q' | grep -v '^\S')"
escaped_release_changelog="$(echo "$release_changelog" | sed -z 's/\n/\\n/g' | sed -z 's/\"/\\"/g')"
curl --silent --request POST \
release_id=$(curl --silent --request POST \
"https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/releases" \
--header "Authorization: token $projects_token" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data "{\"body\": \"$escaped_release_changelog\", \"draft\": false, \"name\": \"borgmatic $version\", \"prerelease\": false, \"tag_name\": \"$version\"}"
| jq ".id")
curl --silent --request POST \
"https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/releases/$release_id/assets?name=$(basename $wheel_path)" \
--header "Authorization: token $projects_token" \
--header "Accept: application/json" \
--form attachment=@"$wheel_path"
curl --silent --request POST \
"https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/releases/$release_id/assets?name=$(basename $tarball_path)" \
--header "Authorization: token $projects_token" \
--header "Accept: application/json" \
--form attachment=@"$tarball_path"
curl --silent --request POST \
"https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/releases/$release_id/assets?name=$(basename $docs_path)" \
--header "Authorization: token $projects_token" \
--header "Accept: application/json" \
--form attachment=@"$docs_path"
github-release create --token="$github_token" --owner=witten --repo=borgmatic --tag="$version" --target_commit="main" \
--name="borgmatic $version" --body="$release_changelog"
+1 -1
View File
@@ -25,5 +25,5 @@ export PATH="/root/.local/bin:$PATH"
uv tool install tox --with tox-uv
export COVERAGE_FILE=/tmp/.coverage
tox --workdir /tmp/.tox -e py39,py313 --sitepackages
tox --workdir /tmp/.tox -e py311,py314 --sitepackages
tox --workdir /tmp/.tox --sitepackages -e end-to-end
+19 -18
View File
@@ -1,26 +1,27 @@
apprise==1.9.3
attrs==25.3.0
certifi==2025.7.14
charset-normalizer==3.4.2
apprise==1.9.5
attrs==25.4.0
certifi==2025.10.5
charset-normalizer==3.4.4
click>=8.1.8
codespell==2.4.1
coverage==7.9.2
coverage==7.11.0
flexmock==0.12.2
idna==3.10
iniconfig==2.1.0
jsonschema==4.24.1
jsonschema-specifications==2025.4.1
Markdown==3.8.2
idna==3.11
iniconfig==2.3.0
jsonschema==4.25.1
jsonschema-specifications==2025.9.1
markdown==3.9
oauthlib==3.3.1
packaging==25.0
pluggy==1.6.0
Pygments==2.19.2
pytest==8.4.1
pytest-cov==6.2.1
PyYAML>5.0.0
referencing==0.36.2
requests==2.32.4
pygments==2.19.2
pytest==8.4.2
pytest-cov==7.0.0
pyyaml>5.0.0
referencing==0.37.0
requests==2.32.5
requests-oauthlib==2.0.0
rpds-py==0.26.0
ruamel.yaml>0.15.0
rpds-py==0.28.0
ruamel-yaml>0.15.0
typing-extensions==4.15.0
urllib3==2.5.0
+11 -3
View File
@@ -512,6 +512,12 @@ def test_generate_sample_configuration_with_dry_run_does_not_write_file():
def test_generate_sample_configuration_with_split_writes_each_option_to_file():
builtins = flexmock(sys.modules['builtins'])
builtins.should_receive('open').with_args('schema.yaml', encoding='utf-8').and_return('')
builtins.should_receive('open').with_args(
'dest/options.json', 'w', encoding='utf-8'
).and_return(flexmock())
flexmock(module.json).should_receive('dump').with_args(
{'option_names': ['foo', 'bar']}, object
).once()
flexmock(module.ruamel.yaml).should_receive('YAML').and_return(
flexmock(load=lambda filename: {})
)
@@ -520,17 +526,18 @@ def test_generate_sample_configuration_with_split_writes_each_option_to_file():
{'foo': 1, 'bar': 2}
)
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module).should_receive('get_configuration_subset')
flexmock(module).should_receive('render_configuration')
flexmock(module).should_receive('transform_optional_configuration')
flexmock(module).should_receive('transform_optional_configuration').and_return(' ')
flexmock(module.os).should_receive('makedirs')
flexmock(module).should_receive('write_configuration').with_args(
'dest/foo.yaml',
None,
'',
overwrite=False,
).once()
flexmock(module).should_receive('write_configuration').with_args(
'dest/bar.yaml',
None,
'',
overwrite=False,
).once()
@@ -549,6 +556,7 @@ def test_generate_sample_configuration_with_split_and_file_destination_errors():
)
flexmock(module.os.path).should_receive('exists').and_return(True)
flexmock(module.os.path).should_receive('isdir').and_return(False)
flexmock(module).should_receive('get_configuration_subset').never()
flexmock(module).should_receive('render_configuration').never()
flexmock(module).should_receive('transform_optional_configuration').never()
flexmock(module.os).should_receive('makedirs').never()
@@ -5,11 +5,22 @@ from borgmatic.hooks.data_source import btrfs as module
def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
patterns = [Pattern('/foo'), Pattern('/mnt/subvol1'), Pattern('/mnt/subvol2')]
patterns = [
Pattern('/foo'),
Pattern('/mnt/subvol1'),
Pattern('/mnt/subvol1/.cache', Pattern_type.EXCLUDE),
Pattern('/mnt/subvol2'),
]
config = {'btrfs': {}}
flexmock(module).should_receive('get_subvolumes').and_return(
(
module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
module.Subvolume(
'/mnt/subvol1',
contained_patterns=(
Pattern('/mnt/subvol1'),
Pattern('/mnt/subvol1/.cache', Pattern_type.EXCLUDE),
),
),
module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
),
)
@@ -50,6 +61,7 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
),
Pattern('/foo'),
Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1'),
Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1/.cache', Pattern_type.EXCLUDE),
Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', Pattern_type.INCLUDE),
Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2'),
Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', Pattern_type.INCLUDE),
@@ -6,13 +6,20 @@ from borgmatic.hooks.data_source import lvm as module
def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
config = {'lvm': {}}
patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
patterns = [
Pattern('/mnt/lvolume1/subdir'),
Pattern('/mnt/lvolume1/subdir/.cache', Pattern_type.EXCLUDE),
Pattern('/mnt/lvolume2'),
]
logical_volumes = (
module.Logical_volume(
name='lvolume1',
device_path='/dev/lvolume1',
mount_point='/mnt/lvolume1',
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
contained_patterns=(
Pattern('/mnt/lvolume1/subdir'),
Pattern('/mnt/lvolume1/subdir/.cache', Pattern_type.EXCLUDE),
),
),
module.Logical_volume(
name='lvolume2',
@@ -75,6 +82,9 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
assert patterns == [
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir/.cache', Pattern_type.EXCLUDE
),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir', Pattern_type.INCLUDE),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2', Pattern_type.INCLUDE),
-1
View File
@@ -5,7 +5,6 @@ from borgmatic.actions import borg as module
def test_run_borg_does_not_raise():
flexmock(module.logger).answer = lambda message: None
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
flexmock(),
)
-1
View File
@@ -5,7 +5,6 @@ from borgmatic.actions import break_lock as module
def test_run_break_lock_does_not_raise():
flexmock(module.logger).answer = lambda message: None
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
flexmock(module.borgmatic.borg.break_lock).should_receive('break_lock')
break_lock_arguments = flexmock(repository=flexmock())
@@ -5,7 +5,6 @@ from borgmatic.actions import change_passphrase as module
def test_run_change_passphrase_does_not_raise():
flexmock(module.logger).answer = lambda message: None
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
flexmock(module.borgmatic.borg.change_passphrase).should_receive('change_passphrase')
change_passphrase_arguments = flexmock(repository=flexmock())

Some files were not shown because too many files have changed in this diff Show More