diff --git a/NEWS b/NEWS
index 38f441bf..60fee340 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,12 @@
* #485: When running commands (database clients, command hooks, etc.), elevate stderr output to
show up as borgmatic error logs.
* #858: With the "--log-json" flag, log borgmatic's own logs as JSON, not just Borg's.
+ * #1092: BREAKING: Treat most Borg warnings as errors by default, so for instance backups now fail
+ when source directories are missing. You can still override this behavior with the
+ "borg_exit_codes" option. See the documentation for more information:
+ https://torsion.org/borgmatic/how-to/customize-warnings-and-errors/
+ * #1092: Deprecate the "source_directories_must_exist" option, as borgmatic now treats "backup file
+ not found" warnings from Borg as errors, which accomplishes the same thing.
* #1132: BREAKING/SECURITY: For the Healthchecks, Apprise, Pagerduty, and Loki monitoring hooks,
disable log sending when not explicitly enabled. This avoids revealing private log information to
third-party services. To send logs anyway, set the monitoring hook's "send_logs" option to
diff --git a/borgmatic/borg/create.py b/borgmatic/borg/create.py
index 84e7d80f..5cd35c7f 100644
--- a/borgmatic/borg/create.py
+++ b/borgmatic/borg/create.py
@@ -180,6 +180,9 @@ def make_base_create_command( # noqa: PLR0912
open pattern file handle).
'''
if config.get('source_directories_must_exist', False):
+ logger.warning(
+ 'The "source_directories_must_exist" option is deprecated and will be removed from a future release; borgmatic now errors on missing files as Borg runs'
+ )
borgmatic.borg.pattern.check_all_root_patterns_exist(patterns)
patterns_file = borgmatic.borg.pattern.write_patterns_file(
diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml
index 1af7cb99..38be8776 100644
--- a/borgmatic/config/schema.yaml
+++ b/borgmatic/config/schema.yaml
@@ -21,11 +21,7 @@ properties:
type: string
description: |
List of source directories and files to back up. Globs and tildes
- are expanded. Do not backslash spaces in path names. Be aware that
- by default, Borg treats missing source directories as warnings
- rather than errors. If you'd like to change that behavior, see
- https://torsion.org/borgmatic/how-to/customize-warnings-and-errors/
- or the "source_directories_must_exist" option.
+ are expanded. Do not backslash spaces in path names.
example:
- /home
- /etc
@@ -34,8 +30,11 @@ properties:
source_directories_must_exist:
type: boolean
description: |
- If true, then source directories (and root pattern paths) must
- exist. If they don't, an error is raised. Defaults to false.
+ Deprecated. Replaced by borgmatic treating Borg's "backup file not
+ found" warning as an error by default. But if
+ "source_directories_must_exist" is true, then source directories
+ (and root pattern paths) must exist before a backup begins. If they
+ don't, borgmatic errors. Defaults to false.
example: true
repositories:
type: array
@@ -468,9 +467,9 @@ properties:
description: |
A list of Borg exit codes that should be elevated to errors or
squashed to warnings as indicated. By default, Borg error exit codes
- (2 to 99) are treated as errors while warning exit codes (1 and
- 100+) are treated as warnings. Exit codes other than 1 and 2 are
- only present in Borg 1.4.0+.
+ (2 to 99) are treated as errors and most warning exit codes (1 and
+ 100+) are treated as errors as well. Exit codes other than 1 and 2
+ are only present in Borg 1.4.0+.
example:
- code: 13
treat_as: warning
diff --git a/borgmatic/execute.py b/borgmatic/execute.py
index 87d141cc..ea3ea467 100644
--- a/borgmatic/execute.py
+++ b/borgmatic/execute.py
@@ -16,6 +16,9 @@ ERROR_OUTPUT_MAX_LINE_COUNT = 25
BORG_ERROR_EXIT_CODE_START = 2
BORG_ERROR_EXIT_CODE_END = 99
+# See https://borgbackup.readthedocs.io/en/stable/internals/frontends.html#message-ids
+BORG_WARNING_EXIT_CODES_TREATED_AS_ERRORS = {101, 102, 104, 105, 106, 107}
+
class Exit_status(enum.Enum):
STILL_RUNNING = 1
@@ -24,7 +27,7 @@ class Exit_status(enum.Enum):
ERROR = 4
-def interpret_exit_code(command, exit_code, borg_local_path=None, borg_exit_codes=None):
+def interpret_exit_code(command, exit_code, borg_local_path=None, borg_exit_codes=None): # noqa: PLR0911
'''
Return an Exit_status value (e.g. SUCCESS, ERROR, or WARNING) based on interpreting the given
exit code. If a Borg local path is given and matches the process' command, then interpret the
@@ -55,8 +58,15 @@ def interpret_exit_code(command, exit_code, borg_local_path=None, borg_exit_code
)
return Exit_status.WARNING
- # If the exit code doesn't have explicit configuration, then fall back to the default Borg
- # behavior.
+ # If the exit code doesn't have explicit configuration, then fall back to the default
+ # behavior of treating Borg errors as errors and some Borg warnings as errors.
+ if exit_code in BORG_WARNING_EXIT_CODES_TREATED_AS_ERRORS:
+ logger.error(
+ f'Treating exit code {exit_code} as an error, as per borgmatic defaults',
+ )
+
+ return Exit_status.ERROR
+
return (
Exit_status.ERROR
if (
diff --git a/docs/how-to/customize-warnings-and-errors.md b/docs/how-to/customize-warnings-and-errors.md
index 3189a385..72edb3c1 100644
--- a/docs/how-to/customize-warnings-and-errors.md
+++ b/docs/how-to/customize-warnings-and-errors.md
@@ -5,40 +5,44 @@ eleventyNavigation:
parent: How-to guides
order: 13
---
-After Borg runs, it indicates whether it succeeded via its exit code, a
-numeric ID indicating success, warning, or error. borgmatic consumes this exit
-code to decide how to respond. Normally, a Borg error results in a borgmatic
-error, while a Borg warning or success doesn't.
+
+After Borg runs, it indicates whether it succeeded via its exit code, a numeric
+ID indicating success, warning, or error. borgmatic consumes this exit code to
+decide how to respond. By default, Borg errors (and some warnings) result
+in a borgmatic error, while Borg successes don't.
+
+New in borgmatic version 2.1.0
+borgmatic elevates most Borg warnings to errors by default. For instance, if a
+source directory is missing during backup, Borg indicates that with a warning
+exit code (`107`). And starting in borgmatic 2.1.0, that exit code is considered
+an error, so you'll actually find out about missing files.
With Borg version 1.4+ If the
default behavior isn't sufficient for your needs, you can customize how
borgmatic interprets [Borg's exit
-codes](https://borgbackup.readthedocs.io/en/stable/usage/general.html#return-codes).
+codes](https://borgbackup.readthedocs.io/en/stable/internals/frontends.html#message-ids).
-For instance, this borgmatic configuration elevates all Borg backup file
-permission warnings (exit code `105`)—and only those warnings—to errors:
+For instance, this borgmatic configuration elevates a Borg warning about source files
+changes during backup (exit code `100`)—and only those warnings—to
+errors:
```yaml
borg_exit_codes:
- - code: 105
+ - code: 100
treat_as: error
```
-The following configuration does that *and* elevates backup file not found
-warnings (exit code `107`) to errors as well:
+The following configuration does that *and* treats Borg's backup file not found
+(exit code `107`) as a warning:
```yaml
borg_exit_codes:
- - code: 105
+ - code: 100
treat_as: error
- code: 107
- treat_as: error
+ treat_as: warning
```
-See the full list of [Borg 1.4 error and warning exit
-codes](https://borgbackup.readthedocs.io/en/stable/internals/frontends.html#message-ids).
-The `rc:` numeric value there tells you the exit code for each.
-
If you don't know the exit code for a particular Borg error or warning you're
experiencing, you can usually find it in your borgmatic output when `--verbosity
2` is enabled. For instance, here's a snippet of that output when a backup file
@@ -50,8 +54,8 @@ is not found:
terminating with warning status, rc 107
```
-So if you want to configure borgmatic to treat this as an error instead of a
-warning, the exit status to use is `107`.
+So if you want to configure borgmatic to treat this as an warning instead of an
+error, the exit status to use is `107`.
With Borg version 1.2 and earlier
Older versions of Borg didn't support granular exit codes, but still
@@ -67,15 +71,4 @@ borg_exit_codes:
Be aware though that Borg exits with a warning code for a variety of benign
situations such as files changing while they're being read, so this example
-may not meet your needs.
-
-Here's another Borg 1.2 example that squashes Borg errors to warnings:
-
-```yaml
-borg_exit_codes:
- - code: 2
- treat_as: warning
-```
-
-Be careful with this example though, because it prevents borgmatic from
-erroring when Borg errors, which may not be desirable.
+may not meet your needs. Upgrading to Borg 1.4+ is recommended.
diff --git a/tests/unit/test_execute.py b/tests/unit/test_execute.py
index 41203794..f0115894 100644
--- a/tests/unit/test_execute.py
+++ b/tests/unit/test_execute.py
@@ -40,7 +40,22 @@ from borgmatic import execute as module
(['borg'], 2, 'borg', [{'code': 99, 'treat_as': 'warning'}], module.Exit_status.ERROR),
(['borg'], 2, 'borg', [{'code': 2, 'treat_as': 'warning'}], module.Exit_status.WARNING),
(['borg'], 100, 'borg', [{'code': 1, 'treat_as': 'error'}], module.Exit_status.WARNING),
+ (['borg'], 100, 'borg', [], module.Exit_status.WARNING),
(['borg'], 100, 'borg', [{'code': 100, 'treat_as': 'error'}], module.Exit_status.ERROR),
+ (['borg'], 101, 'borg', [], module.Exit_status.ERROR),
+ (['borg'], 101, 'borg', [{'code': 101, 'treat_as': 'warning'}], module.Exit_status.WARNING),
+ (['borg'], 102, 'borg', [], module.Exit_status.ERROR),
+ (['borg'], 102, 'borg', [{'code': 102, 'treat_as': 'warning'}], module.Exit_status.WARNING),
+ (['borg'], 103, 'borg', [], module.Exit_status.WARNING),
+ (['borg'], 103, 'borg', [{'code': 103, 'treat_as': 'error'}], module.Exit_status.ERROR),
+ (['borg'], 104, 'borg', [], module.Exit_status.ERROR),
+ (['borg'], 104, 'borg', [{'code': 104, 'treat_as': 'warning'}], module.Exit_status.WARNING),
+ (['borg'], 105, 'borg', [], module.Exit_status.ERROR),
+ (['borg'], 105, 'borg', [{'code': 105, 'treat_as': 'warning'}], module.Exit_status.WARNING),
+ (['borg'], 106, 'borg', [], module.Exit_status.ERROR),
+ (['borg'], 106, 'borg', [{'code': 106, 'treat_as': 'warning'}], module.Exit_status.WARNING),
+ (['borg'], 107, 'borg', [], module.Exit_status.ERROR),
+ (['borg'], 107, 'borg', [{'code': 107, 'treat_as': 'warning'}], module.Exit_status.WARNING),
),
)
def test_interpret_exit_code_respects_exit_code_and_borg_local_path(