Compare commits

...
832 Commits
Author SHA1 Message Date
Dan Helfman bf0d38ff2a Update docs about MySQL/MariaDB database dumps and restores (#228). 2019-11-12 11:59:01 -08:00
Dan Helfman 04e5b42606 Fix repository does not exist error with "borgmatic extract" when repository is remote (#243). 2019-11-12 11:47:24 -08:00
Dan Helfman 30525c43bf Another edge case: Don't error on non-matching restore path globs (#228). 2019-11-12 11:24:31 -08:00
Dan Helfman ebeb5efe05 More accurately detecting restore of unconfigured database (#228). 2019-11-12 11:10:47 -08:00
Dan Helfman a3e939f34b Factor out filtering of database configuration to function with tests (#228). 2019-11-12 10:39:36 -08:00
Dan Helfman 2a771161e7 Finish test coverage for MySQL restore (#228). 2019-11-12 09:50:26 -08:00
Dan Helfman ded042d8cc First crack at MySQL/MariaDB database restore (#228). 2019-11-11 21:59:30 -08:00
Dan Helfman 4ed43ae4dc Indicate in AUTHORS that it's not an exhaustive list. 2019-11-10 14:50:28 -08:00
Dan Helfman 9d29ecf304 Remove MySQL/MariaDB database dumps after backing them up (#228). 2019-11-08 11:53:27 -08:00
Dan Helfman 427b57e2a9 Database dump hooks for MySQL/MariaDB (#228). 2019-11-08 11:17:52 -08:00
Dan Helfman e4f0a336c2 Fix installation path in docs. 2019-11-07 11:09:27 -08:00
Dan Helfman 68459c6795 Add note about setting PATH environment variable when installing borgmatic. 2019-11-07 11:05:41 -08:00
Dan Helfman 17fda7281a Monitor backups with Cronhub hook integration. Fix Healthchecks/Cronitor hooks to respect dry run. 2019-11-07 10:08:44 -08:00
Dan Helfman ac777965d0 Fix regression of generate-borgmatic-config working without --source flag (#239). 2019-11-06 09:52:21 -08:00
Dan Helfman 31d3bc9bd8 In documentation, clarify when Healthchecks/Cronitor hooks fire in relation to other hooks (#238). 2019-11-06 09:44:46 -08:00
Dan Helfman 2115eeb6a2 Upgrade your borgmatic configuration to get new options and comments via "generate-borgmatic-config --source" (#239). 2019-11-06 09:31:00 -08:00
Dan Helfman 08f017bc3e Remove broken Tox Python version filter when running Black (code formatter) environment directly. 2019-11-05 09:36:30 -08:00
Dan Helfman 7bc9482970 Add verbosity level -1 to NEWS. 2019-11-03 17:03:19 -08:00
Dan HelfmanandGitea 57ffad4e04 Verbosity level "-1" for even quieter output: Errors only (#236). 2019-11-04 01:02:13 +00:00
Dan Helfman 5422d14f93 Add Python 3.8. 2019-11-03 14:12:46 -08:00
Dan Helfman e6d8c736d0 Documentation feedback: Don't hard-code Python 3.7 for Black. 2019-11-03 14:08:19 -08:00
palto42 18d3542fbc new verbosity level "-1" for errors 2019-11-03 09:55:19 +01:00
Dan Helfman 93f453cecf Add Python 3.8 to build matrix. 2019-11-02 16:38:50 -07:00
Dan Helfman 505bb778fa Fix logging docs typo. 2019-11-02 12:54:03 -07:00
Dan Helfman b09d464162 Add missing PR link. 2019-11-02 11:27:05 -07:00
Dan Helfman a9104ed090 Handle log file error more consistently with other error. Add --log-file-verbosity flag. Add docs. 2019-11-02 11:23:18 -07:00
Dan HelfmanandGitea 06f134cc71 Log to file instead of syslog via command-line "--log-file" flag (#233). 2019-11-02 17:43:39 +00:00
palto42 584359b6c0 .gitignore 2019-11-02 14:39:16 +01:00
palto42 26a1a3d1e0 test cases for logfile option 2019-11-02 14:34:16 +01:00
palto42 6da05cbe2d Exception handling for logfile option 2019-11-02 14:33:57 +01:00
Dan Helfman f48f52079d Support for Borg --keep-exclude-tags and --exclude-nodump options (#234). 2019-11-01 15:50:59 -07:00
Dan Helfman 76c569cf84 Update test requirements. 2019-11-01 12:18:35 -07:00
palto42 b121290c0f Merge remote-tracking branch 'upstream/master' into logging 2019-11-01 19:44:04 +01:00
Dan Helfman 8fd46b8c70 Monitor backups with Cronitor hook integration. 2019-11-01 11:33:15 -07:00
Dan Helfman 603f525352 Clarify --archive help. 2019-11-01 10:50:20 -07:00
palto42 8c8640d0ab file-logger replaces syslog 2019-11-01 18:42:24 +01:00
Dan Helfman e3dd545345 Extract files to a particular directory via "borgmatic extract --destination" flag. Also rename "--restore-path" to "--path" to reduce confusion. 2019-11-01 10:00:26 -07:00
Dan Helfman 589fc30fc8 To orient the reader, add "borgmatic" to the top of each documentation page. 2019-10-31 22:04:38 -07:00
Dan Helfman bd3c51fc5a Documentation for database restores (#229). 2019-10-31 21:45:47 -07:00
Dan Helfman 2c46f53ef6 Attempt to upgrade version of pip used in CI. 2019-10-31 20:37:01 -07:00
Dan Helfman 939f4d4e3d Add additional tests for database restores. 2019-10-31 14:11:19 -07:00
Dan Helfman 3006db0cae Restore backed up PostgreSQL databases via "borgmatic restore" sub-command (#229). 2019-10-31 12:57:36 -07:00
palto42 22640a9ca0 new option for log-file 2019-10-31 10:44:22 +01:00
Dan Helfman ca23c3b8b3 Remove unnecessary word from documentation. 2019-10-30 10:55:40 -07:00
Dan Helfman 74607fdd43 Documentation on how to develop borgmatic's documentation. 2019-10-30 10:54:42 -07:00
Dan Helfman b53684a8f0 Fix incorrect link to hooks documentation. 2019-10-29 09:53:45 -07:00
Dan Helfman f055f5dea8 Clarify borgmatic restore example with no leading slash. 2019-10-28 12:00:54 -07:00
Dan Helfman 4dc4fe0b8d Reordering hooks a bit so that pre-backup ordering mirrors post-backup ordering. 2019-10-27 14:47:55 -07:00
Dan Helfman 5e3c2da79c Database dump hooks documentation (#225). 2019-10-23 15:35:37 -07:00
Dan Helfman 37dc94bc79 Add test for removal of database dumps. 2019-10-23 13:36:03 -07:00
Dan Helfman fc274b43f0 Rename "borgmatic list --pattern-from" flag to "--patterns-from" to match Borg (#230). 2019-10-22 22:42:36 -07:00
Dan Helfman 9ab12e4312 Tests for database dumping (#225). 2019-10-22 21:39:30 -07:00
Dan Helfman a5ff35c198 Update NEWS with PostgreSQL database dump hook. 2019-10-22 16:31:26 -07:00
Dan Helfman 458e7776c5 Database dump hooks for PostgreSQL, so you can easily dump your databases before backups run (#225). 2019-10-22 16:28:42 -07:00
Dan Helfman fa5fa1c11b Move hooks into directory, so there can be one source file per type of hook (#225). 2019-10-21 15:52:14 -07:00
Dan Helfman f8bc67be8d Config generation support for sequences of maps, needed for database dump hooks (#225). 2019-10-21 15:17:47 -07:00
Dan Helfman 17586d49ac Bump version of tox in CI. 2019-10-21 11:05:37 -07:00
Dan HelfmanandGitea 2f75c9aa9e Bump Tox minimum version. 2019-10-20 21:47:57 +00:00
Luke Murphy 60650ccfc7 Follow latest Tox developments 2019-10-20 12:49:14 +02:00
Dan Helfman c12c47cace Fix "borgmatic list --successful" with a slightly better heuristic for listing successful (non-checkpoint) archives. 2019-10-16 10:24:58 -07:00
Dan Helfman d6aaab8a09 Remove parentheses from docs sentence. 2019-10-15 13:02:54 -07:00
Dan Helfman 128ebf04ce Dead man's switch via healthchecks.io integration (#223) + new monitoring documentation. 2019-10-15 10:49:14 -07:00
Dan Helfman b1941bcce9 Automatically rewrite links to localhost when developing on docs locally. 2019-10-14 13:13:41 -07:00
Dan Helfman 7b3b28616d Add "borgmatic list --successful" flag to only list successful (non-checkpoint) archives (#86). 2019-10-13 15:58:11 -07:00
Dan Helfman f3910f49ca Fix incorrect help on borg list --last flag. 2019-10-13 14:46:28 -07:00
Dan Helfman 59e1cac92c Correct Arch Linux borgmatic package link. 2019-10-11 14:35:07 -07:00
Dan Helfman b1f0287fdb Add documentation link to community AUR (Arch Linux) borgmatic package. 2019-10-11 13:35:57 -07:00
Dan Helfman 99c35d4077 "flags" -> "actions" a few places in the docs. 2019-10-11 10:46:30 -07:00
Dan Helfman 07b9ff61f2 Remove documentation link to the AUR (Arch Linux) borgmatic package, which apparently has been deleted. 2019-10-11 10:42:19 -07:00
Dan Helfman f573c1810a Add a suggestion form to all documentation pages, so users can submit ideas for improving the documentation. 2019-10-10 14:27:48 -07:00
Dan Helfman 1d37b14356 More detailed error alerting via runtime context available in "on_error" hook (#174). 2019-10-01 12:23:16 -07:00
Dan Helfman 6c617eddd5 When backups to one of several repositories fails, keep backing up to the other repositories (#144). 2019-09-30 22:19:31 -07:00
Dan Helfman e14ebee4e0 User-defined hooks for global setup or cleanup that run before/after all actions. (#192). 2019-09-28 16:18:10 -07:00
Dan Helfman a897ffd514 Fix "borgmatic create --progress" output so that it updates on the console in real-time (#221). 2019-09-25 12:03:10 -07:00
Dan Helfman a472735616 Merge sample cron files. 2019-09-24 10:49:46 -07:00
Dan Helfman b3fec03cf4 Up the syslog verbosity in sample cron files. 2019-09-24 10:47:39 -07:00
Dan Helfman 89dccc25c3 Add AC power condition for systemd service (#205). 2019-09-24 10:43:30 -07:00
Dan Helfman 3846155d62 More robust sample systemd service: boot delay, network dependency, lowered CPU/IO priority, etc (#205). 2019-09-24 10:16:30 -07:00
Dan Helfman 386979ebb4 Mention --stats option in documentation. 2019-09-23 13:13:34 -07:00
Dan Helfman 07222cd984 Fix visibility of "borgmatic prune --stats" output (#219). 2019-09-23 13:07:51 -07:00
Dan Helfman cf4c6c274d Upgrade build to Alpine 3.10. 2019-09-23 09:07:17 -07:00
Dan Helfman 340bd72176 Fix regression of argument parsing for default actions (#220). 2019-09-22 11:30:58 -07:00
Dan Helfman 1a1bb71af1 Fix error with "borgmatic check --only" command-line flag with "extract" consistency check (#217). 2019-09-20 11:43:27 -07:00
Dan Helfman ae45dfe63a Clarify command-like help for check --only. 2019-09-19 15:20:05 -07:00
Dan Helfman d6ac7a9192 Upgrade various dependencies. 2019-09-19 13:04:59 -07:00
Dan Helfman d959fdbf8d Document new "check --only" command-line flag. 2019-09-19 11:50:29 -07:00
Dan Helfman 81739791e0 Override configured consistency checks via "borgmatic check --only" command-line flag (#210). 2019-09-19 11:43:53 -07:00
Dan Helfman 4cdff74e9b Support for Borg check --verify-data flag via borgmatic "data" consistency check (#210). 2019-09-18 16:52:27 -07:00
Dan Helfman 11e830bb1d Fix flake8 warning. 2019-09-18 14:11:56 -07:00
Dan Helfman cba00a9c4e Add NEWS entry for generate-borgmatic-config comment change. 2019-09-18 14:06:03 -07:00
Dan HelfmanandGitea f2198de151 Merge branch 'comments-white-space' of polyzen/borgmatic into master 2019-09-18 21:03:56 +00:00
Daniel M. Capella 0c439c0c02 Add space to separate comments from tokens
https://yaml.org/spec/1.2/spec.html#id2780069
2019-09-17 20:00:58 -04:00
Dan Helfman f11a9bb4aa Revert "Fix for spurious Borg traceback when initializing a repository in an empty directory (#201)."
This reverts commit 9585c8f908.
2019-09-14 16:14:20 -07:00
Dan HelfmanandGitea ee6f390910 Merge branch 'point-to-stable-docs' of polyzen/borgmatic into master 2019-09-14 21:53:34 +00:00
Daniel M. Capella 9a5117db14 Consistently point to stable Borg docs 2019-09-14 17:30:28 -04:00
Dan Helfman 9585c8f908 Fix for spurious Borg traceback when initializing a repository in an empty directory (#201). 2019-09-13 13:08:23 -07:00
Dan Helfman 3495484ddd Bump version for release. 2019-09-12 21:35:00 -07:00
Dan Helfman 67ab2acb82 Fix for hook erroring with exit code 1 not being interpreted as an error (#214). 2019-09-12 16:37:43 -07:00
Dan Helfman c085bacccf Reorder arguments passed to Borg to fix duplicate directories when using Borg patterns (#213). 2019-09-12 15:27:04 -07:00
Dan Helfman 896401088e Fix for traceback when the "checks" option has an empty value (#208). 2019-08-26 09:52:32 -07:00
Dan Helfman ef3dda9213 Bypass Borg error about a moved repository (#209). 2019-08-26 09:39:41 -07:00
Dan Helfman c9f5d9b048 In issue template, use python3 instead of python. 2019-08-24 13:08:18 -07:00
Dan Helfman ccbd0b608b Do not treat Borg warnings (exit code 1) as failures (#204). 2019-08-03 15:13:54 -07:00
Dan Helfman a7cc2ea803 When validating configuration files, require strings instead of allowing any scalar type. 2019-08-03 14:52:12 -07:00
Dan Helfman 9ec75ccf3f Fit inadvertent conversion of ordered dict to dict. 2019-07-27 14:15:24 -07:00
Dan Helfman 7c890be76d Black formatting. 2019-07-27 14:08:47 -07:00
Dan Helfman 39e5aac479 If a "prefix" option in borgmatic's configuration has an empty value (blank or ""), then disable default prefix. 2019-07-27 14:04:13 -07:00
Dan Helfman e25f2c4e6c Clarify documentation/schema about on_error hook running if there's an error in another hook (#202). 2019-07-19 09:25:01 -07:00
Dan Helfman 7ad8f9ac6f Link to borgmatic-binary installation method. 2019-07-13 15:40:26 -07:00
Dan Helfman 2add3ff7ad Fix redirect. 2019-07-05 09:19:51 -07:00
Dan Helfman 0602ca1862 Add how-to redirect. Fix capitalization. 2019-07-05 09:03:08 -07:00
Dan Helfman e973802fc1 Iterate on how-to document name wording. 2019-07-05 08:57:25 -07:00
Dan Helfman 2bdf6dfd70 Merge branch 'master' of ssh://projects.torsion.org:3022/witten/borgmatic 2019-07-05 08:52:06 -07:00
Dan HelfmanandGitea f894c49540 Merge branch 'rename_howto_guide' of duncanbetts/borgmatic into master 2019-07-05 15:52:21 +00:00
duncanbetts 7900e5ea53 Update 'README.md' 2019-07-05 14:40:41 +00:00
duncanbetts 5587f48bda Update 'docs/how-to/run-preparation-steps-before-backups.md' 2019-07-05 14:39:21 +00:00
duncanbetts de3ee07566 Update 'README.md'
Improved description of what the resource provides.
2019-07-05 14:37:42 +00:00
Dan Helfman fe39453598 Change example filename to be more descriptive. 2019-06-30 17:23:09 -07:00
Dan Helfman 9c75063c05 Unbreak console snippet in docs. 2019-06-30 17:09:34 -07:00
Dan Helfman 5cf2ef1732 Add note to documentation about using spaces instead of tabs for indentation, as YAML does not allow tabs (#199). 2019-06-30 16:58:01 -07:00
Dan Helfman f35e6ea7ad Upgrade base layers. 2019-06-27 15:38:00 -07:00
Dan Helfman 90595e9c18 Only log to syslog when run from a non-interactive console (e.g. a cron job). Related to #197. 2019-06-27 14:41:21 -07:00
Dan Helfman 032d4adee3 Remove unicode byte order mark from syslog output. (Related to #197.) 2019-06-27 10:03:49 -07:00
Dan Helfman 4444219e17 Support for Borg --noatime, --noctime, and --nobirthtime flags (mentioned in #193). 2019-06-25 11:30:55 -07:00
Dan Helfman 56fd78089d Sort generated flags before passing them to Borg. 2019-06-25 11:04:10 -07:00
Dan Helfman 86dbc00cbe Support for several more borgmatic/borg info command-line flags (#193). 2019-06-25 10:46:55 -07:00
Dan Helfman c644270599 Pass through several "borg list" flags (#193). 2019-06-25 10:18:30 -07:00
Dan Helfman 1676a98c51 Fix for Borg create error output not showing up at borgmatic verbosity level zero (#198). 2019-06-24 09:55:41 -07:00
Dan Helfman 358ed53da0 Only show build status badge for master branch. 2019-06-23 16:53:33 -07:00
Dan Helfman 90925c9428 Provide tips about old-style flags for those on older versions. 2019-06-23 16:42:23 -07:00
Dan Helfman cd192a6909 Bump version for release. 2019-06-23 16:30:16 -07:00
Dan Helfman 7185146481 A bunch of tests for parse_subparser_arguments(). 2019-06-23 16:06:39 -07:00
Dan Helfman c15e6c5fe5 More actions help. 2019-06-23 09:46:22 -07:00
Dan Helfman 79c2b9df06 Don't make major version bump after all. 2019-06-23 09:23:51 -07:00
Dan Helfman acd6772148 Update documentation to refer to dashless action sub-commands. 2019-06-22 22:09:50 -07:00
Dan Helfman cd91dbd4f7 Include sub-command help in documentation. 2019-06-22 22:04:56 -07:00
Dan Helfman 8fc4efff88 Add subcommand note to NEWS. 2019-06-22 21:35:40 -07:00
Dan Helfman 4bf3e906a1 Break out main borgmatic arguments-parsing code into a separate file. 2019-06-22 21:32:27 -07:00
Dan Helfman 0ca43ef67a Get tests passing. 2019-06-22 21:23:48 -07:00
Dan Helfman 603e055a39 Fix borgmatic command unit tests for new parsed arguments. 2019-06-22 16:29:25 -07:00
Dan Helfman 75c04611dc Refactor to support subparsed-based parsed arguments. 2019-06-22 16:10:07 -07:00
Dan Helfman 881dc9b01e Make each subparser get a crack all all arguments. 2019-06-21 23:12:37 -07:00
Dan Helfman 8c72e909a7 Initial stab at subparsers for argument parsing. Not yet fully working. 2019-06-21 22:27:16 -07:00
Dan Helfman 74ac148747 Disable console color via "color" option in borgmatic configuration output section (#191). 2019-06-19 20:48:54 -07:00
Dan Helfman be7887c071 Demote log level of unhelpful info log (#194). 2019-06-19 12:01:03 -07:00
Dan Helfman da459d95b8 Bump version for release. 2019-06-17 12:16:23 -07:00
Dan Helfman b3aa6af859 Don't color syslog output (#197). 2019-06-17 11:53:08 -07:00
Dan Helfman b816af1b13 Undo purge. 2019-06-16 22:10:25 -07:00
Dan Helfman 276aeb9875 Fix tests that assert on default syslog verbosity. 2019-06-16 21:58:41 -07:00
Dan Helfman de94001508 Change default syslog verbosity to show errors only. 2019-06-16 21:57:14 -07:00
Dan Helfman 7cfab3620b Don't prune docs image after push, so watchtower can pick it up. 2019-06-16 21:52:09 -07:00
Dan Helfman 6c136ebbf1 Fix for unclear error message for invalid YAML merge include (#196). 2019-06-16 21:33:40 -07:00
Dan Helfman eaa5eb4174 Note about including config file. 2019-06-15 14:28:32 -07:00
Dan Helfman acc2a39454 Include note about debug output. 2019-06-15 14:27:53 -07:00
Dan Helfman a10c7a8496 Trying out a Gitea issue template for somewhat more structured bug reports. 2019-06-15 14:23:45 -07:00
Dan Helfman de82919e39 Skip coverage on a particular annoying-to-trigger branch in execution code. 2019-06-13 21:38:06 -07:00
Dan Helfman 1ba56d5262 Fix tests in Python 3.6. 2019-06-13 21:34:04 -07:00
Dan Helfman 1c825b5d84 Bump version for release. 2019-06-13 20:50:06 -07:00
Dan Helfman d6d66de251 Set umask used when executing hooks via "umask" option in borgmatic hooks section (#189). 2019-06-13 17:05:26 -07:00
Dan Helfman 76d79f0331 Suppress part of an obnoxious warning about disabling coverage (for end-to-end tests). 2019-06-13 14:15:08 -07:00
Dan Helfman dc43c38e29 Complete test coverage for logging branch. 2019-06-13 11:11:42 -07:00
Dan Helfman 7d7308a80d Integration tests for execute.py. 2019-06-13 10:48:21 -07:00
Dan Helfman b43ef9d76d Move test file to correspond to its code under test. 2019-06-13 10:27:00 -07:00
Dan Helfman 28cdd67743 Error hook test. 2019-06-13 10:14:16 -07:00
Dan Helfman 7f126ce127 Move hook.py file up a level. 2019-06-13 10:09:16 -07:00
Dan Helfman a6c4debf78 Additional test coverage, and upgrade test requirements. 2019-06-13 10:01:55 -07:00
Dan Helfman a74ad5475e Run all Borg commands such that they log to syslog as well as console. 2019-06-12 20:56:20 -07:00
Dan Helfman fa293828df Run hooks such that their output goes to syslog as well as console. 2019-06-12 13:09:04 -07:00
Dan Helfman f5582b1754 Move borgmatic.borg.execute module up a level for broader use. 2019-06-12 12:13:59 -07:00
Dan Helfman 1af95714c2 Collapse two execute_command() parameters into one output log level parameter. 2019-06-12 12:11:36 -07:00
Dan Helfman 0406d18cfd Log Borg --stats output as warning so it shows up at any verbosity level. 2019-06-12 11:49:35 -07:00
Dan Helfman 66e9ec9c3c A few tests for JSON flag suppressing Borg output. 2019-06-12 11:31:46 -07:00
Dan Helfman 899a7c8318 Add some wheel metadata do .gitignore. 2019-06-11 21:40:28 -07:00
Dan Helfman 7c01b69498 Details on where to view logs. 2019-06-11 21:35:43 -07:00
Dan Helfman 4f0d3bf4ed Add docs/default about systemd journald rate limiting. 2019-06-11 17:03:40 -07:00
Dan Helfman 9a5e7a3abb Successfully convert Borg output to Python logging entries. 2019-06-11 16:42:04 -07:00
Dan Helfman 02eb6c7e09 Merge branch 'master' into logging 2019-06-10 10:27:22 -07:00
Dan Helfman 418c09398c Fix incorrect compression default in schema comment. 2019-06-09 21:21:46 -07:00
Dan Helfman cdbd4c55e8 Fix 404 links harder. 2019-06-01 13:23:48 -07:00
Dan Helfman 2374410891 Fix 404 documentation links by switching to absolute links. 2019-06-01 13:02:39 -07:00
Dan Helfman d2c46e91fe Add rsync.net to hosting providers; includ random link rotation. 2019-05-29 15:35:04 -07:00
Dan Helfman 12441331e6 Fix formatting / import ordering. 2019-05-27 15:46:38 -07:00
Dan Helfman 9ceeae2de0 Add separate syslog verbosity flag. 2019-05-27 15:44:48 -07:00
Dan Helfman e0e493c2f1 Factor out configuring of logging into a common function. 2019-05-27 15:05:26 -07:00
Dan Helfman 0f05f7ad93 Log to syslog in addition to existing standard out logging (#53). 2019-05-26 16:34:46 -07:00
Dan Helfman 9bc1b71017 Clarify description in setup.py. 2019-05-26 13:36:53 -07:00
Dan Helfman b3776871b5 Rewrite the borgmatic overview a bit to clarify its place in the world. 2019-05-26 13:35:51 -07:00
Dan Helfman 308cb31bf9 Remove some of the link rewriting to hopefully fix broken docs links. 2019-05-25 21:55:28 -07:00
Dan Helfman e1f4643215 In README, use absolute links to properly rendered documentation (to cut down on confusion). 2019-05-25 21:48:05 -07:00
Dan Helfman bc4fb322b5 Move documenation build step last in CI. Refactor docs build scripts. 2019-05-21 21:33:25 -07:00
Dan Helfman 2c4f192e43 Attempt to build documentation image in CI. 2019-05-21 21:13:35 -07:00
Dan Helfman fb7a6dccaa Link to docs on skipping pruning entirely. 2019-05-21 12:32:19 -07:00
Dan HelfmanandGitea 2826b7bd7c Add files for building documentation into a Docker image for web serving. 2019-05-21 03:16:32 +00:00
Dan Helfman 932848f6c1 Merge branch 'master' into docs-image. 2019-05-20 19:23:12 -07:00
Dan HelfmanandGitea 9255940c6b Upgrade Drone build file format from 0.8 to 1.x. 2019-05-21 02:21:46 +00:00
Dan Helfman 3eadd16856 Add build server upgrade to NEWS. 2019-05-20 19:20:05 -07:00
Dan Helfman 61f46c5ad5 Try without explicit "sh". 2019-05-20 19:06:13 -07:00
Dan Helfman aad47d1741 bash -> sh. 2019-05-20 18:19:42 -07:00
Dan Helfman 079dd3fe4c Another try. 2019-05-20 18:18:30 -07:00
Dan Helfman d47f1bff4d Try to run script. 2019-05-20 18:17:25 -07:00
Dan Helfman 53967f6324 Trigger build. 2019-05-20 17:48:39 -07:00
Dan Helfman f5a70dc2a5 Drone + jsonnet apparently requires an extension, so switching back to plain YAML. 2019-05-20 17:30:07 -07:00
Dan Helfman 31ae1013d7 Add missing close curly. 2019-05-20 17:18:30 -07:00
Dan Helfman 071945e558 Re-order. 2019-05-20 17:15:01 -07:00
Dan Helfman 5c4d6a6e83 Upgrade Drone build file format from 0.8 to 1.x. 2019-05-20 17:05:29 -07:00
Dan Helfman 9c9be65b2b Add files for building documentation into a Docker image for web serving. 2019-05-20 11:41:39 -07:00
Dan HelfmanandGitea c164684703 Allow to only run unit tests with Tox. 2019-05-19 22:07:15 +00:00
Dan Helfman 842c9001ba Auto-join #borgmatic from IRC web chat. 2019-05-19 15:04:01 -07:00
Dan Helfman 481e47076e Add #borgmatic Freenode IRC channel to documentation. 2019-05-19 15:01:03 -07:00
Luke Murphy 917a0dd0a0 Pass posargs to pytest in main testenv 2019-05-19 23:53:43 +02:00
Luke Murphy 358aed7c31 Allow to only run unit tests with Tox 2019-05-19 23:53:42 +02:00
Dan HelfmanandGitea 9893834e85 Pass positional arguments to Tox environments commands. 2019-05-19 21:28:38 +00:00
Dan Helfman 32cf3225c5 Update NEWS. 2019-05-19 14:17:00 -07:00
Dan HelfmanandGitea 2bfd7518c5 Look for .yml configuration file extension in addition to .yaml (#178). 2019-05-19 21:16:25 +00:00
Dan Helfman 4ba56684d1 Update NEWS with remove Python cache files before each Tox run. 2019-05-19 10:07:28 -07:00
Dan HelfmanandGitea 0b1e38e5f6 Remove Python cache files before each Tox run. 2019-05-19 17:07:11 +00:00
Luke Murphy 7974219389 Make sure to pass posargs for Tox testenvs 2019-05-19 13:11:22 +02:00
Luke Murphy 8424e443a9 Also read .yml ending configuration files
Closes https://projects.torsion.org/witten/borgmatic/issues/178.
2019-05-19 13:04:42 +02:00
Luke Murphy 85251cf5d4 Ensure to remove cache files for Tox runs 2019-05-19 12:46:32 +02:00
Dan Helfman 8f882ea3ea Switch to more standard "utm_source" for hosting provider link. 2019-05-18 21:33:39 -07:00
Dan Helfman 7a2bcc96bb Add Borg/borgmatic hosting providers to documentation. 2019-05-18 20:59:50 -07:00
Dan Helfman 8b41e58e1f Mention isort import ordering in documentation. 2019-05-16 12:06:55 -07:00
Dan Helfman 9417359da3 Fix for regression with missing verbose log entries (#177). 2019-05-16 10:50:19 -07:00
Dan Helfman 1cf0e1bd84 Support for various Borg directory environment variables (#153). 2019-05-16 10:34:52 -07:00
Dan Helfman 223f803e87 Fix formatting. 2019-05-14 13:09:36 -07:00
Dan Helfman 6cb901d083 Bump version for release. 2019-05-14 13:07:49 -07:00
Dan HelfmanandGitea 096be14230 Run tests for all installed versions of Python (#166). 2019-05-14 20:06:08 +00:00
Dan Helfman bb8b1e58e8 PR feedback: Consistency. 2019-05-14 12:19:56 -07:00
Dan Helfman 06261d8c86 Merge branch 'master' into tox-skip-missing-interpreters 2019-05-14 12:18:30 -07:00
Dan HelfmanandGitea 869cccf884 Upgrade pip to a particular version during local test and CI. 2019-05-14 19:17:30 +00:00
Dan Helfman 0defaf9cb5 Run tests for all installed versions of Python (#166). 2019-05-14 12:09:07 -07:00
Dan Helfman 60b1f9921d Don't use pip wrapper script in CI. 2019-05-14 12:01:40 -07:00
Dan Helfman f61bc91b0f Merge branch 'master' into upgrade-pip 2019-05-14 10:31:03 -07:00
Dan Helfman ed2c6053de Upgrade pip to a particular version during local test and CI. 2019-05-14 10:28:04 -07:00
Dan Helfman 2cffa8deaa Add missing ticket number to NEWS item. 2019-05-14 10:07:46 -07:00
Dan Helfman f0581271f6 Automatically sort Python imports in code. 2019-05-14 10:02:41 -07:00
Dan HelfmanandGitea 99522234ea Automatically sort Python imports in code. 2019-05-14 17:02:37 +00:00
Dan Helfman 67f2862fb1 Change paths to reflect new pip install --user documentation. 2019-05-14 10:00:50 -07:00
Luke Murphy 1c0dc3f904 Run isort over the source 2019-05-14 18:59:19 +02:00
Luke Murphy b94dbff216 Add isort configuration
Closes https://projects.torsion.org/witten/borgmatic/issues/169.
2019-05-14 18:59:19 +02:00
Dan Helfman 7388c723cd Mention tox.ini refactoring in NEWS. 2019-05-14 09:45:39 -07:00
Dan HelfmanandGitea 128be3c17d Factor out build/test configuration from tox.ini file. 2019-05-14 16:45:24 +00:00
Luke Murphy 4c30c94258 Add workaround for editable failure
See https://projects.torsion.org/witten/borgmatic/pulls/165#issuecomment-1416.
2019-05-14 13:17:15 +02:00
Luke Murphy 20b8b45aeb Remove all configuration from Tox file
This puts tool configuration in their familiar and standard
locations and simplifies the Tox configuration to just laying
out the environments and factors.

This also allows users who do not want to deal with overhead of Tox (for
whatever reasons ...) to run pytest/black/etc. and have the same
behaviour.
2019-05-14 13:17:15 +02:00
Dan Helfman 2dd899f287 Linkify build status badge. 2019-05-13 22:33:28 -07:00
Dan Helfman a13cc0ab17 More tests for colored logging. 2019-05-13 21:10:26 -07:00
Dan Helfman 620f9e64d6 A few more tests for new colored logging. 2019-05-13 20:49:20 -07:00
Dan Helfman 25c320b281 Pin pip version: cherrypick of https://projects.torsion.org/witten/borgmatic/pulls/172 2019-05-13 20:01:25 -07:00
Dan Helfman f19eec56ac Add tox pin to NEWS. 2019-05-13 17:07:20 -07:00
Dan HelfmanandGitea 7cbcff2e9b Pin tox version. 2019-05-14 00:06:07 +00:00
Dan Helfman 9f6407ada6 Mention continuous integration badge in NEWS. 2019-05-13 14:54:24 -07:00
Dan HelfmanandGitea e933ecf046 Add drone CI note and badge. 2019-05-13 21:51:00 +00:00
Luke Murphy 4010a2ed77 Add note about Drone CI to contributing docs 2019-05-13 23:43:11 +02:00
Luke Murphy 2f36096e1a Add Drone build badge 2019-05-13 23:42:11 +02:00
Luke Murphy 82ec45e375 Pin tox version
Towards more reproducible results with Tox.
2019-05-13 23:12:18 +02:00
Dan Helfman 37362150fe Color records that are logged via logger.handle() as well. 2019-05-13 13:50:32 -07:00
Dan Helfman a7ba97803f Add colored output to NEWS. 2019-05-13 19:52:54 +00:00
Dan HelfmanandGitea 31dc903877 Integrate colorama for colored output. 2019-05-13 19:50:36 +00:00
Dan Helfman 8943867433 Bump to dev version. 2019-05-13 19:07:27 +00:00
Dan Helfman d9cb110563 Document installing borgmatic with pip install --user instead of a system Python install. 2019-05-13 19:06:42 +00:00
Dan HelfmanandGitea 32113cee67 Document installing borgmatic with pip install --user instead of a system Python install. 2019-05-13 19:04:24 +00:00
Luke Murphy a621ce199a Add tests for borgmatic.logger.to_bool 2019-05-13 13:40:23 +02:00
Luke Murphy 1f524d6c87 Add borgmatic custom logger 2019-05-13 13:40:18 +02:00
Luke Murphy 0320d449ec Add documentation about colorama 2019-05-13 13:40:17 +02:00
Luke Murphy 30f007687a Add colorama to testing dependencies 2019-05-13 13:40:17 +02:00
Luke Murphy adf7856162 Add new colorama dependency 2019-05-13 13:40:17 +02:00
Luke Murphy f9dce8b2d3 Recommend user installs when upgrading 2019-05-13 13:18:59 +02:00
Luke Murphy 15cb6270ef Recommend a tox user install for developing 2019-05-13 13:18:45 +02:00
Luke Murphy ed14fdbac9 Recommend root user package install
This can do bad things to a system Python install. So, we try to
mitigate this by recommending a root user user site installation.
2019-05-13 13:18:37 +02:00
Dan Helfman 8650a15db1 Document validate-borgmatic-config and add a few tests. 2019-05-11 14:05:16 -07:00
Dan HelfmanandGitea 6a10022543 Add validate-borgmatic-config command. 2019-05-11 20:15:06 +00:00
Robin Schneider 52e4f48eb9 Add validate-borgmatic-config command
Useful when generating the borgmatic configuration file with
configuration management and before moving the generated file in place
checking if it is actually valid.
2019-05-10 00:10:28 +02:00
Dan Helfman f5e1e8bec9 In continuous integration build matrix, use newer Alpine 3.9 instead of 3.8. 2019-05-07 16:19:03 -07:00
Dan Helfman a291477c19 Fix for hooks executing when using --dry-run (#160). 2019-05-07 16:06:31 -07:00
Dan Helfman 1c88dda76a Fix for invalid JSON output when using multiple borgmatic configuration files (#155). 2019-04-02 22:30:14 -07:00
Dan Helfman 0b59c22c23 Fix for seemingly random filename ordering when running through a directory of configuration files (#157). 2019-03-30 16:41:21 -07:00
Dan Helfman 576377e2b2 Clarify differences between Docker images. 2019-03-16 15:04:48 -07:00
Dan Helfman 6ff1867312 Configuration files includes and merging (#148). 2019-03-06 12:06:27 -08:00
Dan Helfman 3cb52423d2 Support for Borg create/extract --numeric-owner flag (#147). 2019-03-05 09:11:35 -08:00
Dan Helfman 5a5b6491ac Add note about uncommenting section names. 2019-03-04 15:15:49 -08:00
Dan Helfman 4272c6b077 List the files within an archive via --list --archive option (#140). 2019-02-23 23:02:17 -08:00
Dan Helfman 26071de2e7 Update extraction docs. 2019-02-18 22:43:32 -08:00
Dan Helfman fe92d9e838 Fix restore paths list to tuple conversion. 2019-02-18 21:59:09 -08:00
Dan Helfman 5ea2d644a2 Fix error handling when --extract repository guard fails. 2019-02-18 21:52:56 -08:00
Dan Helfman c35f90154f Only guard repository when --extract is used. 2019-02-18 21:43:30 -08:00
Dan Helfman 36305c50b1 Update push script to support branches. 2019-02-18 13:51:33 -08:00
Dan Helfman 2b3b8eab71 Add archive extract to end-to-end test. 2019-02-18 13:47:18 -08:00
Dan Helfman aa7c7651e5 Fix config repositories consumption. 2019-02-18 13:27:35 -08:00
Dan Helfman c41ffb5ceb If no extract repository is given, then error if there are multiple configured repositories. 2019-02-18 13:22:14 -08:00
Dan Helfman 766a03375a Guard that the given repository occurs in config exactly once. 2019-02-18 12:58:39 -08:00
Dan Helfman 2a4d4247e3 Tests for extract_archive(). 2019-02-18 10:31:52 -08:00
Dan Helfman 9de5083a7e Additional test coverage for extract options in borgmatic command. 2019-02-18 09:52:56 -08:00
Dan Helfman d0557b2bcd Initial work on #123: Support for Borg extract. 2019-02-18 09:30:34 -08:00
Dan Helfman 1a980d6321 Organize options within command-line help into logical groups. 2019-02-12 22:27:04 -08:00
Dan Helfman fb21d4e645 Remove dead code. 2019-02-09 21:17:55 -08:00
Dan Helfman 5933a4d778 Note tests exclusion in changelog. 2019-02-08 20:59:50 -08:00
Dan HelfmanandGitea 8cf16c7831 Exclude tests from distribution packages. 2019-02-09 05:02:19 +00:00
Julien Nicoulaud fcf4e03c2f exclude tests from distribution packages 2019-02-08 19:22:56 +01:00
Dan Helfman d1b29e82da borgmatic command-line reference. 2019-02-04 22:27:40 -08:00
Dan Helfman 290e969a22 Include a sample borgmatic configuration file in the documentation (#119). 2019-02-04 22:12:33 -08:00
Dan Helfman 18ae91ea6e Strike some unnecessary words from docs. 2019-02-04 20:58:27 -08:00
Dan Helfman 0bce77a2ac Distribute troubleshooting among relevant how-to guides. 2019-02-04 20:53:47 -08:00
Dan Helfman 19155607af Include link to development how-to. 2019-02-03 22:42:33 -08:00
Dan Helfman f357c37e2c Fix/remove some documentation links. 2019-02-03 22:35:38 -08:00
Dan Helfman 2980c14728 Fix README links on GitHub. 2019-02-03 22:26:39 -08:00
Dan Helfman 7e0e00d45d Refactor documentation into multiple separate pages for clarity and findability. 2019-02-03 22:20:59 -08:00
Dan Helfman 8b4ac0017b Fix ticket number in changelog. 2019-01-27 14:00:24 -08:00
Dan Helfman 8ec1ec527e Bump version for release. 2019-01-27 13:54:26 -08:00
Dan Helfman 6096a7181c Leave exclude_patterns glob expansion to Borg, since doing it in borgmatic leads to confusing behavior (#132). 2019-01-27 13:47:26 -08:00
Dan Helfman fa9dfb8ff7 Remove date echo from schema example, as it's not a substitute for real logging (#127). 2019-01-27 12:22:22 -08:00
Dan Helfman 2dc006aab4 Allow use of --stats flag when --create or --prune flags are implied (#139). 2019-01-27 12:15:47 -08:00
Dan Helfman 031b9d6faf Handle and format validation errors raised during argument parsing (#136). 2019-01-27 11:58:04 -08:00
Dan HelfmanandGitHub d9018a47f6 Add link to openSUSE packages from README. 2019-01-21 09:57:05 -08:00
Antonio LarrosaandGitHub e893a20dfd Add link to openSUSE packages
Add a link to the software.opensuse.org page were both official and community packages of borgmatic are available to be downloaded or installed using 1-click-install.
2019-01-21 13:13:40 +01:00
Dan Helfman 09d521661f Remove weasel words like "easily" and "simply". 2018-12-25 22:03:34 -08:00
Dan Helfman fd46efb193 Add borgmatic --version command-line flag to get the current installed version number. 2018-12-25 21:01:08 -08:00
Dan Helfman 426f54c9cc When generating sample configuration, document the defaults for each option (#103). 2018-12-25 17:05:22 -08:00
Dan Helfman 45a537b6b1 When running multiple configuration files, attempt all of them even if one errors (#116). 2018-12-25 15:23:54 -08:00
Dan Helfman d6feca169c Fix duplicate issue number. 2018-12-24 22:35:16 -08:00
Dan Helfman 05e2900ab0 Rev version. 2018-12-24 22:29:41 -08:00
Dan Helfman 30b52e5523 With --init command-line flag, if a repository already exists, proceed without erroring (#117). 2018-12-24 22:28:02 -08:00
Dan Helfman 14aeddc11f Black re-formatting. 2018-12-24 14:38:57 -08:00
Dan Helfman 066399ecdb Mention --stats command-line flag in NEWS file. 2018-12-23 16:06:08 -08:00
Dan HelfmanandGitea d4bbac4467 Support for --stats command-line flag independent of --verbosity (#100). 2018-12-24 00:04:23 +00:00
Felix Buehler 7516443a89 fix changes requested about stats 2018-12-22 23:46:03 +01:00
Felix Buehler 73d67e29b4 Support for Borg create & prune --stats via borgmatic command-line flag (#100) 2018-12-22 23:27:24 +01:00
Dan Helfman c3e7425f4c Some late-breaking README additions (new borgmatic packages). 2018-12-10 22:30:10 -08:00
Dan Helfman cc9dbb1def Support for Borg repository initialization via borgmatic --init command-line flag (#110). 2018-12-10 22:20:57 -08:00
Dan Helfman 2045edc11b Fix warning about classifiers as tuple. 2018-12-09 15:49:58 -08:00
Dan Helfman 1dcac44d6c Fix broken test of deprecated --excludes option. 2018-12-09 15:49:05 -08:00
Dan Helfman 300ead65d3 Error when deprecated --excludes command-line option is used. 2018-12-09 14:57:14 -08:00
Dan Helfman 6a0219a7a4 Update README with link to a new/forked Docker image (#113). 2018-12-02 15:16:52 -08:00
Dan Helfman 80c69aac05 Fix incomplete test coverage around --progress argument validation. 2018-12-02 15:08:42 -08:00
Dan Helfman 7417a3cd00 Update Borg create --filter values so a dry run lists files to back up. (#111). 2018-12-02 15:03:07 -08:00
Dan Helfman 9ca80a54d8 Support for Borg create --progress via borgmatic command-line flag (#108). 2018-11-21 22:03:39 -08:00
Dan Helfman 5c0b17ef39 Support for Borg --chunker-params create option via "chunker_params" in borgmatic's storage section (#105). 2018-10-27 15:57:28 -07:00
Dan Helfman 1697d8aaef Silence curl when posting release description to Gitea. 2018-10-15 22:32:13 -07:00
Dan Helfman fef441a8ff More concessions for Python 3.5 compatibility. 2018-10-15 09:37:26 -07:00
Dan Helfman c1ddc4268b We can't have nice things. 2018-10-15 09:30:04 -07:00
Dan Helfman e323290e61 Switch from bash to sh for black wrapper. 2018-10-15 09:25:57 -07:00
Dan Helfman 1ab44d4201 Wrap black with script that skips it if Python version < 3.6. 2018-10-15 09:20:35 -07:00
Dan Helfman 71b1c3dfb0 Make automated tests support running in Python 3.5. 2018-10-15 09:04:29 -07:00
Dan Helfman 695930a607 Fix for syntax error that occurred in Python 3.5 and below (#102). 2018-10-15 08:47:15 -07:00
Dan Helfman eb2a4ff1f0 Add Python 3.5 to continuous integration. 2018-10-15 08:17:34 -07:00
Dan Helfman 531d5c80c0 Fix quoting and escaping in release script. 2018-10-14 12:14:29 -07:00
Dan Helfman 067ed27689 Rev to 1.2.8. 2018-10-14 11:45:34 -07:00
Dan Helfman fa38de2de7 Enable consistency checks for only certain repositories via "check_repositories" (#73). 2018-10-13 20:34:51 -07:00
Dan Helfman e4d1b49c39 Switch some functions with many arguments to kwargs only. 2018-10-13 15:19:16 -07:00
Dan Helfman af7caec509 Mention minimum Borg version to install in README. 2018-10-13 13:35:42 -07:00
Dan Helfman 90c1f899fc Use newer Alpine (with newer version of Borg) in matrix builds. 2018-10-13 13:35:18 -07:00
Dan Helfman a0691ae4cd Run continuous integration tests on a matrix of Python and Borg versions. 2018-10-13 13:09:12 -07:00
Dan Helfman 2f20e6f808 Include link to issue tracker within various command output. 2018-10-07 22:29:56 -07:00
Dan Helfman 7a4636ae0f Remove curl --verbose in release script. 2018-10-06 22:35:00 -07:00
Dan Helfman 53435dcc3e Post release changelogs to projects.evoworx.org. 2018-10-06 22:24:46 -07:00
Dan Helfman 4d01278037 Update release file to post changelogs to GitHub release descriptions. 2018-10-06 15:18:21 -07:00
Dan Helfman 2299e5d41e Additional dependency version pins in test requirements. 2018-10-06 14:17:47 -07:00
Dan Helfman d16f5d5df3 Add backticks to path literal in README. 2018-10-06 13:23:54 -07:00
Dan Helfman da8e9638f4 Support for Borg --keep-secondly prune option (#98). 2018-10-04 21:54:23 -07:00
Dan Helfman 900ea80a42 Hack to uncomment all options in config file used for finding unsupported Borg options. 2018-10-04 21:45:31 -07:00
Dan Helfman 4b92d0f685 Remove unneeded Dockerfile for end-to-end tests. 2018-10-03 22:44:23 -07:00
Dan Helfman 3ce5533103 Make end-to-end test clean up after itself, and drop unnecessary use of Docker for it. 2018-10-03 22:36:25 -07:00
Dan Helfman 4a1ee8c911 Pull new base Docker images during CI. 2018-10-03 19:35:42 -07:00
Dan Helfman 3f22a99412 Rev pykwalify. 2018-10-03 08:59:08 -07:00
Dan Helfman caf95cc913 Rebuild. 2018-09-30 22:58:23 -07:00
Dan Helfman fd3130b4d9 Install tox before using it. 2018-09-30 22:47:07 -07:00
Dan Helfman 65bb5a49e2 CI? 2018-09-30 22:43:49 -07:00
Dan Helfman 4bcc517326 Attempted Drone CI configuration. 2018-09-30 22:09:53 -07:00
Dan Helfman 0b164973e0 Add an end-to-end automated test that actually integrates with Borg. 2018-09-30 17:30:04 -07:00
Dan Helfman a125df991b Move tests to the root of the repository, in keeping with more common convention. 2018-09-30 13:57:20 -07:00
Dan Helfman f9a9b42c58 A little introductory text for the screencast. 2018-09-30 11:11:07 -07:00
Dan Helfman 56ad1d164a Use Flake8 code checker as part of running automated tests. 2018-09-29 23:15:18 -07:00
Dan Helfman 3cce18919c Switch Black link to documentation. 2018-09-29 22:46:34 -07:00
Dan Helfman 76d6a69f5a Use Black code formatter as part of running automated tests. 2018-09-29 22:45:00 -07:00
Dan Helfman 3db17277b4 Replace broken screencast thumbnail with embedded player. 2018-09-29 21:38:38 -07:00
Dan Helfman ece49eb500 Update screencast. 2018-09-29 18:56:39 -07:00
Dan Helfman 746428ed44 Fix generated configuration to also include a "keep_daily" value so pruning works out of the box. 2018-09-29 15:44:37 -07:00
Dan Helfman 984702b3b2 Fix various warnings. 2018-09-29 15:06:57 -07:00
Dan Helfman 1bc71e1c5d Upgrade test requirements. 2018-09-29 15:04:42 -07:00
Dan Helfman 47efa88c9d In generate-borgmatic-config, comment out all optional config (#57). 2018-09-29 15:03:11 -07:00
Dan Helfman 3821636b77 Bump version. 2018-09-27 08:13:23 -07:00
Dan Helfman 596f6f9dac Update help/README about --create --json. 2018-09-27 08:12:54 -07:00
Dan HelfmanandGitHub 7ecdaea83a Fix check_archives does not take json parameter. 2018-09-27 08:09:23 -07:00
Nils HesseandGitHub 98cb2644db check_archives does not take json parameter 2018-09-27 12:21:14 +02:00
Dan Helfman 31db6faa19 Set to release version. 2018-09-26 21:32:28 -07:00
Dan Helfman 872d8b695a Flesh out NEWS line item a bit. 2018-09-24 21:37:45 -07:00
Dan HelfmanandGitea 6db3e1dda5 Merge branch 'master' of floli/borgmatic into master 2018-09-25 04:36:09 +00:00
Florian Lindner 9aaf78b9dd Add --json option for --create command line.
Closes #94.
2018-09-24 21:53:09 +02:00
Dan Helfman 5d8ac158ce Merge ssh://projects.torsion.org:3022/witten/borgmatic 2018-09-17 22:34:08 -07:00
Dan Helfman d32a53d58f Mention log level fix in NEWS. 2018-09-17 22:33:34 -07:00
floliandGitea a836ec944f Limit argument range for --verbose, make default log level more explicit. (#93) 2018-09-18 05:31:27 +00:00
Dan Helfman e7b128e735 --read-special is now supported. 2018-09-09 11:21:06 -07:00
Dan Helfman ff3cb1d80f Attach #64 to logging rewrite in NEWS. 2018-09-09 11:18:26 -07:00
Dan Helfman c5ff08ee25 Remove now-gone verbosity parameter from test. 2018-09-09 11:14:33 -07:00
Dan Helfman 856db29180 Mention --read-special in NEWS. 2018-09-09 10:42:06 -07:00
Steve KerrisonandDan Helfman 20e09b4ea8 Support for Borg create --read-special via "read_special" option (#25). 2018-09-09 10:39:56 -07:00
Dan Helfman 1dd0682661 Merge branch 'master' of ssh://projects.torsion.org:3022/witten/borgmatic 2018-09-08 13:54:18 -07:00
floliandGitea 7252b8d614 Rework logging/verbosity system (#90)
Looks great, merged! Thanks again for all your hard work here.
2018-09-08 20:53:37 +00:00
grerrgandDan Helfman e5870a169b Add example for cron in Alpine Linux (#24) 2018-09-05 21:58:30 -07:00
Dan Helfman 94795a3560 Link to Borg home page instead of docs. 2018-09-02 22:06:57 -07:00
Dan 7705debab0 Switching back to table-like 11ty front matter. It looks less bad than JSON. 2018-09-01 22:45:13 -07:00
Dan f87df0527f Adding JSON front matter for 11ty. 2018-09-01 22:38:17 -07:00
Dan Helfman e4512a40e0 Removing 11ty front matter out of README since it renders as a table on GitHub. 2018-09-01 22:11:38 -07:00
Dan Helfman 1d4a9510b8 Upgrade pytest. 2018-09-01 20:29:05 -07:00
Dan Helfman 2648f07e7a Add missing syntax highlighting. 2018-08-29 23:01:11 -07:00
Dan Helfman 459bf1fcf6 Document --list and --info flags. 2018-08-29 22:57:32 -07:00
Dan 3930e63320 Merge branch 'master' of ssh://projects.torsion.org:3022/witten/borgmatic 2018-08-29 22:44:45 -07:00
Dan acecb1e397 README metadata changes to support 11ty static site generator. 2018-08-29 22:44:12 -07:00
Dan Helfman 9b48eb5a61 Clarify that --json can be used with --info command-line flag. 2018-08-19 12:57:52 -07:00
Dan Helfman 7d40a448cb Pass --show-rc option to Borg when at highest verbosity level (#89). 2018-08-19 12:44:40 -07:00
Dan Helfman da7aed3814 Support for Borg create --checkpoint-interval (#87). 2018-08-19 11:41:49 -07:00
Dan Helfman c7f4200417 Somewhat more robust mechanism to find unsupported Borg arguments. 2018-08-19 11:24:48 -07:00
Dan Helfman 5e2a5494af Fix declared pykwalify compatibility version range in setup.py (#88). 2018-08-18 14:07:18 -07:00
Dan Helfman 7b77fd2510 Fix compatibility issue between pykwalify and ruamel.yaml 0.15.52 (#85). 2018-08-11 13:59:27 -07:00
Dan Helfman ece5608677 Rev for release. 2018-07-28 22:27:39 -07:00
Dan Helfman 4644f613b2 Fix typo in README. 2018-07-28 22:24:24 -07:00
Dan Helfman 3afa5ac76d Document hooks (#81). 2018-07-28 22:22:25 -07:00
Dan Helfman 27f8a1df04 Switch to non-raw link to sample cron job. 2018-07-28 20:29:55 -07:00
Dan Helfman 8e5b0bbf17 Remove errant ctrl-F character from docs. 2018-07-28 20:27:18 -07:00
Dan Helfman 282e9565c9 Mentioning new --info --json option in NEWS. 2018-07-28 20:24:19 -07:00
Thomas LÉVEILandGitea b714ffd48b add support for --info --json (#83) 2018-07-29 03:17:45 +00:00
Dan Helfman 9968a15ef8 Clarifying code style for multiline constructs. 2018-07-28 15:21:19 -07:00
Dan Helfman d93da55ce9 Add code style guidelines to the documention, and reformat some code accordingly. 2018-07-28 15:02:17 -07:00
Thomas LÉVEILandGitea 789bcd402a add support for --list --json (#74) 2018-07-28 21:21:38 +00:00
Dan HelfmanandGitea cf6ab60d2e Use XDG_CONFIG_HOME for user configuration directory, if set. (Thanks to floli.) (#71)
Thanks! This will go out in the next release.
2018-07-25 01:34:05 +00:00
Dan Helfman 64364b20ff Skip non-"*.yaml" config filenames in /etc/borgmatic.d/ so as not to parse backup files, editor swap files, etc. (#77) 2018-07-22 12:08:49 -07:00
Dan Helfman d29c7956bc Upgrade ruamel.yaml compatibility version range and fix support for Python 3.7 (#38, #76). 2018-07-22 11:25:06 -07:00
Dan Helfman e5ef485d6b Merge branch 'master' of ssh://projects.torsion.org:3022/witten/borgmatic 2018-07-01 14:54:15 -07:00
Dan Helfman fc8046edc4 Adding NEWS item about skipping before/after backup hooks. 2018-07-01 14:51:57 -07:00
Dan HelfmanandGitea 4538017206 Merge branch 'fix-72-hooks-are-executed-when-list-or-info' of thomasleveil/borgmatic into master
Thanks for fixing this!

I agree about more specific hooks if and when `--check` or `--prune`-specific hook use cases are needed. I think what you've done here is fine until then.
2018-07-01 21:47:39 +00:00
Thomas LEVEIL d664b6d253 only run hooks when creating an archive
fix #72
2018-07-01 21:09:45 +02:00
Dan Helfman f42aa0a6f2 Revving version for development. 2018-06-17 15:26:53 -07:00
Dan Helfman 9d4ba66f6e Revving version for release. 2018-06-17 15:14:45 -07:00
Dan Helfman cf846ab8ac Support for Borg prune --umask option (#69). 2018-06-17 15:12:43 -07:00
Dan Helfman 219e287c6c Document how to develop on and contribute to borgmatic. 2018-06-17 14:55:57 -07:00
Dan Helfman dede8f9d4b News for: ~/.config/borgmatic/config.yaml. 2018-06-17 14:30:47 -07:00
Dan HelfmanandGitea 7a1e3f5639 Merge branch 'add_user_config' of floli/borgmatic into master 2018-06-17 21:26:36 +00:00
Florian Lindner 9bd77292ff Add default path for user configuration 2018-06-10 15:03:23 +02:00
Dan Helfman f1a143de5b Adding list and info Borg sub-commands to find-unsupported-borg-options script. 2018-05-26 20:53:03 -07:00
Dan Helfman 765e343c71 Support for Borg --info via borgmatic command-line (#61). 2018-05-26 16:19:05 -07:00
Dan Helfman af4b91a048 Support for Borg --list option via borgmatic command-line to list all archives (#61). 2018-05-26 16:09:08 -07:00
Dan Helfman cc9044487b Support for Borg --nobsdflags option to skip recording bsdflags (e.g. NODUMP, IMMUTABLE) in archive (#63). 2018-05-26 15:09:23 -07:00
Dan Helfman 11c30001c3 Add "Persistent" flag to systemd timer example. (#60) 2018-05-20 22:20:21 -07:00
Dan HelfmanandGitea ac9161035a Merge branch 'master' of floli/borgmatic into master
Thanks for taking the time to add this! Makes sense.
2018-05-21 05:18:13 +00:00
Dan Helfman 007ec0644c Ignore "check_last" and consistency "prefix" when "archives" not in consistency checks. (#59) 2018-05-20 22:11:40 -07:00
Dan Helfman 1db808fb3d Link to OpenBSD port of borgmatic. 2018-05-19 16:16:54 -07:00
Dan Helfman 76656275c3 Update README to mention other ways of installing borgmatic. (#62) 2018-05-19 16:06:54 -07:00
Florian Lindner 64bdbc4bf0 Add Persistent, so that the timer is triggered if missed last time. 2018-05-17 21:47:58 +02:00
Dan Helfman 61033bb4e5 Update tox.ini to only assume Python 3.x instead of Python 3.4 specifically. 2018-04-09 20:34:59 -07:00
Dan Helfman e608b7924a Adding note about executable location. 2018-04-08 12:06:15 -07:00
Dan Helfman f7f852a28b Fix tests broken by addition of check --prefix default. 2018-03-03 22:36:51 -08:00
Dan Helfman 9b9c4c4abb Clarifying note in schema about adding prefix to consistency section. 2018-03-03 22:33:34 -08:00
Dan Helfman 1b59f5b190 Changing version in warning to correspond with next release version. 2018-03-03 22:30:30 -08:00
Dan Helfman 65ab230961 Noting new Borg check --prefix feature in release notes. 2018-03-03 22:21:48 -08:00
Nick WhyteandDan Helfman c64d0100d5 Only check archives with matching prefix. 2018-03-03 22:17:39 -08:00
Dan Helfman 0112407250 Add introductory screencast link to documentation. 2018-02-19 17:44:20 -08:00
Dan Helfman 2d3f5fa05d Support for Borg --lock-wait option for the maximum wait for a repository/cache lock (#56). 2018-02-19 15:51:04 -08:00
Dan Helfman a87036ee46 Support for using tilde in exclude_patterns to reference home directory (#58). 2018-02-18 15:34:19 -08:00
Dan Helfman a72f5ff69a Tests for --dry-run + --verbosity fix. 2018-02-18 14:26:51 -08:00
newtonneandDan Helfman bb99009191 Fix issue when using both --dry-run and -v options. 2018-02-18 14:18:25 -08:00
Dan Helfman 4c45d60529 Mentioning ssh_command for additional SSH configuration.. 2018-02-18 13:39:05 -08:00
Dan Helfman 2211f959ae AUTHORS addition for recent encryption pass command changes. 2018-01-18 21:13:20 -08:00
Dan Helfman cc1d6f53a0 55: Fix for missing tags/releases from Gitea and GitHub project hosting. 2018-01-17 20:27:09 -08:00
Dan Helfman 389778c716 Adding BORG_PASSCOMMAND update to NEWS. 2018-01-16 21:05:53 -08:00
newtonneandDan Helfman e55e9e8139 Add encryption_passcommand configuration option 2018-01-16 21:03:25 -08:00
Dan Helfman ef76e87477 Bumping version for release. 2018-01-15 20:55:49 -08:00
Dan Helfman 62526038d6 47: Support for Borg --dry-run option via borgmatic command-line. 2018-01-15 20:55:27 -08:00
Dan Helfman bf2f39623e 49: Rename incorrect --pattern-from option to correct --patterns-from. 2018-01-15 20:22:53 -08:00
Dan Helfman 28c890a52d Bumping version for release. 2018-01-14 16:37:02 -08:00
Dan Helfman cd189c4fe4 48: Add "local_path" to configuration for specifying an alternative Borg executable path. 2018-01-14 16:35:24 -08:00
Dan Helfman b8f6bab12d 49: Support for Borg experimental --patterns-from and --patterns options for specifying mixed includes/excludes. 2018-01-14 15:52:19 -08:00
Dan Helfman 50b3240c4f 54: Fix for incorrect consistency check flags passed to Borg when all three checks in borgmatic config. 2018-01-14 14:09:20 -08:00
Dan Helfman 18fbc75e16 Revising history to account for off-by-one error when importing issue numbers into Gitea. 2018-01-04 21:49:43 -08:00
Dan Helfman 0881da4a82 New issue tracker. 2018-01-04 21:37:43 -08:00
Dan fa210766a2 Update for release. 2018-01-02 20:36:52 -08:00
Dan d4f52e3137 Update AUTHORS with recent changes + sort. 2018-01-02 20:33:27 -08:00
Dan 8b2ebdc5f7 Simplifying example. 2018-01-02 20:31:06 -08:00
Dan HelfmanandGitea a00407256d Merge branch 'keep_minutely' of thomasleveil/borgmatic into master 2018-01-03 04:30:13 +00:00
Thomas LEVEIL 24b5eccefc add support for Borg --keep-minutely prune option 2018-01-03 00:13:44 +01:00
Dan 815fb39a05 Declare dependency on pykwalify 1.6 or above, as older versions yield "Unknown key: version" rule errors. 2017-11-26 10:30:31 -08:00
Dan 24c196d2a4 Script to find unsupported Borg options in borgmatic, to assist with #13. 2017-11-11 16:07:54 -08:00
Dan 3e26e70d0c Fix for incorrect /etc/borgmatic.d/ configuration path probing on macOS. 2017-11-10 21:33:29 -08:00
Dan 5ce25e2790 Re-fixing logo image. 2017-11-04 11:55:48 -07:00
Dan 8243552c8c Fixing PNG path. 2017-11-04 11:54:29 -07:00
Dan 425e27dee5 Add "ssh_command" to configuration for specifying a custom SSH command or options. 2017-11-03 22:01:04 -07:00
Dan 9ec9269a18 Link to repository encryption section of Borg Quick Start. 2017-11-03 20:28:31 -07:00
Dan bf5cbd1deb Mentioning use of BORG_PASSPHRASE environment variable. 2017-11-03 20:27:21 -07:00
Dan 4c09cbf1a4 Releasing. 2017-11-02 22:38:16 -07:00
Dan fc077af4ce Mentioning borgmatic --config option in docs. 2017-11-02 22:28:53 -07:00
Dan ca4312bb85 Support for Borg --remote-ratelimit for limiting upload rate. And log Borg commands. 2017-11-02 22:22:40 -07:00
Dan fc3b1fccba Support for Borg --files-cache option for setting the files cache operation mode. 2017-11-02 22:03:11 -07:00
Dan f83346b9b3 Support for using tilde in repository paths to reference home directory. 2017-11-02 21:34:04 -07:00
Dan 63c7241aec Typo in comment. 2017-10-31 22:21:49 -07:00
Dan fd77dc579e Pass through several more Unix signals that Borg happens to consume. 2017-10-31 22:10:00 -07:00
Dan f017ed648f Disabling code coverage on this one-line functions. 2017-10-31 22:01:18 -07:00
Dan 27a6745743 Passing the Unix SIGTERM signal through to child processes like Borg. 2017-10-31 21:58:35 -07:00
Dan 95be0c8e46 Removing broken download URL. 2017-10-29 21:42:01 -07:00
Dan 17a774ba7e Removing .hg* files. 2017-10-29 20:32:21 -07:00
Dan a1d2bd173b Bumping version for release. 2017-10-29 20:29:53 -07:00
Dan f495550ad7 Default "prefix" to "{hostname}-" if not specified. 2017-10-29 20:14:18 -07:00
Dan 43d0e597a2 Require "prefix" in retention section when "archive_name_format" is set. 2017-10-29 19:36:26 -07:00
Dan f1c07b5cf5 Updated dead links to Borg documentation. 2017-10-29 17:05:49 -07:00
Dan f2782426d5 Comment typo. 2017-10-29 16:46:30 -07:00
Dan f13ed92b0e Breaking borgmatic command main() apart, since it was getting a little unwieldy. 2017-10-29 16:44:15 -07:00
Dan 6e9e7c45d7 Being explicit about markdown syntax highlighting in README. 2017-10-28 10:45:27 -07:00
Dan c1ca4b9421 Using absolute path for logo. 2017-10-28 10:33:36 -07:00
Dan 469feadbc0 Adding missing PNG logo. 2017-10-28 10:31:30 -07:00
Dan a5403a4373 Switched logo from SVG to PNG for compatibility reasons. 2017-10-28 10:29:18 -07:00
Dan 56c902258d Setting up download URL for new hosting location. 2017-10-27 22:40:34 -07:00
Dan 9c1660f467 Fix typo in README. 2017-10-27 22:27:28 -07:00
Dan dd926b5762 Updating links. 2017-10-27 22:26:33 -07:00
Dan 9d03351b5d Converted main source repository from Mercurial to Git. 2017-10-27 21:55:08 -07:00
Dan 719d9a9835 Merge branch 'master' of github.com:witten/borgmatic 2017-10-27 21:51:34 -07:00
Dan 731c8c9ad9 Adding push and release scripts. 2017-10-27 21:51:10 -07:00
Dan Helfman 2ae8ac2947 Add tests for verbosity mapping. 2017-10-25 22:36:23 -07:00
Dan Helfman cc94e5f52f Add tests for verbosity mapping. 2017-10-25 22:36:23 -07:00
Dan Helfman a09c9f248e Adding logging to hook execution! 2017-10-25 22:32:06 -07:00
Dan Helfman 16f0a3976c Adding logging to hook execution! 2017-10-25 22:32:06 -07:00
Dan Helfman cc78223164 Fixing inconsistent indentation. 2017-10-25 21:58:02 -07:00
Dan Helfman 30f56235c1 Fixing inconsistent indentation. 2017-10-25 21:58:02 -07:00
Dan Helfman 7458769cb3 Merge. 2017-10-25 21:54:50 -07:00
Dan Helfman a5aa9355f5 Merge. 2017-10-25 21:54:50 -07:00
Dan Helfman 5c229639f0 Improve clarity of logging spew at high verbosity levels. 2017-10-25 21:47:33 -07:00
Dan Helfman 059322b7f8 Improve clarity of logging spew at high verbosity levels. 2017-10-25 21:47:33 -07:00
Johannes FeichtnerandDan Helfman f1a98d82c6 #16, #38: Support for user-defined hooks before/after backup, or on error. 2017-10-25 21:38:27 -07:00
Johannes Feichtner 80e2c023dd #16, #38: Support for user-defined hooks before/after backup, or on error. 2017-10-26 06:38:27 +02:00
b3visandDan Helfman 86511deac4 Added section about docker (#18) 2017-10-25 21:24:24 -07:00
b3vis bb3475b3f8 Added section about docker (#18) 2017-10-26 05:24:24 +01:00
Dan Helfman bd196c1fb9 Removing "from __future__ import print_function". This isn't Python 2 anymore, Toto. 2017-09-09 17:38:14 -07:00
Dan Helfman 873fc22cfb Removing "from __future__ import print_function". This isn't Python 2 anymore, Toto. 2017-09-09 17:38:14 -07:00
Dan Helfman f3d6d7c0a3 #29: Support for using tilde in source directory path to reference home directory. 2017-09-09 17:23:31 -07:00
Dan Helfman 86cc6ca869 #29: Support for using tilde in source directory path to reference home directory. 2017-09-09 17:23:31 -07:00
Dan Helfman d30caa422e #39: Fix to make /etc/borgmatic/config.yaml optional rather than required when using the default config paths. 2017-09-08 21:25:42 -07:00
Dan Helfman 84c148fb3b #39: Fix to make /etc/borgmatic/config.yaml optional rather than required when using the default config paths. 2017-09-08 21:25:42 -07:00
Dan Helfman 6c4f641c1e Added tag 1.1.7 for changeset ec7949a14a20 2017-09-03 11:33:10 -07:00
Dan Helfman b44bc57548 Added tag 1.1.7 for changeset ec7949a14a20 2017-09-03 11:33:10 -07:00
Dan Helfman bb18a9a3f2 Update NEWS and AUTHORS for release. 2017-09-03 11:33:07 -07:00
Dan Helfman f7dcbe40d4 Update NEWS and AUTHORS for release. 2017-09-03 11:33:07 -07:00
Michele LazzeriandDan Helfman 95533d2b31 Added storage.archive_name_format to config (#16)
* Added storage.archive_name_format to config
2017-09-03 11:13:14 -07:00
Michele Lazzeri 867d3fceb0 Added storage.archive_name_format to config (#16)
* Added storage.archive_name_format to config
2017-09-03 20:13:14 +02:00
Dan Helfman 3af92f8b92 Fix for traceback when "exclude_from" value is empty in configuration file. 2017-08-27 10:01:49 -07:00
Dan Helfman 7c048d1989 Fix for traceback when "exclude_from" value is empty in configuration file. 2017-08-27 10:01:49 -07:00
Dan Helfman d127e73590 Clarification of Python 3 pip usage in documentation. 2017-08-26 16:18:53 -07:00
Dan Helfman 13ba5c84de Clarification of Python 3 pip usage in documentation. 2017-08-26 16:18:53 -07:00
Dan Helfman 50c4f6f2a1 Adding documentation note about pruning happening before archiving. 2017-08-26 16:13:41 -07:00
Dan Helfman 9588e111c4 Adding documentation note about pruning happening before archiving. 2017-08-26 16:13:41 -07:00
Dan Helfman 37ae34a432 When pruning, make highest verbosity level list archives kept and pruned. 2017-08-26 16:07:30 -07:00
Dan Helfman e3a559e13b When pruning, make highest verbosity level list archives kept and pruned. 2017-08-26 16:07:30 -07:00
Dan Helfman 3664ac7418 Added tag 1.1.6 for changeset 4daa944c122c 2017-08-05 23:33:08 -07:00
Dan Helfman 3f83788858 Added tag 1.1.6 for changeset 4daa944c122c 2017-08-05 23:33:08 -07:00
Dan Helfman 10cac46f4c #12, #35: Support for Borg --exclude-from, --exclude-caches, and --exclude-if-present options. 2017-08-05 23:32:39 -07:00
Dan Helfman b1f429f4b5 #12, #35: Support for Borg --exclude-from, --exclude-caches, and --exclude-if-present options. 2017-08-05 23:32:39 -07:00
Dan Helfman 51095cd419 Remove unused imports. 2017-08-05 22:26:38 -07:00
Dan Helfman ddd56bf2a7 Remove unused imports. 2017-08-05 22:26:38 -07:00
Dan Helfman 674a6153f3 Fix imports of borg/*.py modules now that they've been split out. 2017-08-05 22:26:28 -07:00
Dan Helfman 793ffbd048 Fix imports of borg/*.py modules now that they've been split out. 2017-08-05 22:26:28 -07:00
Dan Helfman aa04473521 Split out Borg integration code into multiple files, as it was getting kind of hairy all in one. 2017-08-05 16:21:39 -07:00
Dan Helfman 247d36a309 Split out Borg integration code into multiple files, as it was getting kind of hairy all in one. 2017-08-05 16:21:39 -07:00
Dan Helfman 77d3c66fb9 Added tag 1.1.5 for changeset 64ca13bfe050 2017-07-30 11:16:41 -07:00
Dan Helfman 9f5b808042 Added tag 1.1.5 for changeset 64ca13bfe050 2017-07-30 11:16:41 -07:00
Dan Helfman 9bea7ae5ed #34: New "extract" consistency check that performs a dry-run extraction of the most recent archive. 2017-07-30 11:16:26 -07:00
Dan Helfman e85d487c3a #34: New "extract" consistency check that performs a dry-run extraction of the most recent archive. 2017-07-30 11:16:26 -07:00
Dan Helfman 23679a6edd Removing Pelican-specific title metadata out of README markdown. 2017-07-29 16:05:11 -07:00
Dan Helfman 525ffa6a28 Removing Pelican-specific title metadata out of README markdown. 2017-07-29 16:05:11 -07:00
Dan Helfman 0f44fbedf4 Getting logo to show up on GitHub. 2017-07-28 22:36:16 -07:00
Dan Helfman ac47301a64 Getting logo to show up on GitHub. 2017-07-28 22:36:16 -07:00
Dan Helfman ae15e0f404 Added tag 1.1.4 for changeset 3d605962d891 2017-07-28 22:02:43 -07:00
Dan Helfman 9347c02268 Added tag 1.1.4 for changeset 3d605962d891 2017-07-28 22:02:43 -07:00
Dan Helfman a2e8abc537 #17: Added command-line flags for performing a borgmatic run with only pruning, creating, or checking enabled. 2017-07-28 22:02:18 -07:00
Dan Helfman ceeaf25443 #17: Added command-line flags for performing a borgmatic run with only pruning, creating, or checking enabled. 2017-07-28 22:02:18 -07:00
Dan Helfman 10404143c6 Added tag 1.1.3 for changeset 3f838f661546 2017-07-25 21:21:50 -07:00
Dan Helfman 62d2b267da Added tag 1.1.3 for changeset 3f838f661546 2017-07-25 21:21:50 -07:00
Dan Helfman 94aaf4554f Releasing. 2017-07-25 21:21:47 -07:00
Dan Helfman 03d50d74ca Releasing. 2017-07-25 21:21:47 -07:00
Dan Helfman 0c8816e6cc #14: Support for running multiple config files in /etc/borgmatic.d/ from a single borgmatic run. 2017-07-25 21:18:51 -07:00
Dan Helfman 7ed5b33db5 #14: Support for running multiple config files in /etc/borgmatic.d/ from a single borgmatic run. 2017-07-25 21:18:51 -07:00
Dan Helfman e3e4aeff94 Fix for generate-borgmatic-config writing config with invalid one_file_system value. 2017-07-25 20:32:32 -07:00
Dan Helfman 57b3066987 Fix for generate-borgmatic-config writing config with invalid one_file_system value. 2017-07-25 20:32:32 -07:00
Dan Helfman 89cd879529 Added tag 1.1.2 for changeset f052a77a8ad5 2017-07-24 19:29:28 -07:00
Dan Helfman 1527ff7898 Added tag 1.1.2 for changeset f052a77a8ad5 2017-07-24 19:29:28 -07:00
Dan Helfman 2c61c0bc08 #32: Fix for passing check_last as integer to subprocess when calling Borg. 2017-07-24 19:29:26 -07:00
Dan Helfman 3967e1b5f0 #32: Fix for passing check_last as integer to subprocess when calling Borg. 2017-07-24 19:29:26 -07:00
Dan Helfman bcd8b9982d Added tag 1.1.1 for changeset 7d3d11eff6c0 2017-07-24 08:41:05 -07:00
Dan Helfman 8cbd51512b Added tag 1.1.1 for changeset 7d3d11eff6c0 2017-07-24 08:41:05 -07:00
Dan Helfman b36b923c5d #32: Fix for upgrade-borgmatic-config converting check_last option as a string instead of an integer. 2017-07-24 08:41:02 -07:00
Dan Helfman c38f7a3693 #32: Fix for upgrade-borgmatic-config converting check_last option as a string instead of an integer. 2017-07-24 08:41:02 -07:00
Dan Helfman f44a7884e6 No longer producing univeral (Python 2 + 3) wheel. 2017-07-23 17:34:17 -07:00
Dan Helfman 7c77a5a8a5 No longer producing univeral (Python 2 + 3) wheel. 2017-07-23 17:34:17 -07:00
Dan Helfman b61b09f55c Added tag 1.1.0 for changeset 5a003056a8ff 2017-07-22 23:27:26 -07:00
Dan Helfman 9caaee18b5 Added tag 1.1.0 for changeset 5a003056a8ff 2017-07-22 23:27:26 -07:00
Dan Helfman 588955a467 Setting release version. 2017-07-22 23:27:21 -07:00
Dan Helfman 7c0407ed22 Setting release version. 2017-07-22 23:27:21 -07:00
Dan Helfman ee3edeaac2 Support for backing up to multiple repositories. 2017-07-22 22:56:46 -07:00
Dan Helfman 499f8aa0a4 Support for backing up to multiple repositories. 2017-07-22 22:56:46 -07:00
Dan Helfman 90a0d3b1e0 Renaming group to section for consistency. 2017-07-22 22:17:37 -07:00
Dan Helfman 548212274f Renaming group to section for consistency. 2017-07-22 22:17:37 -07:00
Dan Helfman cd8ceccfaf To free up space, now pruning backups prior to creating a new backup. 2017-07-22 21:50:29 -07:00
Dan Helfman 1292dd2162 To free up space, now pruning backups prior to creating a new backup. 2017-07-22 21:50:29 -07:00
Dan Helfman e5c12fc81c Mentioning test coverage addition in NEWS. 2017-07-22 21:23:01 -07:00
Dan Helfman b02ac44cfc Mentioning test coverage addition in NEWS. 2017-07-22 21:23:01 -07:00
Dan Helfman f5abe05ce9 Instructions to make cron file executable. 2017-07-22 21:20:48 -07:00
Dan Helfman 52963adfc9 Instructions to make cron file executable. 2017-07-22 21:20:48 -07:00
Dan Helfman 6af53d1163 Fixing gets on config group names. 2017-07-22 21:19:26 -07:00
Dan Helfman 2274cfe480 Fixing gets on config group names. 2017-07-22 21:19:26 -07:00
Dan Helfman 3cccac8cb1 Mentioning libyaml compile errors in troubleshooting. 2017-07-22 21:07:09 -07:00
Dan Helfman 8cf52651fe Mentioning libyaml compile errors in troubleshooting. 2017-07-22 21:07:09 -07:00
Dan Helfman 919d7573c3 Upgrading instructions to super clarify Python 3 upgrade. 2017-07-22 20:52:29 -07:00
Dan Helfman 166ef8faae Upgrading instructions to super clarify Python 3 upgrade. 2017-07-22 20:52:29 -07:00
Dan Helfman 8bfffd8cf7 Removing TODO that basically entails testing ruamel.yaml round-tripping, which in theory already has its own tests. 2017-07-22 20:31:26 -07:00
Dan Helfman ac2a63763f Removing TODO that basically entails testing ruamel.yaml round-tripping, which in theory already has its own tests. 2017-07-22 20:31:26 -07:00
Dan Helfman edb54b300b Fixing up borg module to deal with new parsed config file structures. 2017-07-22 20:11:49 -07:00
Dan Helfman 8b2b41eefc Fixing up borg module to deal with new parsed config file structures. 2017-07-22 20:11:49 -07:00
Dan Helfman 41d202c2e7 TODO about using the new exclude_patterns. 2017-07-10 16:26:32 -07:00
Dan Helfman fb172f018a TODO about using the new exclude_patterns. 2017-07-10 16:26:32 -07:00
Dan Helfman 8ef6c6fcbe Bail if "--excludes" argument is provided, as it's now deprecated in favor of configuration file. 2017-07-10 16:25:13 -07:00
Dan Helfman b1355e75c4 Bail if "--excludes" argument is provided, as it's now deprecated in favor of configuration file. 2017-07-10 16:25:13 -07:00
Dan Helfman 0691cda46f Mention generate-borgmatic-config in changelog. 2017-07-10 16:07:07 -07:00
Dan Helfman d2c143d39c Mention generate-borgmatic-config in changelog. 2017-07-10 16:07:07 -07:00
Dan Helfman 8bf07e4766 Provide helpful message when borgmatic is run with only legacy config present. 2017-07-10 16:06:02 -07:00
Dan Helfman ef32b292a8 Provide helpful message when borgmatic is run with only legacy config present. 2017-07-10 16:06:02 -07:00
Dan Helfman b3d0fb0cee When writing config, make containing directory if necessary. Also default to tighter permissions. 2017-07-10 15:20:50 -07:00
Dan Helfman 61f88228b0 When writing config, make containing directory if necessary. Also default to tighter permissions. 2017-07-10 15:20:50 -07:00
Dan Helfman ff28be7724 Documentation updates based on the new YAML configuration. 2017-07-10 11:06:28 -07:00
Dan Helfman f98558546c Documentation updates based on the new YAML configuration. 2017-07-10 11:06:28 -07:00
Dan Helfman 5ff016238e Don't overwrite config files. And retain file permissions when upgrading config. 2017-07-10 10:37:11 -07:00
Dan Helfman 9cc7c77ba9 Don't overwrite config files. And retain file permissions when upgrading config. 2017-07-10 10:37:11 -07:00
Dan Helfman 618e56b2a5 Display result of config upgrade. 2017-07-10 10:13:57 -07:00
Dan Helfman 3b1b058ffe Display result of config upgrade. 2017-07-10 10:13:57 -07:00
Dan Helfman 338b80903c Fixing tests broken by excludes merging. 2017-07-10 10:09:06 -07:00
Dan Helfman 9a3b52e1fd Fixing tests broken by excludes merging. 2017-07-10 10:09:06 -07:00
Dan Helfman fea97b5149 Merge excludes into config file format. 2017-07-10 09:43:25 -07:00
Dan Helfman 0dfc935af6 Merge excludes into config file format. 2017-07-10 09:43:25 -07:00
Dan Helfman 17c87f8758 Completed test coverage of commands (except for main()s). 2017-07-09 17:03:45 -07:00
Dan Helfman 2f7527a333 Completed test coverage of commands (except for main()s). 2017-07-09 17:03:45 -07:00
Dan Helfman d49be19544 Add a version to the schema, because inevitably I'll want to revise the schema. 2017-07-09 16:18:10 -07:00
Dan Helfman 263891f414 Add a version to the schema, because inevitably I'll want to revise the schema. 2017-07-09 16:18:10 -07:00
Dan Helfman d4ae7814a0 Adding TODO about a helpful notice about legacy config. 2017-07-09 11:49:51 -07:00
Dan Helfman 644c2e6612 Adding TODO about a helpful notice about legacy config. 2017-07-09 11:49:51 -07:00
Dan Helfman dc9b075d5a Rename convert-borgmatic-config to upgrade-borgmatic-config. 2017-07-09 11:48:24 -07:00
Dan Helfman 999feb81ca Rename convert-borgmatic-config to upgrade-borgmatic-config. 2017-07-09 11:48:24 -07:00
Dan Helfman 1bcb2a8be4 More test coverage, and simplification of config generation. 2017-07-09 11:41:55 -07:00
Dan Helfman f581f4b8d9 More test coverage, and simplification of config generation. 2017-07-09 11:41:55 -07:00
Dan Helfman a16d90ff46 Adding a "does not raise" test for displaying errors. 2017-07-09 10:27:34 -07:00
Dan Helfman c7803a2814 Adding a "does not raise" test for displaying errors. 2017-07-09 10:27:34 -07:00
Dan Helfman e50fd04750 Adding test coverage report. Making tests a little less brittle. 2017-07-08 23:01:41 -07:00
Dan Helfman f4e5dc8382 Adding test coverage report. Making tests a little less brittle. 2017-07-08 23:01:41 -07:00
Dan Helfman 745de200df Basic YAML generating / validating / converting to. 2017-07-08 22:33:51 -07:00
Dan Helfman f19a40ef9c Basic YAML generating / validating / converting to. 2017-07-08 22:33:51 -07:00
Dan Helfman bff6980eee Tests for YAML config code. 2017-07-04 18:32:37 -07:00
Dan Helfman 483bd50bdf Tests for YAML config code. 2017-07-04 18:32:37 -07:00
Dan Helfman 1dc60d2856 Integrating YAML config into borgmatic and updating README. 2017-07-04 18:23:59 -07:00
Dan Helfman 5110e64e63 Integrating YAML config into borgmatic and updating README. 2017-07-04 18:23:59 -07:00
Dan Helfman 6e85940d63 Basic YAML configuration file parsing. 2017-07-04 16:52:24 -07:00
Dan Helfman 4d7556f68b Basic YAML configuration file parsing. 2017-07-04 16:52:24 -07:00
Dan Helfman e00f74ddf7 Dropped Python 2 support. Now Python 3 only. 2017-07-02 17:18:33 -07:00
Dan Helfman 9212f87735 Dropped Python 2 support. Now Python 3 only. 2017-07-02 17:18:33 -07:00
Dan Helfman 1aaf27dfb2 Changed example umask config to be more realistic. 2017-06-25 10:36:36 -07:00
Dan Helfman ebd34f1695 Changed example umask config to be more realistic. 2017-06-25 10:36:36 -07:00
Dan Helfman 87c65fb723 Removing unnecessary curlies from bash command. 2016-07-04 09:35:51 -07:00
Dan Helfman a34dccbd27 Removing unnecessary curlies from bash command. 2016-07-04 09:35:51 -07:00
Dan Helfman abb6bed459 Sample files for triggering borgmatic from a systemd timer. 2016-07-04 09:19:34 -07:00
Dan Helfman 49c4f483fd Sample files for triggering borgmatic from a systemd timer. 2016-07-04 09:19:34 -07:00
Dan Helfman 5bd1cc5580 #18: Fix for README mention of sample files not included in package. Also, added logo. 2016-07-03 22:07:53 -07:00
Dan Helfman 4447956da7 #18: Fix for README mention of sample files not included in package. Also, added logo. 2016-07-03 22:07:53 -07:00
Dan Helfman f6d2e983d9 Added tag 1.0.3 for changeset 32c6341dda9f 2016-06-23 07:13:29 -07:00
Dan Helfman 9a96a277e6 Added tag 1.0.3 for changeset 32c6341dda9f 2016-06-23 07:13:29 -07:00
Dan Helfman 6bfe524bac #21: Fix for verbosity flag not actually causing verbose output. 2016-06-23 07:13:25 -07:00
Dan Helfman a45d7bec81 #21: Fix for verbosity flag not actually causing verbose output. 2016-06-23 07:13:25 -07:00
Dan Helfman ead991dcd1 Added tag 1.0.2 for changeset 9603d13910b3 2016-06-13 12:02:37 -07:00
Dan Helfman 63c4bf3bf9 Added tag 1.0.2 for changeset 9603d13910b3 2016-06-13 12:02:37 -07:00
Dan Helfman b22b552bf3 #20: Fix for traceback when remote_path option is missing. 2016-06-13 08:53:41 -07:00
Dan Helfman ed0127df91 #20: Fix for traceback when remote_path option is missing. 2016-06-13 08:53:41 -07:00
Dan Helfman 938392b25b Restricting issues list to open issues. 2016-06-12 22:40:04 -07:00
Dan Helfman 17e9f21fb9 Restricting issues list to open issues. 2016-06-12 22:40:04 -07:00
Dan Helfman 481dbc14c3 Rename issues URL. 2016-06-12 22:37:42 -07:00
Dan Helfman 16cc77fd9d Rename issues URL. 2016-06-12 22:37:42 -07:00
Dan Helfman 600c438951 Reverting to pre-rename issues link, because that link isn't yet renamed to borgmatic. 2016-06-10 17:11:28 -07:00
Dan Helfman 5d9bb13410 Reverting to pre-rename issues link, because that link isn't yet renamed to borgmatic. 2016-06-10 17:11:28 -07:00
Dan Helfman 2e3e68d2cb Added tag 1.0.1 for changeset de2d7721cdec 2016-06-10 13:34:23 -07:00
Dan Helfman da513c1089 Added tag 1.0.1 for changeset de2d7721cdec 2016-06-10 13:34:23 -07:00
Dan Helfman 331adca23e #19: Support for Borg's --remote-path option to use an alternate Borg executable. 2016-06-10 13:31:37 -07:00
Dan Helfman 3579dbe813 #19: Support for Borg's --remote-path option to use an alternate Borg executable. 2016-06-10 13:31:37 -07:00
Dan Helfman e1e5db22f8 Making a univeral wheel that supports both Python 2 and 3. 2016-06-10 12:34:49 -07:00
Dan Helfman 8e3a2c7a85 Making a univeral wheel that supports both Python 2 and 3. 2016-06-10 12:34:49 -07:00
Dan Helfman 377e3948ff Added tag 1.0.0 for changeset 0e1fbee9358d 2016-06-10 12:15:48 -07:00
Dan Helfman 706a31f189 Added tag 1.0.0 for changeset 0e1fbee9358d 2016-06-10 12:15:48 -07:00
Dan Helfman 4533fec167 Documenting how to upgrade from atticmatic to borgmatic. 2016-06-10 11:53:45 -07:00
Dan Helfman e5c772d8a5 Documenting how to upgrade from atticmatic to borgmatic. 2016-06-10 11:53:45 -07:00
Dan Helfman 633700c0af Dropping support for Attic. 2016-06-10 11:21:53 -07:00
Dan Helfman 40a215802f Dropping support for Attic. 2016-06-10 11:21:53 -07:00
Dan Helfman 007b3e6d4e Merge pull request #12 from dawez/master
fixed typos in readme
2016-04-17 21:21:44 -07:00
Dan Helfman 5d46acbe41 Merge pull request #12 from dawez/master
fixed typos in readme
2016-04-17 21:21:44 -07:00
dawez 175761c757 fixed typos in readme 2016-04-17 22:26:07 +02:00
dawez 9dc4960277 fixed typos in readme 2016-04-17 22:26:07 +02:00
Dan Helfman c7e23fe9ed Added tag 0.1.8 for changeset dbc96d3f83bd 2016-04-10 16:01:18 -07:00
Dan Helfman 7644a38574 Added tag 0.1.8 for changeset dbc96d3f83bd 2016-04-10 16:01:18 -07:00
Dan Helfman 9e45da75cb Cutting a release. 2016-04-10 16:01:05 -07:00
Dan Helfman 2359135327 Cutting a release. 2016-04-10 16:01:05 -07:00
Dan Helfman 0ea5824427 Switching from the no-longer-maintained nose test runner to pytest. 2016-04-10 15:59:36 -07:00
Dan Helfman df9cc6a7d2 Switching from the no-longer-maintained nose test runner to pytest. 2016-04-10 15:59:36 -07:00
Dan Helfman 5b66dc69a1 Refreshing flexmock version in test requirements. 2016-04-10 15:48:10 -07:00
Dan Helfman 448c2593ed Refreshing flexmock version in test requirements. 2016-04-10 15:48:10 -07:00
Dan Helfman 8210172d7f Fixing "check" backend tests to support new use of stderr=STDOUT. 2016-04-10 15:46:43 -07:00
Dan Helfman a364369f25 Fixing "check" backend tests to support new use of stderr=STDOUT. 2016-04-10 15:46:43 -07:00
Dan Helfman 82e8dae948 At verbosity zero, suppressing Borg check spew to stderr about "Checking segments". 2016-04-10 15:29:42 -07:00
Dan Helfman e430b8c281 At verbosity zero, suppressing Borg check spew to stderr about "Checking segments". 2016-04-10 15:29:42 -07:00
Dan Helfman fa87aed263 Normalizing recent changes. No new content. 2016-04-10 15:27:21 -07:00
Dan Helfman 219408647c Normalizing recent changes. No new content. 2016-04-10 15:27:21 -07:00
Dan Helfman c3b4cb21ed Fixed links to Borg documentation. 2016-04-10 10:23:32 -07:00
Dan Helfman 5bffa35741 Fixed links to Borg documentation. 2016-04-10 10:23:32 -07:00
Dan Helfman 030b321e39 Merge pull request #11 from jangondol/patch-1
Fix broken link to Borg quickstart
2016-04-10 10:21:23 -07:00
Dan Helfman a938a2ad61 Merge pull request #11 from jangondol/patch-1
Fix broken link to Borg quickstart
2016-04-10 10:21:23 -07:00
Jan Gondol 15bf273e6e Fix broken link to Borg quickstart 2016-04-06 14:54:06 +02:00
Jan Gondol 37b6f043dd Fix broken link to Borg quickstart 2016-04-06 14:54:06 +02:00
Dan Helfman cf545ae93a Mocking out glob() in test so it doesn't hit the filesystem, and simplifying comprehension. 2016-02-13 16:41:17 -08:00
Dan Helfman 0c97dba5f1 Mocking out glob() in test so it doesn't hit the filesystem, and simplifying comprehension. 2016-02-13 16:41:17 -08:00
Dan Helfman 45a2b9cded Merge pull request #5 from ypid/support-file-globs
Added support for file globs in source_directories.
2016-02-13 16:32:05 -08:00
Dan Helfman f149fa4b4b Merge pull request #5 from ypid/support-file-globs
Added support for file globs in source_directories.
2016-02-13 16:32:05 -08:00
Robin Schneider 953d08ba63 Made globing for source_directories the default.
Don’t remove non existing files/directories from the list and let
attic/borg handle this.
2016-02-13 21:10:05 +01:00
Robin Schneider 88da0c3039 Added support for file globs in source_directories.
source_directories_glob can be used to enable glob support (defaults to
disabled).
2016-02-13 21:07:07 +01:00
Robin Schneider f669e31305 Made globing for source_directories the default.
Don’t remove non existing files/directories from the list and let
attic/borg handle this.
2016-02-13 21:05:34 +01:00
Dan Helfman 0012e0cdea Support borg create --umask. (Merge PR from ypid.) 2016-02-13 10:59:43 -08:00
Dan Helfman dd2be365b1 Support borg create --umask. (Merge PR from ypid.) 2016-02-13 10:59:43 -08:00
Dan Helfman 049f9c8853 Added support for --one-file-system for Borg. 2016-02-13 10:43:31 -08:00
Dan Helfman d6585811d6 Added support for --one-file-system for Borg. 2016-02-13 10:43:31 -08:00
Dan Helfman 31482ee559 Merge pull request #8 from ypid/fixed-source-split-bug
Use /\s+/ to split source_directories to handle 1+ spaces.
2016-02-13 10:29:31 -08:00
Dan Helfman 14a277a64f Merge pull request #8 from ypid/fixed-source-split-bug
Use /\s+/ to split source_directories to handle 1+ spaces.
2016-02-13 10:29:31 -08:00
Dan Helfman 1ffff3255a Merge pull request #6 from ypid/added-gitignore
Added .gitignore file.
2016-02-13 10:27:55 -08:00
Dan Helfman 9723dbdd4c Merge pull request #6 from ypid/added-gitignore
Added .gitignore file.
2016-02-13 10:27:55 -08:00
Robin Schneider 9e52be6ffd Use /\s+/ to split source_directories to handle 1+ spaces.
This bug is can be quite annoying because when you accidentally used
something like:

```ini
[location]
source_directories: backup_one  backup_two
;                              A (Additional space here)
```

It would call Attic/Borg with ('backup_one', '', 'backup_two') which in
turn backups your whole $PWD.
2016-02-07 23:30:54 +01:00
Robin Schneider 978096b402 Added .gitignore file. 2016-02-07 22:14:57 +01:00
Robin Schneider 9f5c5c8e13 Use /\s+/ to split source_directories to handle 1+ spaces.
This bug is can be quite annoying because when you accidentally used
something like:

```ini
[location]
source_directories: backup_one  backup_two
;                              A (Additional space here)
```

It would call Attic/Borg with ('backup_one', '', 'backup_two') which in
turn backups your whole $PWD.
2016-01-31 11:42:07 +01:00
Robin Schneider 4453f41967 Added .gitignore file. 2016-01-30 20:24:29 +01:00
Robin Schneider e4cf193cd7 Added support for file globs in source_directories.
source_directories_glob can be used to enable glob support (defaults to
disabled).
2016-01-25 23:52:16 +01:00
Dan Helfman cc6aa7af05 Merge pull request #3 from ypid/README-ini-hightlighting
Also allow the INI example to be highlighted on GitHub.
2016-01-24 20:52:01 -08:00
Dan Helfman e1605ae6ab Merge pull request #3 from ypid/README-ini-hightlighting
Also allow the INI example to be highlighted on GitHub.
2016-01-24 20:52:01 -08:00
Robin Schneider 32858fb0b4 Also allow the INI example to be highlighted on GitHub. 2016-01-20 13:11:15 +01:00
Robin Schneider d6ea9b6658 Also allow the INI example to be highlighted on GitHub. 2016-01-20 13:11:15 +01:00
Dan Helfman e59845d4e1 Added tag github/master for changeset 28434dd0440c 2015-11-08 17:06:48 -08:00
Dan Helfman 147996b234 Added tag github/master for changeset 28434dd0440c 2015-11-08 17:06:48 -08:00
Dan Helfman 9437e95849 Merge. 2015-11-08 17:04:49 -08:00
Dan Helfman ac374f89d2 Merge. 2015-11-08 17:04:49 -08:00
Dan Helfman 3a3851d2a5 Removed tag github/yaml_config_files 2015-11-08 17:04:14 -08:00
Dan Helfman ddde37ff59 Removed tag github/yaml_config_files 2015-11-08 17:04:14 -08:00
Dan Helfman 80318e6e30 Removed tag github/yaml_config_files 2015-11-08 17:03:40 -08:00
Dan Helfman 278aaabeb9 Removed tag github/yaml_config_files 2015-11-08 17:03:40 -08:00
Dan Helfman 6756ca8311 Merge pull request #2 from ThomasWaldmann/patch-1
fixed typo in README
2015-10-20 18:33:16 -07:00
Dan Helfman 06cdc2c46c Merge pull request #2 from ThomasWaldmann/patch-1
fixed typo in README
2015-10-20 18:33:16 -07:00
TW fa7955b8cf fixed typo in README 2015-10-20 23:08:43 +02:00
TW e5586f7d87 fixed typo in README 2015-10-20 23:08:43 +02:00
Dan Helfman 944c0212c3 Added tag 0.1.7 for changeset 5a458ebef804 2015-09-06 16:40:46 -07:00
Dan Helfman d044504693 Added tag 0.1.7 for changeset 5a458ebef804 2015-09-06 16:40:46 -07:00
Dan Helfman ec416cb152 Revving version. 2015-09-06 16:40:39 -07:00
Dan Helfman 0da1c6ec7b Better error message when configuration file is missing. 2015-09-06 15:55:14 -07:00
Dan Helfman f54acc9bbf #11: Fixed parsing of punctuation in configuration file. 2015-09-06 15:33:56 -07:00
Dan Helfman d86faa30d3 Added tag 0.1.6 for changeset 4c63f3d90ec2 2015-09-02 22:48:14 -07:00
Dan Helfman a44212ff00 #9: New configuration option for the encryption passphrase. #10: Support for Borg's new archive compression feature. 2015-09-02 22:48:07 -07:00
Dan Helfman 178e56e77b Adding documentation note about logging into the issue tracker in order to create issues. 2015-09-02 18:45:15 -07:00
Dan Helfman 1cb44da6b3 Adding build to hgignore. 2015-08-09 11:04:57 -07:00
Dan Helfman 7e8123379f Added tag 0.1.5 for changeset 0afff209b902 2015-08-09 10:59:40 -07:00
Dan Helfman c554d1d36d Changes to support release on PyPI. Now pip installable by name! 2015-08-09 10:59:27 -07:00
Dan Helfman a56529871c Added tag 0.1.4 for changeset e58246fc92bb 2015-07-30 08:13:32 -07:00
Dan Helfman 820492f9a9 Adding version test. 2015-07-30 08:13:27 -07:00
Dan Helfman f803198f14 Added tag 0.1.4 for changeset 6dda59c12de8 2015-07-30 08:12:36 -07:00
Dan Helfman f59bc98d79 Adding test that setup.py version matches release version. 2015-07-30 08:12:31 -07:00
Dan Helfman 158e889deb Added tag 0.1.3 for changeset acc7fb61566f 2015-07-27 21:48:21 -07:00
Dan Helfman 2444c4b372 #1: Add support for "borg check --last N" to Borg backend. 2015-07-27 21:47:52 -07:00
Dan Helfman 9ecc207139 #6: Fixing example config file to use valid keep_within value. 2015-07-27 19:06:39 -07:00
Dan Helfman 803fd3a851 Linking to both Attic and Borg check docs from sample config. 2015-07-26 22:02:43 -07:00
Dan Helfman e18cfd6c80 Removing some annoying Pelican metadata from docs. 2015-07-26 21:29:14 -07:00
Dan Helfman 7a287ba289 Added tag 0.1.2 for changeset 83067f995dd3 2015-07-26 21:06:06 -07:00
Dan Helfman 3f99dc6db2 As a convenience to new users, allow a missing default excludes file. 2015-07-26 21:06:03 -07:00
Dan Helfman 837d25cfd8 Linking to both Attic and Borg prune docs from sample config. 2015-07-26 20:57:31 -07:00
Dan Helfman fedc75cafc New issue tracker, linked from documentation. 2015-07-21 21:29:40 -07:00
Dan Helfman 29106b9645 Added tag 0.1.1 for changeset 7b6c87dca7ea 2015-07-18 23:49:06 -07:00
Dan Helfman 4f6337c46e Bumping version. 2015-07-18 23:48:58 -07:00
Dan Helfman 55183bf890 Added tag 0.1.1 for changeset ac5dfa01e9d1 2015-07-18 18:44:14 -07:00
Dan Helfman cc09d7fc10 Adding borgmatic cron example. 2015-07-18 18:44:11 -07:00
Dan Helfman 96ce9309e0 Added tag 0.1.0 for changeset 38d72677343f 2015-07-18 18:39:33 -07:00
Dan Helfman f2f8503e77 New "borgmatic" command to support Borg backup software, a fork of Attic. 2015-07-18 18:35:29 -07:00
Dan Helfman bdce17ae71 Added tag 0.0.7 for changeset cf4c7065f071 2015-07-17 21:58:58 -07:00
Dan Helfman b501a568aa Flag for multiple levels of verbosity: some, and lots. 2015-07-17 21:58:50 -07:00
Dan Helfman 1578b44536 Improved mocking of Python builtins in unit tests. 2015-06-14 11:00:46 -07:00
Dan Helfman 932145f20b Adding some explanitory text about consistency checks to README example. 2015-05-10 22:06:48 -07:00
Dan Helfman 6c2e668262 Added tag 0.0.6 for changeset 7ea93ca83f42 2015-05-10 22:00:34 -07:00
Dan Helfman df2d059af2 New configuration section for customizing which Attic consistency checks run, if any. 2015-05-10 22:00:31 -07:00
Dan Helfman 301eb4926e Passing through command-line options from tox to nosetests. 2015-03-15 11:15:40 -07:00
Dan Helfman 897046cf51 Added tag 0.0.5 for changeset a03495a8e8b4 2015-03-15 10:47:58 -07:00
Dan Helfman 645c66c98a Re-fixing version. 2015-03-15 10:47:49 -07:00
Dan Helfman 811678396a Added tag 0.0.5 for changeset 569aef47a9b2 2015-03-15 10:46:55 -07:00
Dan Helfman fd94debba6 Added tag 0.0.5 for changeset aa8a807f4ba2 2015-03-15 10:44:25 -07:00
Dan Helfman 851dd69785 Backout out "helpful" error message that broke --verbose. 2015-03-15 10:44:18 -07:00
Dan Helfman 6430e3799a Added tag 0.0.5 for changeset b31d51b63370 2015-03-15 10:39:08 -07:00
Dan Helfman c15a89b9f8 Added tag 0.0.4 for changeset 4bb2e81fc770 2015-03-15 10:19:12 -07:00
Dan Helfman 0bf033de06 Releasing 0.0.4. 2015-03-15 10:15:03 -07:00
Dan Helfman 5bc7c04cdf Now using tox to run tests against multiple versions of Python in one go. 2015-03-15 10:14:30 -07:00
Dan Helfman a5eda0d305 Fixing Python 3 test incompatibility with builtins. 2015-03-15 10:14:16 -07:00
Dan Helfman 04216922c4 Bumping setup.py version. 2015-03-15 09:52:40 -07:00
Dan Helfman aa482d088c Added nosetests config file (setup.cfg) with defaults. 2015-03-15 09:41:58 -07:00
Dan Helfman b755d09b0a Added a troubleshooting section with steps to deal with broken pipes. 2015-02-28 11:03:22 -08:00
Dan Helfman 89703111d0 Updating install instructions so you can upgrade from one release of atticmatic to the next. 2015-02-14 09:31:42 -08:00
Dan Helfman 0f21919626 Added tag 0.0.3 for changeset 7730ae34665c 2015-02-14 09:24:15 -08:00
Dan Helfman e3fa18b892 After pruning, run attic's consistency checks on all archives. 2015-02-14 09:23:40 -08:00
Dan Helfman 6d4e40a819 Correcting doc string based on updated command-line arguments source. 2014-12-20 11:42:27 -08:00
Dan Helfman de40570db4 Integration tests for argument parsing. 2014-12-20 11:37:25 -08:00
Dan Helfman 2a45f43167 Adding a note about repository encryption. 2014-12-20 10:56:03 -08:00
Dan Helfman f8c6da6ff4 Added tag 0.0.2 for changeset 467d3a3ce918 2014-12-06 18:35:28 -08:00
Dan Helfman 63018fad4e Configuration support for additional attic prune flags: keep_within, keep_hourly, keep_yearly, and prefix. 2014-12-06 18:35:20 -08:00
Dan Helfman 2038354b61 For convenience, adding some short-form arguments in addition to the long-form arguments. 2014-12-01 22:47:51 -08:00
Dan Helfman 9d7db93655 Adding sudo to installation of test dependencies, for consistency with installation of main dependencies. 2014-12-01 22:39:11 -08:00
Dan Helfman f4d8469085 Preventing ConfigParser from swallowing file read IOErrors, so that the user gets a more useful message. 2014-12-01 22:35:25 -08:00
Dan Helfman 273ca83f6b Fixing configparser import for Python 3. 2014-12-01 22:14:35 -08:00
Dan Helfman 41008b3ef4 Expanding description. 2014-12-01 20:36:43 -08:00
Dan Helfman 16ba9ef4c6 Adding authors/contributors file. 2014-12-01 20:30:07 -08:00
Dan Helfman 25b78d6de4 Python 3 ConfigParser compatibility. 2014-12-01 20:26:19 -08:00
Dan Helfman d311e90792 Python 3 compatible exceptions. 2014-12-01 20:23:29 -08:00
Dan Helfman 0fc9acce7c Mentioning source code location explicitly. 2014-12-01 20:22:49 -08:00
Dan Helfman 5a3c66a667 Saving README when rendered such that it can be served easily. 2014-12-01 20:15:21 -08:00
Dan Helfman f8387c7bd1 Markdown metadata and link formatting updates. 2014-12-01 19:49:25 -08:00
Dan Helfman 181fed83df Renaming README to indicate markdown. 2014-11-27 09:34:13 -08:00
Dan Helfman 243e2b698d Playing nicely with markdown. 2014-11-27 09:29:31 -08:00
Dan Helfman f2f4ce38c9 Adding note about making etc configuration directory before copying a file to it. 2014-11-26 20:15:21 -08:00
Dan Helfman 965ec51171 Merge pull request #1 from hajs/master
fixed README: copy cronjob to /etc/cron.d instead of /etc/init.d
2014-11-26 08:03:47 -08:00
Henning Schroder cd11e89fde fixed README: copy cronjob to /etc/cron.d instead of /etc/init.d (like comment in sample/atticmatic.cron correctly explains) 2014-11-26 13:04:14 +01:00
Dan Helfman 866c707f58 Updating README with clarifications and examples. 2014-11-25 16:01:59 -08:00
Dan Helfman c459c42ffc Note about hosting arrangement. 2014-11-18 18:32:16 -08:00
Dan Helfman 0bca430c02 Merge with Github head! 2014-11-18 18:28:20 -08:00
Dan Helfman 7b3efddcbd Adding GPL v3 license. 2014-11-18 18:22:51 -08:00
Dan Helfman cdb4035bab Unit tests for attic invocation code. 2014-11-17 22:19:34 -08:00
Dan Helfman c7d68246cd Adding unit tests for config module. 2014-11-17 21:57:44 -08:00
Dan Helfman 8784e19e45 Adding contact info. 2014-11-17 18:35:47 -08:00
Dan Helfman 48523810ec Adding PATH necessary to find the attic binary. 2014-11-01 17:46:04 -07:00
Dan Helfman 6dff335c8b Initial import. 2014-10-30 22:34:03 -07:00
160 changed files with 13453 additions and 1100 deletions
+2
View File
@@ -0,0 +1,2 @@
.git
.tox
+67
View File
@@ -0,0 +1,67 @@
---
kind: pipeline
name: python-3-5-alpine-3-10
steps:
- name: build
image: python:3.5-alpine3.10
pull: always
commands:
- scripts/run-tests
---
kind: pipeline
name: python-3-6-alpine-3-10
steps:
- name: build
image: python:3.6-alpine3.10
pull: always
commands:
- scripts/run-tests
---
kind: pipeline
name: python-3-7-alpine-3-10
steps:
- name: build
image: python:3.7-alpine3.10
pull: always
commands:
- scripts/run-tests
---
kind: pipeline
name: python-3-7-alpine-3-7
steps:
- name: build
image: python:3.7-alpine3.7
pull: always
commands:
- scripts/run-tests
---
kind: pipeline
name: python-3-8-alpine-3-10
steps:
- name: build
image: python:3.8-alpine3.10
pull: always
commands:
- scripts/run-tests
---
kind: pipeline
name: documentation
steps:
- name: build
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: witten/borgmatic-docs
dockerfile: docs/Dockerfile
when:
branch:
- master
+42
View File
@@ -0,0 +1,42 @@
const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const inclusiveLangPlugin = require("@11ty/eleventy-plugin-inclusive-language");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(pluginSyntaxHighlight);
eleventyConfig.addPlugin(inclusiveLangPlugin);
let markdownIt = require("markdown-it");
let markdownItAnchor = require("markdown-it-anchor");
let markdownItReplaceLink = require("markdown-it-replace-link");
let markdownItOptions = {
html: true,
breaks: false,
linkify: true,
replaceLink: function (link, env) {
if (process.env.NODE_ENV == "production") {
return link;
}
return link.replace('https://torsion.org/borgmatic/', 'http://localhost:8080/');
}
};
let markdownItAnchorOptions = {
permalink: true,
permalinkClass: "direct-link"
};
eleventyConfig.setLibrary(
"md",
markdownIt(markdownItOptions)
.use(markdownItAnchor, markdownItAnchorOptions)
.use(markdownItReplaceLink)
);
return {
templateFormats: [
"md",
"txt"
]
}
};
+31
View File
@@ -0,0 +1,31 @@
#### What I'm trying to do and why
#### Steps to reproduce (if a bug)
Include (sanitized) borgmatic configuration files if applicable.
#### Actual behavior (if a bug)
Include (sanitized) `--verbosity 2` output if applicable.
#### Expected behavior (if a bug)
#### Other notes / implementation ideas
#### Environment
**borgmatic version:** [version here]
Use `sudo borgmatic --version` or `sudo pip show borgmatic | grep ^Version`
**borgmatic installation method:** [e.g., Debian package, Docker container, etc.]
**Borg version:** [version here]
Use `sudo borg --version`
**Python version:** [version here]
Use `python3 --version`
**operating system and version:** [OS here]
+11
View File
@@ -0,0 +1,11 @@
*.egg-info
*.pyc
*.swp
.cache
.coverage
.pytest_cache
.tox
__pycache__
build/
dist/
pip-wheel-metadata/
-7
View File
@@ -1,7 +0,0 @@
syntax: glob
*.egg-info
*.pyc
*.swp
.tox
build
dist
-23
View File
@@ -1,23 +0,0 @@
467d3a3ce9185e84ee51ca9156499162efd94f9a 0.0.2
7730ae34665c0dedf46deab90b32780abf6dbaff 0.0.3
4bb2e81fc77038be4499b7ea6797ab7d109460e0 0.0.4
b31d51b633701554e84f996cc0c73bad2990780b 0.0.5
b31d51b633701554e84f996cc0c73bad2990780b 0.0.5
aa8a807f4ba28f0652764ed14713ffea2fd6922d 0.0.5
aa8a807f4ba28f0652764ed14713ffea2fd6922d 0.0.5
569aef47a9b25c55b13753f94706f5d330219995 0.0.5
569aef47a9b25c55b13753f94706f5d330219995 0.0.5
a03495a8e8b471da63b5e2ae79d3ff9065839c2a 0.0.5
7ea93ca83f426ec0a608a68580c72c0775b81f86 0.0.6
cf4c7065f0711deda1cba878398bc05390e2c3f9 0.0.7
38d72677343f0a5d6845f4ac50d6778397083d45 0.1.0
ac5dfa01e9d14d09845f5e94c2c679e21c5eb2f9 0.1.1
ac5dfa01e9d14d09845f5e94c2c679e21c5eb2f9 0.1.1
7b6c87dca7ea312b2257ac1b46857b3f8c56b39c 0.1.1
83067f995dd391e38544a7722dc3b254b59c5521 0.1.2
acc7fb61566fe8028c179f43ecc735c851220b06 0.1.3
6dda59c12de88f060eb7244e6d330173985a9639 0.1.4
6dda59c12de88f060eb7244e6d330173985a9639 0.1.4
e58246fc92bb22c2b2fd8b86a1227de69d2d0315 0.1.4
0afff209b902698c2266986129d6dc9f5f913101 0.1.5
4c63f3d90ec2bf6af1714a3acec84654a7c9edf3 0.1.6
+10
View File
@@ -1,4 +1,14 @@
Dan Helfman <witten@torsion.org>: Main developer
Alexander Görtz: Python 3 compatibility
Florian Lindner: Logging rewrite
Henning Schroeder: Copy editing
Johannes Feichtner: Support for user hooks
Michele Lazzeri: Custom archive names
Nick Whyte: Support prefix filtering for archive consistency checks
newtonne: Read encryption password from external file
Robin `ypid` Schneider: Support additional options of Borg and add validate-borgmatic-config command
Scott Squires: Custom archive names
Thomas LÉVEIL: Support for a keep_minutely prune option. Support for the --json option
Any many others! See the output of "git log".
+2
View File
@@ -0,0 +1,2 @@
include borgmatic/config/schema.yaml
graft sample/systemd
+445 -4
View File
@@ -1,12 +1,453 @@
1.4.9
* #228: Database dump hooks for MySQL/MariaDB, so you can easily dump your databases before backups
run.
* #243: Fix repository does not exist error with "borgmatic extract" when repository is remote.
1.4.8
* Monitor backups with Cronhub hook integration. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronhub-hook
* Fix Healthchecks/Cronitor hooks to skip actions when the borgmatic "--dry-run" flag is used.
1.4.7
* #238: In documentation, clarify when Healthchecks/Cronitor hooks fire in relation to other hooks.
* #239: Upgrade your borgmatic configuration to get new options and comments via
"generate-borgmatic-config --source". See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/upgrade/#upgrading-your-configuration
1.4.6
* Verbosity level "-1" for even quieter output: Errors only (#236).
1.4.5
* Log to file instead of syslog via command-line "--log-file" flag (#233).
1.4.4
* #234: Support for Borg --keep-exclude-tags and --exclude-nodump options.
1.4.3
* Monitor backups with Cronitor hook integration. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronitor-hook
1.4.2
* Extract files to a particular directory via "borgmatic extract --destination" flag.
* Rename "borgmatic extract --restore-path" flag to "--path" to reduce confusion with the separate
"borgmatic restore" action. Any uses of "--restore-path" will continue working.
1.4.1
* #229: Restore backed up PostgreSQL databases via "borgmatic restore" action. See the
documentation for more information:
https://torsion.org/borgmatic/docs/how-to/backup-your-databases/
* Documentation on how to develop borgmatic's documentation:
https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/#documentation-development
1.4.0
* #225: Database dump hooks for PostgreSQL, so you can easily dump your databases before backups
run.
* #230: Rename "borgmatic list --pattern-from" flag to "--patterns-from" to match Borg.
1.3.26
* #224: Fix "borgmatic list --successful" with a slightly better heuristic for listing successful
(non-checkpoint) archives.
1.3.25
* #223: Dead man's switch to detect when backups start failing silently, implemented via
healthchecks.io hook integration. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#healthchecks-hook
* Documentation on monitoring and alerting options for borgmatic backups:
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/
* Automatically rewrite links when developing on documentation locally.
1.3.24
* #86: Add "borgmatic list --successful" flag to only list successful (non-checkpoint) archives.
* Add a suggestion form to all documentation pages, so users can submit ideas for improving the
documentation.
* Update documentation link to community Arch Linux borgmatic package.
1.3.23
* #174: More detailed error alerting via runtime context available in "on_error" hook.
1.3.22
* #144: When backups to one of several repositories fails, keep backing up to the other
repositories and report errors afterwards.
1.3.21
* #192: User-defined hooks for global setup or cleanup that run before/after all actions. See the
documentation for more information:
https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/
1.3.20
* #205: More robust sample systemd service: boot delay, network dependency, lowered CPU/IO
priority, etc.
* #221: Fix "borgmatic create --progress" output so that it updates on the console in real-time.
1.3.19
* #219: Fix visibility of "borgmatic prune --stats" output.
1.3.18
* #220: Fix regression of argument parsing for default actions.
1.3.17
* #217: Fix error with "borgmatic check --only" command-line flag with "extract" consistency check.
1.3.16
* #210: Support for Borg check --verify-data flag via borgmatic "data" consistency check.
* #210: Override configured consistency checks via "borgmatic check --only" command-line flag.
* When generating sample configuration with generate-borgmatic-config, add a space after each "#"
comment indicator.
1.3.15
* #208: Fix for traceback when the "checks" option has an empty value.
* #209: Bypass Borg error about a moved repository via "relocated_repo_access_is_ok" option in
borgmatic storage configuration section.
* #213: Reorder arguments passed to Borg to fix duplicate directories when using Borg patterns.
* #214: Fix for hook erroring with exit code 1 not being interpreted as an error.
1.3.14
* #204: Do not treat Borg warnings (exit code 1) as failures.
* When validating configuration files, require strings instead of allowing any scalar type.
1.3.13
* #199: Add note to documentation about using spaces instead of tabs for indentation, as YAML does
not allow tabs.
* #203: Fix compatibility with ruamel.yaml 0.16.x.
* If a "prefix" option in borgmatic's configuration has an empty value (blank or ""), then disable
default prefix.
1.3.12
* Only log to syslog when run from a non-interactive console (e.g. a cron job).
* Remove unicode byte order mark from syslog output so it doesn't show up as a literal in rsyslog
output. See discussion on #197.
1.3.11
* #193: Pass through several "borg list" and "borg info" flags like --short, --format, --sort-by,
--first, --last, etc. via borgmatic command-line flags.
* Add borgmatic info --repository and --archive command-line flags to display info for individual
repositories or archives.
* Support for Borg --noatime, --noctime, and --nobirthtime flags via corresponding options in
borgmatic configuration location section.
1.3.10
* #198: Fix for Borg create error output not showing up at borgmatic verbosity level zero.
1.3.9
* #195: Switch to command-line actions as more traditional sub-commands, e.g. "borgmatic create",
"borgmatic prune", etc. However, the classic dashed options like "--create" still work!
1.3.8
* #191: Disable console color via "color" option in borgmatic configuration output section.
1.3.7
* #196: Fix for unclear error message for invalid YAML merge include.
* #197: Don't color syslog output.
* Change default syslog verbosity to show errors only.
1.3.6
* #53: Log to syslog in addition to existing console logging. Add --syslog-verbosity flag to
customize the log level. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/
* #178: Look for .yml configuration file extension in addition to .yaml.
* #189: Set umask used when executing hooks via "umask" option in borgmatic hooks section.
* Remove Python cache files before each Tox run.
* Add #borgmatic Freenode IRC channel to documentation.
* Add Borg/borgmatic hosting providers section to documentation.
* Add files for building documentation into a Docker image for web serving.
* Upgrade project build server from Drone 0.8 to 1.1.
* Build borgmatic documentation during continuous integration.
* We're nearly at 500 ★s on GitHub. We can do this!
1.3.5
* #153: Support for various Borg directory environment variables (BORG_CONFIG_DIR, BORG_CACHE_DIR,
etc.) via options in borgmatic's storage configuration.
* #177: Fix for regression with missing verbose log entries.
1.3.4
* Part of #125: Color borgmatic (but not Borg) output when using an interactive terminal.
* #166: Run tests for all installed versions of Python.
* #168: Update README with continuous integration badge.
* #169: Automatically sort Python imports in code.
* Document installing borgmatic with pip install --user instead of a system Python install.
* Get more reproducible builds by pinning the versions of pip and tox used to run tests.
* Factor out build/test configuration from tox.ini file.
1.3.3
* Add validate-borgmatic-config command, useful for validating borgmatic config generated by
configuration management or even edited by hand.
1.3.2
* #160: Fix for hooks executing when using --dry-run. Now hooks are skipped during a dry run.
1.3.1
* #155: Fix for invalid JSON output when using multiple borgmatic configuration files.
* #157: Fix for seemingly random filename ordering when running through a directory of
configuration files.
* Fix for empty JSON output when using --create --json.
* Now capturing Borg output only when --json flag is used. Previously, borgmatic delayed Borg
output even without the --json flag.
1.3.0
* #148: Configuration file includes and merging via "!include" tag to support reuse of common
options across configuration files.
1.2.18
* #147: Support for Borg create/extract --numeric-owner flag via "numeric_owner" option in
borgmatic's location section.
1.2.17
* #140: List the files within an archive via --list --archive option.
1.2.16
* #119: Include a sample borgmatic configuration file in the documentation.
* #123: Support for Borg archive restoration via borgmatic --extract command-line flag.
* Refactor documentation into multiple separate pages for clarity and findability.
* Organize options within command-line help into logical groups.
* Exclude tests from distribution packages.
1.2.15
* #127: Remove date echo from schema example, as it's not a substitute for real logging.
* #132: Leave exclude_patterns glob expansion to Borg, since doing it in borgmatic leads to
confusing behavior.
* #136: Handle and format validation errors raised during argument parsing.
* #138: Allow use of --stats flag when --create or --prune flags are implied.
1.2.14
* #103: When generating sample configuration with generate-borgmatic-config, document the defaults
for each option.
* #116: When running multiple configuration files, attempt all configuration files even if one of
them errors. Log a summary of results at the end.
* Add borgmatic --version command-line flag to get the current installed version number.
1.2.13
* #100: Support for --stats command-line flag independent of --verbosity.
* #117: With borgmatic --init command-line flag, proceed without erroring if a repository already
exists.
1.2.12
* #110: Support for Borg repository initialization via borgmatic --init command-line flag.
* #111: Update Borg create --filter values so a dry run lists files to back up.
* #113: Update README with link to a new/forked Docker image.
* Prevent deprecated --excludes command-line option from being used.
* Refactor README a bit to flow better for first-time users.
* Update README with a few additional borgmatic packages (Debian and Ubuntu).
1.2.11
* #108: Support for Borg create --progress via borgmatic command-line flag.
1.2.10
* #105: Support for Borg --chunker-params create option via "chunker_params" option in borgmatic's
storage section.
1.2.9
* #102: Fix for syntax error that occurred in Python 3.5 and below.
* Make automated tests support running in Python 3.5.
1.2.8
* #73: Enable consistency checks for only certain repositories via "check_repositories" option in
borgmatic's consistency configuration. Handy for large repositories that take forever to check.
* Include link to issue tracker within various command output.
* Run continuous integration tests on a matrix of Python and Borg versions.
1.2.7
* #98: Support for Borg --keep-secondly prune option.
* Use Black code formatter and Flake8 code checker as part of running automated tests.
* Add an end-to-end automated test that actually integrates with Borg.
* Set up continuous integration for borgmatic automated tests on projects.evoworx.org.
1.2.6
* Fix generated configuration to also include a "keep_daily" value so pruning works out of the
box.
1.2.5
* #57: When generating sample configuration with generate-borgmatic-config, comment out all
optional configuration so as to streamline the initial configuration process.
1.2.4
* Fix for archive checking traceback due to parameter mismatch.
1.2.3
* #64, #90, #92: Rewrite of logging system. Now verbosity flags passed to Borg are derived from
borgmatic's log level. Note that the output of borgmatic might slightly change.
* Part of #80: Support for Borg create --read-special via "read_special" option in borgmatic's
location configuration.
* #87: Support for Borg create --checkpoint-interval via "checkpoint_interval" option in
borgmatic's storage configuration.
* #88: Fix declared pykwalify compatibility version range in setup.py to prevent use of ancient
versions of pykwalify with large version numbers.
* #89: Pass --show-rc option to Borg when at highest verbosity level.
* #94: Support for Borg --json option via borgmatic command-line to --create archives.
1.2.2
* #85: Fix compatibility issue between pykwalify and ruamel.yaml 0.15.52, which manifested in
borgmatic as a pykwalify RuleError.
1.2.1
* Skip before/after backup hooks when only doing --prune, --check, --list, and/or --info.
* #71: Support for XDG_CONFIG_HOME environment variable for specifying alternate user ~/.config/
path.
* #74, #83: Support for Borg --json option via borgmatic command-line to --list archives or show
archive --info in JSON format, ideal for programmatic consumption.
* #38, #76: Upgrade ruamel.yaml compatibility version range and fix support for Python 3.7.
* #77: Skip non-"*.yaml" config filenames in /etc/borgmatic.d/ so as not to parse backup files,
editor swap files, etc.
* #81: Document user-defined hooks run before/after backup, or on error.
* Add code style guidelines to the documention.
1.2.0
* #61: Support for Borg --list option via borgmatic command-line to list all archives.
* #61: Support for Borg --info option via borgmatic command-line to display summary information.
* #62: Update README to mention other ways of installing borgmatic.
* Support for Borg --prefix option for consistency checks via "prefix" option in borgmatic's
consistency configuration.
* Add introductory screencast link to documentation.
* #59: Ignore "check_last" and consistency "prefix" when "archives" not in consistency checks.
* #60: Add "Persistent" flag to systemd timer example.
* #63: Support for Borg --nobsdflags option to skip recording bsdflags (e.g. NODUMP, IMMUTABLE) in
archive.
* #69: Support for Borg prune --umask option using value of existing "umask" option in borgmatic's
storage configuration.
* Update tox.ini to only assume Python 3.x instead of Python 3.4 specifically.
* Add ~/.config/borgmatic/config.yaml to default configuration path probing.
* Document how to develop on and contribute to borgmatic.
1.1.15
* Support for Borg BORG_PASSCOMMAND environment variable to read a password from an external file.
* Fix for Borg create error when using borgmatic's --dry-run and --verbosity options together.
Work-around for behavior introduced in Borg 1.1.3: https://github.com/borgbackup/borg/issues/3298
* #55: Fix for missing tags/releases on Gitea and GitHub project hosting.
* #56: Support for Borg --lock-wait option for the maximum wait for a repository/cache lock.
* #58: Support for using tilde in exclude_patterns to reference home directory.
1.1.14
* #49: Fix for typo in --patterns-from option.
* #47: Support for Borg --dry-run option via borgmatic command-line.
1.1.13
* #54: Fix for incorrect consistency check flags passed to Borg when all three checks ("repository",
"archives", and "extract") are specified in borgmatic configuration.
* #48: Add "local_path" to configuration for specifying an alternative Borg executable path.
* #49: Support for Borg experimental --patterns-from and --patterns options for specifying mixed
includes/excludes.
* Moved issue tracker from Taiga to integrated Gitea tracker at
https://projects.torsion.org/witten/borgmatic/issues
1.1.12
* #46: Declare dependency on pykwalify 1.6 or above, as older versions yield "Unknown key: version"
rule errors.
* Support for Borg --keep-minutely prune option.
1.1.11
* #26: Add "ssh_command" to configuration for specifying a custom SSH command or options.
* Fix for incorrect /etc/borgmatic.d/ configuration path probing on macOS. This problem manifested
as an error on startup: "[Errno 2] No such file or directory: '/etc/borgmatic.d'".
1.1.10
* Pass several Unix signals through to child processes like Borg. This means that Borg now properly
shuts down if borgmatic is terminated (e.g. due to a system suspend).
* #30: Support for using tilde in repository paths to reference home directory.
* #43: Support for Borg --files-cache option for setting the files cache operation mode.
* #45: Support for Borg --remote-ratelimit option for limiting upload rate.
* Log invoked Borg commands when at highest verbosity level.
1.1.9
* #17, #39: Support for user-defined hooks before/after backup, or on error.
* #34: Improve clarity of logging spew at high verbosity levels.
* #30: Support for using tilde in source directory path to reference home directory.
* Require "prefix" in retention section when "archive_name_format" is set. This is to avoid
accidental pruning of archives with a different archive name format. For similar reasons, default
"prefix" to "{hostname}-" if not specified.
* Convert main source repository from Mercurial to Git.
* Update dead links to Borg documentation.
1.1.8
* #40: Fix to make /etc/borgmatic/config.yaml optional rather than required when using the default
config paths.
1.1.7
* #29: Add "archive_name_format" to configuration for customizing archive names.
* Fix for traceback when "exclude_from" value is empty in configuration file.
* When pruning, make highest verbosity level list archives kept and pruned.
* Clarification of Python 3 pip usage in documentation.
1.1.6
* #13, #36: Support for Borg --exclude-from, --exclude-caches, and --exclude-if-present options.
1.1.5
* #35: New "extract" consistency check that performs a dry-run extraction of the most recent
archive.
1.1.4
* #18: Added command-line flags for performing a borgmatic run with only pruning, creating, or
checking enabled. This supports use cases like running consistency checks from a different cron
job with a different frequency, or running pruning with a different verbosity level.
1.1.3
* #15: Support for running multiple config files in /etc/borgmatic.d/ from a single borgmatic run.
* Fix for generate-borgmatic-config writing config with invalid one_file_system value.
1.1.2
* #33: Fix for passing check_last as integer to subprocess when calling Borg.
1.1.1
* Part of #33: Fix for upgrade-borgmatic-config converting check_last option as a string instead of
an integer.
* Fix for upgrade-borgmatic-config erroring when consistency checks option is not present.
1.1.0
* Switched config file format to YAML. Run upgrade-borgmatic-config to upgrade.
* Added generate-borgmatic-config command for initial config creation.
* Dropped Python 2 support. Now Python 3 only.
* #19: Fix for README mention of sample files not included in package.
* #23: Sample files for triggering borgmatic from a systemd timer.
* Support for backing up to multiple repositories.
* To free up space, now pruning backups prior to creating a new backup.
* Enabled test coverage output during tox runs.
* Added logo.
1.0.3
* #22: Fix for verbosity flag not actually causing verbose output.
1.0.2
* #21: Fix for traceback when remote_path option is missing.
1.0.1
* #20: Support for Borg's --remote-path option to use an alternate Borg
executable. See sample/config.
1.0.0
* Attic is no longer supported, as there hasn't been any recent development on
it. Dropping Attic support will allow faster iteration on Borg-specific
features. If you're still using Attic, this is a good time to switch to Borg!
* Project renamed from atticmatic to borgmatic. See the borgmatic README for
information on upgrading.
0.1.8
* Fix for handling of spaces in source_directories which resulted in backup up everything.
* Fix for broken links to Borg documentation.
* At verbosity zero, suppressing Borg check stderr spew about "Checking segments".
* Support for Borg --one-file-system.
* Support for Borg create --umask.
* Support for file globs in source_directories.
0.1.7
* #11: Fixed parsing of punctuation in configuration file.
* #12: Fixed parsing of punctuation in configuration file.
* Better error message when configuration file is missing.
0.1.6
* #9: New configuration option for the encryption passphrase.
* #10: Support for Borg's new archive compression feature.
* #10: New configuration option for the encryption passphrase.
* #11: Support for Borg's new archive compression feature.
0.1.5
@@ -18,7 +459,7 @@
0.1.3
* #1: Add support for "borg check --last N" to Borg backend.
* #2: Add support for "borg check --last N" to Borg backend.
0.1.2
+105 -120
View File
@@ -1,143 +1,128 @@
title: Atticmatic
---
title: borgmatic
permalink: index.html
---
<a href="https://build.torsion.org/witten/borgmatic" alt="build status">![Build Status](https://build.torsion.org/api/badges/witten/borgmatic/status.svg?ref=refs/heads/master)</a>
## Overview
atticmatic is a simple Python wrapper script for the
[Attic](https://attic-backup.org/) and
[Borg](https://borgbackup.github.io/borgbackup/) backup software that
initiates a backup, prunes any old backups according to a retention policy,
and validates backups for consistency. The script supports specifying your
settings in a declarative configuration file rather than having to put them
all on the command-line, and handles common errors.
<img src="https://projects.torsion.org/witten/borgmatic/raw/branch/master/static/borgmatic.png" alt="borgmatic logo" width="150px" style="float: right; padding-left: 1em;">
borgmatic is simple, configuration-driven backup software for servers and
workstations. Backup all of your machines from the command-line or scheduled
jobs. No GUI required. Built atop [Borg Backup](https://www.borgbackup.org/),
borgmatic initiates a backup, prunes any old backups according to a retention
policy, and validates backups for consistency. borgmatic supports specifying
your settings in a declarative configuration file, rather than having to put
them all on the command-line, and handles common errors.
Here's an example config file:
[location]
# Space-separated list of source directories to backup.
source_directories: /home /etc
```yaml
location:
# List of source directories to backup. Globs are expanded.
source_directories:
- /home
- /etc
- /var/log/syslog*
# Path to local or remote backup repository.
repository: user@backupserver:sourcehostname.attic
# Paths to local or remote repositories.
repositories:
- user@backupserver:sourcehostname.borg
[retention]
# Any paths matching these patterns are excluded from backups.
exclude_patterns:
- /home/*/.cache
retention:
# Retention policy for how many backups to keep in each category.
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
[consistency]
# Consistency checks to run, or "disabled" to prevent checks.
checks: repository archives
Additionally, exclude patterns can be specified in a separate excludes config
file, one pattern per line.
atticmatic is hosted at <https://torsion.org/atticmatic> with [source code
available](https://torsion.org/hg/atticmatic). It's also mirrored on
[GitHub](https://github.com/witten/atticmatic) and
[BitBucket](https://bitbucket.org/dhelfman/atticmatic) for convenience.
## Setup
To get up and running, follow the [Attic Quick
Start](https://attic-backup.org/quickstart.html) or the [Borg Quick
Start](https://borgbackup.github.io/borgbackup/quickstart.html) to create a
repository on a local or remote host. Note that if you plan to run atticmatic
on a schedule with cron, and you encrypt your attic repository with a
passphrase instead of a key file, you'll need to set the atticmatic
`encryption_passphrase` configuration variable. See the repository encryption
section of the Quick Start for more info.
If the repository is on a remote host, make sure that your local root user has
key-based ssh access to the desired user account on the remote host.
To install atticmatic, run the following command to download and install it:
sudo pip install --upgrade atticmatic
If you are using Attic, copy the following configuration files:
sudo cp sample/atticmatic.cron /etc/cron.d/atticmatic
sudo mkdir /etc/atticmatic/
sudo cp sample/config sample/excludes /etc/atticmatic/
If you are using Borg, copy the files like this instead:
sudo cp sample/borgmatic.cron /etc/cron.d/borgmatic
sudo mkdir /etc/borgmatic/
sudo cp sample/config sample/excludes /etc/borgmatic/
Lastly, modify the /etc files with your desired configuration.
## Usage
You can run atticmatic and start a backup simply by invoking it without
arguments:
atticmatic
Or, if you're using Borg, use this command instead to make use of the Borg
backend:
borgmatic
This will also prune any old backups as per the configured retention policy,
and check backups for consistency problems due to things like file damage.
By default, the backup will proceed silently except in the case of errors. But
if you'd like to to get additional information about the progress of the
backup as it proceeds, use the verbosity option:
atticmattic --verbosity 1
Or, for even more progress spew:
atticmattic --verbosity 2
If you'd like to see the available command-line arguments, view the help:
atticmattic --help
## Running tests
First install tox, which is used for setting up testing environments:
pip install tox
Then, to actually run tests, run:
tox
## Troubleshooting
### Broken pipe with remote repository
When running atticmatic on a large remote repository, you may receive errors
like the following, particularly while "attic check" is valiating backups for
consistency:
# List of consistency checks to run: "repository", "archives", etc.
checks:
- repository
- archives
Write failed: Broken pipe
attic: Error: Connection closed by remote host
hooks:
# Preparation scripts to run, databases to dump, and monitoring to perform.
before_backup:
- prepare-for-backup.sh
postgresql_databases:
- name: users
healthchecks: https://hc-ping.com/be067061-cf96-4412-8eae-62b0c50d6a8c
```
This error can be caused by an ssh timeout, which you can rectify by adding
the following to the ~/.ssh/config file on the client:
borgmatic is hosted at <https://torsion.org/borgmatic> with [source code
available](https://projects.torsion.org/witten/borgmatic). It's also mirrored
on [GitHub](https://github.com/witten/borgmatic) for convenience.
Host *
ServerAliveInterval 120
Want to see borgmatic in action? Check out the <a
href="https://asciinema.org/a/203761" target="_blank">screencast</a>.
This should make the client keep the connection alive while validating
backups.
<script src="https://asciinema.org/a/203761.js" id="asciicast-203761" async></script>
## Issues and feedback
## How-to guides
Got an issue or an idea for a feature enhancement? Check out the [atticmatic
issue tracker](https://tree.taiga.io/project/witten-atticmatic/issues). In
order to create a new issue or comment on an issue, you'll need to [login
first](https://tree.taiga.io/login).
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/) ⬅ *Start here!*
* [Make per-application backups](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/)
* [Deal with very large backups](https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Extract a backup](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/)
* [Backup your databases](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Upgrade borgmatic](https://torsion.org/borgmatic/docs/how-to/upgrade/)
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
## Reference guides
* [borgmatic configuration reference](https://torsion.org/borgmatic/docs/reference/configuration/)
* [borgmatic command-line reference](https://torsion.org/borgmatic/docs/reference/command-line/)
## Hosting providers
Need somewhere to store your encrypted offsite backups? The following hosting
providers include specific support for Borg/borgmatic. Using these links and
services helps support borgmatic development and hosting. (These are referral
links, but without any tracking scripts or cookies.)
<ul>
<li class="referral"><a href="https://www.rsync.net/cgi-bin/borg.cgi?campaign=borg&adgroup=borgmatic">rsync.net</a>: Cloud Storage provider with full support for borg and any other SSH/SFTP tool</li>
<li class="referral"><a href="https://www.borgbase.com/?utm_source=borgmatic">BorgBase</a>: Borg hosting service with support for monitoring, 2FA, and append-only repos</li>
</ul>
## Support and contributing
### Issues
You've got issues? Or an idea for a feature enhancement? We've got an [issue
tracker](https://projects.torsion.org/witten/borgmatic/issues). In order to
create a new issue or comment on an issue, you'll need to [login
first](https://projects.torsion.org/user/login). Note that you can login with
an existing GitHub account if you prefer.
If you'd like to chat with borgmatic developers or users, head on over to the
`#borgmatic` IRC channel on Freenode, either via <a
href="https://webchat.freenode.net/?channels=borgmatic">web chat</a> or a
native <a href="irc://chat.freenode.net:6697">IRC client</a>.
Other questions or comments? Contact <mailto:witten@torsion.org>.
### Contributing
If you'd like to contribute to borgmatic development, please feel free to
submit a [Pull Request](https://projects.torsion.org/witten/borgmatic/pulls)
or open an [issue](https://projects.torsion.org/witten/borgmatic/issues) first
to discuss your idea. We also accept Pull Requests on GitHub, if that's more
your thing. In general, contributions are very welcome. We don't bite!
Also, please check out the [borgmatic development
how-to](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/) for
info on cloning source code, running tests, etc.
-14
View File
@@ -1,14 +0,0 @@
from functools import partial
from atticmatic.backends import shared
# An atticmatic backend that supports Attic for actually handling backups.
COMMAND = 'attic'
CONFIG_FORMAT = shared.CONFIG_FORMAT
initialize = partial(shared.initialize, command=COMMAND)
create_archive = partial(shared.create_archive, command=COMMAND)
prune_archives = partial(shared.prune_archives, command=COMMAND)
check_archives = partial(shared.check_archives, command=COMMAND)
-32
View File
@@ -1,32 +0,0 @@
from functools import partial
from atticmatic.config import Section_format, option
from atticmatic.backends import shared
# An atticmatic backend that supports Borg for actually handling backups.
COMMAND = 'borg'
CONFIG_FORMAT = (
shared.CONFIG_FORMAT[0], # location
Section_format(
'storage',
(
option('encryption_passphrase', required=False),
option('compression', required=False),
),
),
shared.CONFIG_FORMAT[2], # retention
Section_format(
'consistency',
(
option('checks', required=False),
option('check_last', required=False),
),
)
)
initialize = partial(shared.initialize, command=COMMAND)
create_archive = partial(shared.create_archive, command=COMMAND)
prune_archives = partial(shared.prune_archives, command=COMMAND)
check_archives = partial(shared.check_archives, command=COMMAND)
-210
View File
@@ -1,210 +0,0 @@
from datetime import datetime
import os
import platform
import subprocess
from atticmatic.config import Section_format, option
from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
# Common backend functionality shared by Attic and Borg. As the two backup
# commands diverge, these shared functions will likely need to be replaced
# with non-shared functions within atticmatic.backends.attic and
# atticmatic.backends.borg.
CONFIG_FORMAT = (
Section_format(
'location',
(
option('source_directories'),
option('repository'),
),
),
Section_format(
'storage',
(
option('encryption_passphrase', required=False),
),
),
Section_format(
'retention',
(
option('keep_within', required=False),
option('keep_hourly', int, required=False),
option('keep_daily', int, required=False),
option('keep_weekly', int, required=False),
option('keep_monthly', int, required=False),
option('keep_yearly', int, required=False),
option('prefix', required=False),
),
),
Section_format(
'consistency',
(
option('checks', required=False),
),
)
)
def initialize(storage_config, command):
passphrase = storage_config.get('encryption_passphrase')
if passphrase:
os.environ['{}_PASSPHRASE'.format(command.upper())] = passphrase
def create_archive(
excludes_filename, verbosity, storage_config, source_directories, repository, command,
):
'''
Given an excludes filename (or None), a vebosity flag, a storage config dict, a space-separated
list of source directories, a local or remote repository path, and a command to run, create an
attic archive.
'''
sources = tuple(source_directories.split(' '))
exclude_flags = ('--exclude-from', excludes_filename) if excludes_filename else ()
compression = storage_config.get('compression', None)
compression_flags = ('--compression', compression) if compression else ()
verbosity_flags = {
VERBOSITY_SOME: ('--stats',),
VERBOSITY_LOTS: ('--verbose', '--stats'),
}.get(verbosity, ())
full_command = (
command, 'create',
'{repo}::{hostname}-{timestamp}'.format(
repo=repository,
hostname=platform.node(),
timestamp=datetime.now().isoformat(),
),
) + sources + exclude_flags + compression_flags + verbosity_flags
subprocess.check_call(full_command)
def _make_prune_flags(retention_config):
'''
Given a retention config dict mapping from option name to value, tranform it into an iterable of
command-line name-value flag pairs.
For example, given a retention config of:
{'keep_weekly': 4, 'keep_monthly': 6}
This will be returned as an iterable of:
(
('--keep-weekly', '4'),
('--keep-monthly', '6'),
)
'''
return (
('--' + option_name.replace('_', '-'), str(retention_config[option_name]))
for option_name, value in retention_config.items()
)
def prune_archives(verbosity, repository, retention_config, command):
'''
Given a verbosity flag, a local or remote repository path, a retention config dict, and a
command to run, prune attic archives according the the retention policy specified in that
configuration.
'''
verbosity_flags = {
VERBOSITY_SOME: ('--stats',),
VERBOSITY_LOTS: ('--verbose', '--stats'),
}.get(verbosity, ())
full_command = (
command, 'prune',
repository,
) + tuple(
element
for pair in _make_prune_flags(retention_config)
for element in pair
) + verbosity_flags
subprocess.check_call(full_command)
DEFAULT_CHECKS = ('repository', 'archives')
def _parse_checks(consistency_config):
'''
Given a consistency config with a space-separated "checks" option, transform it to a tuple of
named checks to run.
For example, given a retention config of:
{'checks': 'repository archives'}
This will be returned as:
('repository', 'archives')
If no "checks" option is present, return the DEFAULT_CHECKS. If the checks value is the string
"disabled", return an empty tuple, meaning that no checks should be run.
'''
checks = consistency_config.get('checks', '').strip()
if not checks:
return DEFAULT_CHECKS
return tuple(
check for check in consistency_config['checks'].split(' ')
if check.lower() not in ('disabled', '')
)
def _make_check_flags(checks, check_last=None):
'''
Given a parsed sequence of checks, transform it into tuple of command-line flags.
For example, given parsed checks of:
('repository',)
This will be returned as:
('--repository-only',)
Additionally, if a check_last value is given, a "--last" flag will be added. Note that only
Borg supports this flag.
'''
last_flag = ('--last', check_last) if check_last else ()
if checks == DEFAULT_CHECKS:
return last_flag
return tuple(
'--{}-only'.format(check) for check in checks
) + last_flag
def check_archives(verbosity, repository, consistency_config, command):
'''
Given a verbosity flag, a local or remote repository path, a consistency config dict, and a
command to run, check the contained attic archives for consistency.
If there are no consistency checks to run, skip running them.
'''
checks = _parse_checks(consistency_config)
check_last = consistency_config.get('check_last', None)
if not checks:
return
verbosity_flags = {
VERBOSITY_SOME: ('--verbose',),
VERBOSITY_LOTS: ('--verbose',),
}.get(verbosity, ())
full_command = (
command, 'check',
repository,
) + _make_check_flags(checks, check_last) + verbosity_flags
# The check command spews to stdout even without the verbose flag. Suppress it.
stdout = None if verbosity_flags else open(os.devnull, 'w')
subprocess.check_call(full_command, stdout=stdout)
-75
View File
@@ -1,75 +0,0 @@
from __future__ import print_function
from argparse import ArgumentParser
from importlib import import_module
import os
from subprocess import CalledProcessError
import sys
from atticmatic.config import parse_configuration
DEFAULT_CONFIG_FILENAME_PATTERN = '/etc/{}/config'
DEFAULT_EXCLUDES_FILENAME_PATTERN = '/etc/{}/excludes'
def parse_arguments(command_name, *arguments):
'''
Given the name of the command with which this script was invoked and command-line arguments,
parse the arguments and return them as an ArgumentParser instance. Use the command name to
determine the default configuration and excludes paths.
'''
config_filename_default = DEFAULT_CONFIG_FILENAME_PATTERN.format(command_name)
excludes_filename_default = DEFAULT_EXCLUDES_FILENAME_PATTERN.format(command_name)
parser = ArgumentParser()
parser.add_argument(
'-c', '--config',
dest='config_filename',
default=config_filename_default,
help='Configuration filename',
)
parser.add_argument(
'--excludes',
dest='excludes_filename',
default=excludes_filename_default if os.path.exists(excludes_filename_default) else None,
help='Excludes filename',
)
parser.add_argument(
'-v', '--verbosity',
type=int,
help='Display verbose progress (1 for some, 2 for lots)',
)
return parser.parse_args(arguments)
def load_backend(command_name):
'''
Given the name of the command with which this script was invoked, return the corresponding
backend module responsible for actually dealing with backups.
'''
backend_name = {
'atticmatic': 'attic',
'borgmatic': 'borg',
}.get(command_name, 'attic')
return import_module('atticmatic.backends.{}'.format(backend_name))
def main():
try:
command_name = os.path.basename(sys.argv[0])
args = parse_arguments(command_name, *sys.argv[1:])
backend = load_backend(command_name)
config = parse_configuration(args.config_filename, backend.CONFIG_FORMAT)
repository = config.location['repository']
backend.initialize(config.storage)
backend.create_archive(
args.excludes_filename, args.verbosity, config.storage, **config.location
)
backend.prune_archives(args.verbosity, repository, config.retention)
backend.check_archives(args.verbosity, repository, config.consistency)
except (ValueError, IOError, CalledProcessError) as error:
print(error, file=sys.stderr)
sys.exit(1)
-11
View File
@@ -1,11 +0,0 @@
from flexmock import flexmock
import sys
def builtins_mock():
try:
# Python 2
return flexmock(sys.modules['__builtin__'])
except KeyError:
# Python 3
return flexmock(sys.modules['builtins'])
@@ -1,72 +0,0 @@
import os
import sys
from flexmock import flexmock
from nose.tools import assert_raises
from atticmatic import command as module
COMMAND_NAME = 'foomatic'
def test_parse_arguments_with_no_arguments_uses_defaults():
flexmock(os.path).should_receive('exists').and_return(True)
parser = module.parse_arguments(COMMAND_NAME)
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME_PATTERN.format(COMMAND_NAME)
assert parser.excludes_filename == module.DEFAULT_EXCLUDES_FILENAME_PATTERN.format(COMMAND_NAME)
assert parser.verbosity == None
def test_parse_arguments_with_filename_arguments_overrides_defaults():
flexmock(os.path).should_receive('exists').and_return(True)
parser = module.parse_arguments(COMMAND_NAME, '--config', 'myconfig', '--excludes', 'myexcludes')
assert parser.config_filename == 'myconfig'
assert parser.excludes_filename == 'myexcludes'
assert parser.verbosity == None
def test_parse_arguments_with_missing_default_excludes_file_sets_filename_to_none():
flexmock(os.path).should_receive('exists').and_return(False)
parser = module.parse_arguments(COMMAND_NAME)
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME_PATTERN.format(COMMAND_NAME)
assert parser.excludes_filename == None
assert parser.verbosity == None
def test_parse_arguments_with_missing_overridden_excludes_file_retains_filename():
flexmock(os.path).should_receive('exists').and_return(False)
parser = module.parse_arguments(COMMAND_NAME, '--excludes', 'myexcludes')
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME_PATTERN.format(COMMAND_NAME)
assert parser.excludes_filename == 'myexcludes'
assert parser.verbosity == None
def test_parse_arguments_with_verbosity_flag_overrides_default():
flexmock(os.path).should_receive('exists').and_return(True)
parser = module.parse_arguments(COMMAND_NAME, '--verbosity', '1')
assert parser.config_filename == module.DEFAULT_CONFIG_FILENAME_PATTERN.format(COMMAND_NAME)
assert parser.excludes_filename == module.DEFAULT_EXCLUDES_FILENAME_PATTERN.format(COMMAND_NAME)
assert parser.verbosity == 1
def test_parse_arguments_with_invalid_arguments_exits():
flexmock(os.path).should_receive('exists').and_return(True)
original_stderr = sys.stderr
sys.stderr = sys.stdout
try:
with assert_raises(SystemExit):
module.parse_arguments(COMMAND_NAME, '--posix-me-harder')
finally:
sys.stderr = original_stderr
@@ -1,29 +0,0 @@
try:
# Python 2
from cStringIO import StringIO
except ImportError:
# Python 3
from io import StringIO
from collections import OrderedDict
import string
from atticmatic import config as module
def test_parse_section_options_with_punctuation_should_return_section_options():
parser = module.RawConfigParser()
parser.readfp(StringIO('[section]\nfoo: {}\n'.format(string.punctuation)))
section_format = module.Section_format(
'section',
(module.Config_option('foo', str, required=True),),
)
config = module.parse_section_options(parser, section_format)
assert config == OrderedDict(
(
('foo', string.punctuation),
)
)
@@ -1,8 +0,0 @@
import subprocess
def test_setup_version_matches_news_version():
setup_version = subprocess.check_output(('python', 'setup.py', '--version')).decode('ascii')
news_version = open('NEWS').readline()
assert setup_version == news_version
@@ -1,325 +0,0 @@
from collections import OrderedDict
import os
from flexmock import flexmock
from atticmatic.backends import shared as module
from atticmatic.tests.builtins import builtins_mock
from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
def test_initialize_with_passphrase_should_set_environment():
orig_environ = os.environ
try:
os.environ = {}
module.initialize({'encryption_passphrase': 'pass'}, command='attic')
assert os.environ.get('ATTIC_PASSPHRASE') == 'pass'
finally:
os.environ = orig_environ
def test_initialize_without_passphrase_should_not_set_environment():
orig_environ = os.environ
try:
os.environ = {}
module.initialize({}, command='attic')
assert os.environ.get('ATTIC_PASSPHRASE') == None
finally:
os.environ = orig_environ
def insert_subprocess_mock(check_call_command, **kwargs):
subprocess = flexmock()
subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
flexmock(module).subprocess = subprocess
def insert_subprocess_never():
subprocess = flexmock()
subprocess.should_receive('check_call').never()
flexmock(module).subprocess = subprocess
def insert_platform_mock():
flexmock(module.platform).should_receive('node').and_return('host')
def insert_datetime_mock():
flexmock(module).datetime = flexmock().should_receive('now').and_return(
flexmock().should_receive('isoformat').and_return('now').mock
).mock
CREATE_COMMAND_WITHOUT_EXCLUDES = ('attic', 'create', 'repo::host-now', 'foo', 'bar')
CREATE_COMMAND = CREATE_COMMAND_WITHOUT_EXCLUDES + ('--exclude-from', 'excludes')
def test_create_archive_should_call_attic_with_parameters():
insert_subprocess_mock(CREATE_COMMAND)
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename='excludes',
verbosity=None,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
)
def test_create_archive_with_none_excludes_filename_should_call_attic_without_excludes():
insert_subprocess_mock(CREATE_COMMAND_WITHOUT_EXCLUDES)
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename=None,
verbosity=None,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
)
def test_create_archive_with_verbosity_some_should_call_attic_with_stats_parameter():
insert_subprocess_mock(CREATE_COMMAND + ('--stats',))
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename='excludes',
verbosity=VERBOSITY_SOME,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
)
def test_create_archive_with_verbosity_lots_should_call_attic_with_verbose_parameter():
insert_subprocess_mock(CREATE_COMMAND + ('--verbose', '--stats'))
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename='excludes',
verbosity=VERBOSITY_LOTS,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
)
def test_create_archive_with_compression_should_call_attic_with_compression_parameters():
insert_subprocess_mock(CREATE_COMMAND + ('--compression', 'rle'))
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename='excludes',
verbosity=None,
storage_config={'compression': 'rle'},
source_directories='foo bar',
repository='repo',
command='attic',
)
BASE_PRUNE_FLAGS = (
('--keep-daily', '1'),
('--keep-weekly', '2'),
('--keep-monthly', '3'),
)
def test_make_prune_flags_should_return_flags_from_config():
retention_config = OrderedDict(
(
('keep_daily', 1),
('keep_weekly', 2),
('keep_monthly', 3),
)
)
result = module._make_prune_flags(retention_config)
assert tuple(result) == BASE_PRUNE_FLAGS
PRUNE_COMMAND = (
'attic', 'prune', 'repo', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3',
)
def test_prune_archives_should_call_attic_with_parameters():
retention_config = flexmock()
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS,
)
insert_subprocess_mock(PRUNE_COMMAND)
module.prune_archives(
verbosity=None,
repository='repo',
retention_config=retention_config,
command='attic',
)
def test_prune_archives_with_verbosity_some_should_call_attic_with_stats_parameter():
retention_config = flexmock()
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS,
)
insert_subprocess_mock(PRUNE_COMMAND + ('--stats',))
module.prune_archives(
repository='repo',
verbosity=VERBOSITY_SOME,
retention_config=retention_config,
command='attic',
)
def test_prune_archives_with_verbosity_lots_should_call_attic_with_verbose_parameter():
retention_config = flexmock()
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS,
)
insert_subprocess_mock(PRUNE_COMMAND + ('--verbose', '--stats',))
module.prune_archives(
repository='repo',
verbosity=VERBOSITY_LOTS,
retention_config=retention_config,
command='attic',
)
def test_parse_checks_returns_them_as_tuple():
checks = module._parse_checks({'checks': 'foo disabled bar'})
assert checks == ('foo', 'bar')
def test_parse_checks_with_missing_value_returns_defaults():
checks = module._parse_checks({})
assert checks == module.DEFAULT_CHECKS
def test_parse_checks_with_blank_value_returns_defaults():
checks = module._parse_checks({'checks': ''})
assert checks == module.DEFAULT_CHECKS
def test_parse_checks_with_disabled_returns_no_checks():
checks = module._parse_checks({'checks': 'disabled'})
assert checks == ()
def test_make_check_flags_with_checks_returns_flags():
flags = module._make_check_flags(('foo', 'bar'))
assert flags == ('--foo-only', '--bar-only')
def test_make_check_flags_with_default_checks_returns_no_flags():
flags = module._make_check_flags(module.DEFAULT_CHECKS)
assert flags == ()
def test_make_check_flags_with_checks_and_last_returns_flags_including_last():
flags = module._make_check_flags(('foo', 'bar'), check_last=3)
assert flags == ('--foo-only', '--bar-only', '--last', 3)
def test_make_check_flags_with_last_returns_last_flag():
flags = module._make_check_flags(module.DEFAULT_CHECKS, check_last=3)
assert flags == ('--last', 3)
def test_check_archives_should_call_attic_with_parameters():
checks = flexmock()
check_last = flexmock()
consistency_config = flexmock().should_receive('get').and_return(check_last).mock
flexmock(module).should_receive('_parse_checks').and_return(checks)
flexmock(module).should_receive('_make_check_flags').with_args(checks, check_last).and_return(())
stdout = flexmock()
insert_subprocess_mock(
('attic', 'check', 'repo'),
stdout=stdout,
)
insert_platform_mock()
insert_datetime_mock()
builtins_mock().should_receive('open').and_return(stdout)
flexmock(module.os).should_receive('devnull')
module.check_archives(
verbosity=None,
repository='repo',
consistency_config=consistency_config,
command='attic',
)
def test_check_archives_with_verbosity_some_should_call_attic_with_verbose_parameter():
consistency_config = flexmock().should_receive('get').and_return(None).mock
flexmock(module).should_receive('_parse_checks').and_return(flexmock())
flexmock(module).should_receive('_make_check_flags').and_return(())
insert_subprocess_mock(
('attic', 'check', 'repo', '--verbose'),
stdout=None,
)
insert_platform_mock()
insert_datetime_mock()
module.check_archives(
verbosity=VERBOSITY_SOME,
repository='repo',
consistency_config=consistency_config,
command='attic',
)
def test_check_archives_with_verbosity_lots_should_call_attic_with_verbose_parameter():
consistency_config = flexmock().should_receive('get').and_return(None).mock
flexmock(module).should_receive('_parse_checks').and_return(flexmock())
flexmock(module).should_receive('_make_check_flags').and_return(())
insert_subprocess_mock(
('attic', 'check', 'repo', '--verbose'),
stdout=None,
)
insert_platform_mock()
insert_datetime_mock()
module.check_archives(
verbosity=VERBOSITY_LOTS,
repository='repo',
consistency_config=consistency_config,
command='attic',
)
def test_check_archives_without_any_checks_should_bail():
consistency_config = flexmock().should_receive('get').and_return(None).mock
flexmock(module).should_receive('_parse_checks').and_return(())
insert_subprocess_never()
module.check_archives(
verbosity=None,
repository='repo',
consistency_config=consistency_config,
command='attic',
)
-33
View File
@@ -1,33 +0,0 @@
from flexmock import flexmock
from atticmatic import command as module
def test_load_backend_with_atticmatic_command_should_return_attic_backend():
backend = flexmock()
(
flexmock(module).should_receive('import_module').with_args('atticmatic.backends.attic')
.and_return(backend).once()
)
assert module.load_backend('atticmatic') == backend
def test_load_backend_with_unknown_command_should_return_attic_backend():
backend = flexmock()
(
flexmock(module).should_receive('import_module').with_args('atticmatic.backends.attic')
.and_return(backend).once()
)
assert module.load_backend('unknownmatic') == backend
def test_load_backend_with_borgmatic_command_should_return_borg_backend():
backend = flexmock()
(
flexmock(module).should_receive('import_module').with_args('atticmatic.backends.borg')
.and_return(backend).once()
)
assert module.load_backend('borgmatic') == backend
-2
View File
@@ -1,2 +0,0 @@
VERBOSITY_SOME = 1
VERBOSITY_LOTS = 2
+132
View File
@@ -0,0 +1,132 @@
import logging
from borgmatic.borg import extract
from borgmatic.execute import execute_command
DEFAULT_CHECKS = ('repository', 'archives')
DEFAULT_PREFIX = '{hostname}-'
logger = logging.getLogger(__name__)
def _parse_checks(consistency_config, only_checks=None):
'''
Given a consistency config with a "checks" list, and an optional list of override checks,
transform them a tuple of named checks to run.
For example, given a retention config of:
{'checks': ['repository', 'archives']}
This will be returned as:
('repository', 'archives')
If no "checks" option is present in the config, return the DEFAULT_CHECKS. If the checks value
is the string "disabled", return an empty tuple, meaning that no checks should be run.
If the "data" option is present, then make sure the "archives" option is included as well.
'''
checks = [
check.lower() for check in (only_checks or consistency_config.get('checks', []) or [])
]
if checks == ['disabled']:
return ()
if 'data' in checks and 'archives' not in checks:
checks.append('archives')
return tuple(check for check in checks if check not in ('disabled', '')) or DEFAULT_CHECKS
def _make_check_flags(checks, check_last=None, prefix=None):
'''
Given a parsed sequence of checks, transform it into tuple of command-line flags.
For example, given parsed checks of:
('repository',)
This will be returned as:
('--repository-only',)
However, if both "repository" and "archives" are in checks, then omit them from the returned
flags because Borg does both checks by default.
Additionally, if a check_last value is given and "archives" is in checks, then include a
"--last" flag. And if a prefix value is given and "archives" is in checks, then include a
"--prefix" flag.
'''
if 'archives' in checks:
last_flags = ('--last', str(check_last)) if check_last else ()
prefix_flags = ('--prefix', prefix) if prefix else ()
else:
last_flags = ()
prefix_flags = ()
if check_last:
logger.warning(
'Ignoring check_last option, as "archives" is not in consistency checks.'
)
if prefix:
logger.warning(
'Ignoring consistency prefix option, as "archives" is not in consistency checks.'
)
common_flags = last_flags + prefix_flags + (('--verify-data',) if 'data' in checks else ())
if set(DEFAULT_CHECKS).issubset(set(checks)):
return common_flags
return (
tuple('--{}-only'.format(check) for check in checks if check in DEFAULT_CHECKS)
+ common_flags
)
def check_archives(
repository,
storage_config,
consistency_config,
local_path='borg',
remote_path=None,
only_checks=None,
):
'''
Given a local or remote repository path, a storage config dict, a consistency config dict,
local/remote commands to run, and an optional list of checks to use instead of configured
checks, check the contained Borg archives for consistency.
If there are no consistency checks to run, skip running them.
'''
checks = _parse_checks(consistency_config, only_checks)
check_last = consistency_config.get('check_last', None)
lock_wait = None
if set(checks).intersection(set(DEFAULT_CHECKS + ('data',))):
remote_path_flags = ('--remote-path', remote_path) if remote_path else ()
lock_wait = storage_config.get('lock_wait', None)
lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else ()
verbosity_flags = ()
if logger.isEnabledFor(logging.INFO):
verbosity_flags = ('--info',)
if logger.isEnabledFor(logging.DEBUG):
verbosity_flags = ('--debug', '--show-rc')
prefix = consistency_config.get('prefix', DEFAULT_PREFIX)
full_command = (
(local_path, 'check')
+ _make_check_flags(checks, check_last, prefix)
+ remote_path_flags
+ lock_wait_flags
+ verbosity_flags
+ (repository,)
)
execute_command(full_command)
if 'extract' in checks:
extract.extract_last_archive_dry_run(repository, lock_wait, local_path, remote_path)
+205
View File
@@ -0,0 +1,205 @@
import glob
import itertools
import logging
import os
import tempfile
from borgmatic.execute import execute_command, execute_command_without_capture
logger = logging.getLogger(__name__)
def _expand_directory(directory):
'''
Given a directory path, expand any tilde (representing a user's home directory) and any globs
therein. Return a list of one or more resulting paths.
'''
expanded_directory = os.path.expanduser(directory)
return glob.glob(expanded_directory) or [expanded_directory]
def _expand_directories(directories):
'''
Given a sequence of directory paths, expand tildes and globs in each one. Return all the
resulting directories as a single flattened tuple.
'''
if directories is None:
return ()
return tuple(
itertools.chain.from_iterable(_expand_directory(directory) for directory in directories)
)
def _expand_home_directories(directories):
'''
Given a sequence of directory paths, expand tildes in each one. Do not perform any globbing.
Return the results as a tuple.
'''
if directories is None:
return ()
return tuple(os.path.expanduser(directory) for directory in directories)
def _write_pattern_file(patterns=None):
'''
Given a sequence of patterns, write them to a named temporary file and return it. Return None
if no patterns are provided.
'''
if not patterns:
return None
pattern_file = tempfile.NamedTemporaryFile('w')
pattern_file.write('\n'.join(patterns))
pattern_file.flush()
return pattern_file
def _make_pattern_flags(location_config, pattern_filename=None):
'''
Given a location config dict with a potential patterns_from option, and a filename containing
any additional patterns, return the corresponding Borg flags for those files as a tuple.
'''
pattern_filenames = tuple(location_config.get('patterns_from') or ()) + (
(pattern_filename,) if pattern_filename else ()
)
return tuple(
itertools.chain.from_iterable(
('--patterns-from', pattern_filename) for pattern_filename in pattern_filenames
)
)
def _make_exclude_flags(location_config, exclude_filename=None):
'''
Given a location config dict with various exclude options, and a filename containing any exclude
patterns, return the corresponding Borg flags as a tuple.
'''
exclude_filenames = tuple(location_config.get('exclude_from') or ()) + (
(exclude_filename,) if exclude_filename else ()
)
exclude_from_flags = tuple(
itertools.chain.from_iterable(
('--exclude-from', exclude_filename) for exclude_filename in exclude_filenames
)
)
caches_flag = ('--exclude-caches',) if location_config.get('exclude_caches') else ()
if_present = location_config.get('exclude_if_present')
if_present_flags = ('--exclude-if-present', if_present) if if_present else ()
keep_exclude_tags_flags = (
('--keep-exclude-tags',) if location_config.get('keep_exclude_tags') else ()
)
exclude_nodump_flags = ('--exclude-nodump',) if location_config.get('exclude_nodump') else ()
return (
exclude_from_flags
+ caches_flag
+ if_present_flags
+ keep_exclude_tags_flags
+ exclude_nodump_flags
)
BORGMATIC_SOURCE_DIRECTORY = '~/.borgmatic'
def borgmatic_source_directories():
'''
Return a list of borgmatic-specific source directories used for state like database backups.
'''
return (
[BORGMATIC_SOURCE_DIRECTORY]
if os.path.exists(os.path.expanduser(BORGMATIC_SOURCE_DIRECTORY))
else []
)
def create_archive(
dry_run,
repository,
location_config,
storage_config,
local_path='borg',
remote_path=None,
progress=False,
stats=False,
json=False,
):
'''
Given vebosity/dry-run flags, a local or remote repository path, a location config dict, and a
storage config dict, create a Borg archive and return Borg's JSON output (if any).
'''
sources = _expand_directories(
location_config['source_directories'] + borgmatic_source_directories()
)
pattern_file = _write_pattern_file(location_config.get('patterns'))
exclude_file = _write_pattern_file(
_expand_home_directories(location_config.get('exclude_patterns'))
)
checkpoint_interval = storage_config.get('checkpoint_interval', None)
chunker_params = storage_config.get('chunker_params', None)
compression = storage_config.get('compression', None)
remote_rate_limit = storage_config.get('remote_rate_limit', None)
umask = storage_config.get('umask', None)
lock_wait = storage_config.get('lock_wait', None)
files_cache = location_config.get('files_cache')
default_archive_name_format = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
archive_name_format = storage_config.get('archive_name_format', default_archive_name_format)
full_command = (
(local_path, 'create')
+ _make_pattern_flags(location_config, pattern_file.name if pattern_file else None)
+ _make_exclude_flags(location_config, exclude_file.name if exclude_file else None)
+ (('--checkpoint-interval', str(checkpoint_interval)) if checkpoint_interval else ())
+ (('--chunker-params', chunker_params) if chunker_params else ())
+ (('--compression', compression) if compression else ())
+ (('--remote-ratelimit', str(remote_rate_limit)) if remote_rate_limit else ())
+ (('--one-file-system',) if location_config.get('one_file_system') else ())
+ (('--numeric-owner',) if location_config.get('numeric_owner') else ())
+ (('--noatime',) if location_config.get('atime') is False else ())
+ (('--noctime',) if location_config.get('ctime') is False else ())
+ (('--nobirthtime',) if location_config.get('birthtime') is False else ())
+ (('--read-special',) if location_config.get('read_special') else ())
+ (('--nobsdflags',) if location_config.get('bsd_flags') is False else ())
+ (('--files-cache', files_cache) if files_cache else ())
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--list', '--filter', 'AME-') if logger.isEnabledFor(logging.INFO) and not json else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (
('--stats',)
if not dry_run and (logger.isEnabledFor(logging.INFO) or stats) and not json
else ()
)
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--dry-run',) if dry_run else ())
+ (('--progress',) if progress else ())
+ (('--json',) if json else ())
+ (
'{repository}::{archive_name_format}'.format(
repository=repository, archive_name_format=archive_name_format
),
)
+ sources
)
# The progress output isn't compatible with captured and logged output, as progress messes with
# the terminal directly.
if progress:
execute_command_without_capture(full_command)
return
if json:
output_log_level = None
elif stats:
output_log_level = logging.WARNING
else:
output_log_level = logging.INFO
return execute_command(full_command, output_log_level)
+31
View File
@@ -0,0 +1,31 @@
import os
OPTION_TO_ENVIRONMENT_VARIABLE = {
'borg_base_directory': 'BORG_BASE_DIR',
'borg_config_directory': 'BORG_CONFIG_DIR',
'borg_cache_directory': 'BORG_CACHE_DIR',
'borg_security_directory': 'BORG_SECURITY_DIR',
'borg_keys_directory': 'BORG_KEYS_DIR',
'encryption_passcommand': 'BORG_PASSCOMMAND',
'encryption_passphrase': 'BORG_PASSPHRASE',
'ssh_command': 'BORG_RSH',
}
DEFAULT_BOOL_OPTION_TO_ENVIRONMENT_VARIABLE = {
'relocated_repo_access_is_ok': 'BORG_RELOCATED_REPO_ACCESS_IS_OK',
'unknown_unencrypted_repo_access_is_ok': 'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK',
}
def initialize(storage_config):
for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
value = storage_config.get(option_name)
if value:
os.environ[environment_variable_name] = value
for (
option_name,
environment_variable_name,
) in DEFAULT_BOOL_OPTION_TO_ENVIRONMENT_VARIABLE.items():
value = storage_config.get(option_name, False)
os.environ[environment_variable_name] = 'yes' if value else 'no'
+102
View File
@@ -0,0 +1,102 @@
import logging
import os
from borgmatic.execute import execute_command, execute_command_without_capture
logger = logging.getLogger(__name__)
def extract_last_archive_dry_run(repository, lock_wait=None, local_path='borg', remote_path=None):
'''
Perform an extraction dry-run of the most recent archive. If there are no archives, skip the
dry-run.
'''
remote_path_flags = ('--remote-path', remote_path) if remote_path else ()
lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else ()
verbosity_flags = ()
if logger.isEnabledFor(logging.DEBUG):
verbosity_flags = ('--debug', '--show-rc')
elif logger.isEnabledFor(logging.INFO):
verbosity_flags = ('--info',)
full_list_command = (
(local_path, 'list', '--short')
+ remote_path_flags
+ lock_wait_flags
+ verbosity_flags
+ (repository,)
)
list_output = execute_command(full_list_command, output_log_level=None)
try:
last_archive_name = list_output.strip().splitlines()[-1]
except IndexError:
return
list_flag = ('--list',) if logger.isEnabledFor(logging.DEBUG) else ()
full_extract_command = (
(local_path, 'extract', '--dry-run')
+ remote_path_flags
+ lock_wait_flags
+ verbosity_flags
+ list_flag
+ (
'{repository}::{last_archive_name}'.format(
repository=repository, last_archive_name=last_archive_name
),
)
)
execute_command(full_extract_command, working_directory=None, error_on_warnings=True)
def extract_archive(
dry_run,
repository,
archive,
paths,
location_config,
storage_config,
local_path='borg',
remote_path=None,
destination_path=None,
progress=False,
error_on_warnings=True,
):
'''
Given a dry-run flag, a local or remote repository path, an archive name, zero or more paths to
restore from the archive, location/storage configuration dicts, optional local and remote Borg
paths, and an optional destination path to extract to, extract the archive into the current
directory.
'''
umask = storage_config.get('umask', None)
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(local_path, 'extract')
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--numeric-owner',) if location_config.get('numeric_owner') else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--dry-run',) if dry_run else ())
+ (('--progress',) if progress else ())
+ ('::'.join((repository if ':' in repository else os.path.abspath(repository), archive)),)
+ (tuple(paths) if paths else ())
)
# The progress output isn't compatible with captured and logged output, as progress messes with
# the terminal directly.
if progress:
execute_command_without_capture(
full_command, working_directory=destination_path, error_on_warnings=error_on_warnings
)
return
# Error on warnings by default, as Borg only gives a warning if the restore paths don't exist in
# the archive!
execute_command(
full_command, working_directory=destination_path, error_on_warnings=error_on_warnings
)
+31
View File
@@ -0,0 +1,31 @@
import itertools
def make_flags(name, value):
'''
Given a flag name and its value, return it formatted as Borg-compatible flags.
'''
if not value:
return ()
flag = '--{}'.format(name.replace('_', '-'))
if value is True:
return (flag,)
return (flag, str(value))
def make_flags_from_arguments(arguments, excludes=()):
'''
Given borgmatic command-line arguments as an instance of argparse.Namespace, and optionally a
list of named arguments to exclude, generate and return the corresponding Borg command-line
flags as a tuple.
'''
return tuple(
itertools.chain.from_iterable(
make_flags(name, value=getattr(arguments, name))
for name in sorted(vars(arguments))
if name not in excludes and not name.startswith('_')
)
)
+43
View File
@@ -0,0 +1,43 @@
import logging
from borgmatic.borg.flags import make_flags, make_flags_from_arguments
from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
def display_archives_info(
repository, storage_config, info_arguments, local_path='borg', remote_path=None
):
'''
Given a local or remote repository path, a storage config dict, and the arguments to the info
action, display summary information for Borg archives in the repository or return JSON summary
information.
'''
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(local_path, 'info')
+ (
('--info',)
if logger.getEffectiveLevel() == logging.INFO and not info_arguments.json
else ()
)
+ (
('--debug', '--show-rc')
if logger.isEnabledFor(logging.DEBUG) and not info_arguments.json
else ()
)
+ make_flags('remote-path', remote_path)
+ make_flags('lock-wait', lock_wait)
+ make_flags_from_arguments(info_arguments, excludes=('repository', 'archive'))
+ (
'::'.join((repository, info_arguments.archive))
if info_arguments.archive
else repository,
)
)
return execute_command(
full_command, output_log_level=None if info_arguments.json else logging.WARNING
)
+48
View File
@@ -0,0 +1,48 @@
import logging
import subprocess
from borgmatic.execute import execute_command, execute_command_without_capture
logger = logging.getLogger(__name__)
INFO_REPOSITORY_NOT_FOUND_EXIT_CODE = 2
def initialize_repository(
repository,
encryption_mode,
append_only=None,
storage_quota=None,
local_path='borg',
remote_path=None,
):
'''
Given a local or remote repository path, a Borg encryption mode, whether the repository should
be append-only, and the storage quota to use, initialize the repository. If the repository
already exists, then log and skip initialization.
'''
info_command = (local_path, 'info', repository)
logger.debug(' '.join(info_command))
try:
execute_command(info_command, output_log_level=None)
logger.info('Repository already exists. Skipping initialization.')
return
except subprocess.CalledProcessError as error:
if error.returncode != INFO_REPOSITORY_NOT_FOUND_EXIT_CODE:
raise
init_command = (
(local_path, 'init')
+ (('--encryption', encryption_mode) if encryption_mode else ())
+ (('--append-only',) if append_only else ())
+ (('--storage-quota', storage_quota) if storage_quota else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--remote-path', remote_path) if remote_path else ())
+ (repository,)
)
# Don't use execute_command() here because it doesn't support interactive prompts.
execute_command_without_capture(init_command)
+50
View File
@@ -0,0 +1,50 @@
import logging
from borgmatic.borg.flags import make_flags, make_flags_from_arguments
from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
# A hack to convince Borg to exclude archives ending in ".checkpoint". This assumes that a
# non-checkpoint archive name ends in a digit (e.g. from a timestamp).
BORG_EXCLUDE_CHECKPOINTS_GLOB = '*[0123456789]'
def list_archives(repository, storage_config, list_arguments, local_path='borg', remote_path=None):
'''
Given a local or remote repository path, a storage config dict, and the arguments to the list
action, display the output of listing Borg archives in the repository or return JSON output. Or,
if an archive name is given, listing the files in that archive.
'''
lock_wait = storage_config.get('lock_wait', None)
if list_arguments.successful:
list_arguments.glob_archives = BORG_EXCLUDE_CHECKPOINTS_GLOB
full_command = (
(local_path, 'list')
+ (
('--info',)
if logger.getEffectiveLevel() == logging.INFO and not list_arguments.json
else ()
)
+ (
('--debug', '--show-rc')
if logger.isEnabledFor(logging.DEBUG) and not list_arguments.json
else ()
)
+ make_flags('remote-path', remote_path)
+ make_flags('lock-wait', lock_wait)
+ make_flags_from_arguments(
list_arguments, excludes=('repository', 'archive', 'successful')
)
+ (
'::'.join((repository, list_arguments.archive))
if list_arguments.archive
else repository,
)
)
return execute_command(
full_command, output_log_level=None if list_arguments.json else logging.WARNING
)
+67
View File
@@ -0,0 +1,67 @@
import logging
from borgmatic.execute import execute_command
logger = logging.getLogger(__name__)
def _make_prune_flags(retention_config):
'''
Given a retention config dict mapping from option name to value, tranform it into an iterable of
command-line name-value flag pairs.
For example, given a retention config of:
{'keep_weekly': 4, 'keep_monthly': 6}
This will be returned as an iterable of:
(
('--keep-weekly', '4'),
('--keep-monthly', '6'),
)
'''
config = retention_config.copy()
if 'prefix' not in config:
config['prefix'] = '{hostname}-'
elif not config['prefix']:
config.pop('prefix')
return (
('--' + option_name.replace('_', '-'), str(value)) for option_name, value in config.items()
)
def prune_archives(
dry_run,
repository,
storage_config,
retention_config,
local_path='borg',
remote_path=None,
stats=False,
):
'''
Given dry-run flag, a local or remote repository path, a storage config dict, and a
retention config dict, prune Borg archives according to the retention policy specified in that
configuration.
'''
umask = storage_config.get('umask', None)
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(local_path, 'prune')
+ tuple(element for pair in _make_prune_flags(retention_config) for element in pair)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--stats',) if not dry_run and logger.isEnabledFor(logging.INFO) else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--dry-run',) if dry_run else ())
+ (('--stats',) if stats else ())
+ (repository,)
)
execute_command(full_command, output_log_level=logging.WARNING if stats else logging.INFO)
+461
View File
@@ -0,0 +1,461 @@
import collections
from argparse import ArgumentParser
from borgmatic.config import collect
SUBPARSER_ALIASES = {
'init': ['--init', '-I'],
'prune': ['--prune', '-p'],
'create': ['--create', '-C'],
'check': ['--check', '-k'],
'extract': ['--extract', '-x'],
'restore': ['--restore', '-r'],
'list': ['--list', '-l'],
'info': ['--info', '-i'],
}
def parse_subparser_arguments(unparsed_arguments, subparsers):
'''
Given a sequence of arguments, and a subparsers object as returned by
argparse.ArgumentParser().add_subparsers(), give each requested action's subparser a shot at
parsing all arguments. This allows common arguments like "--repository" to be shared across
multiple subparsers.
Return the result as a dict mapping from subparser name to a parsed namespace of arguments.
'''
arguments = collections.OrderedDict()
remaining_arguments = list(unparsed_arguments)
alias_to_subparser_name = {
alias: subparser_name
for subparser_name, aliases in SUBPARSER_ALIASES.items()
for alias in aliases
}
for subparser_name, subparser in subparsers.choices.items():
if subparser_name not in remaining_arguments:
continue
canonical_name = alias_to_subparser_name.get(subparser_name, subparser_name)
# If a parsed value happens to be the same as the name of a subparser, remove it from the
# remaining arguments. This prevents, for instance, "check --only extract" from triggering
# the "extract" subparser.
parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
for value in vars(parsed).values():
if isinstance(value, str):
if value in subparsers.choices:
remaining_arguments.remove(value)
elif isinstance(value, list):
for item in value:
if item in subparsers.choices:
remaining_arguments.remove(item)
arguments[canonical_name] = parsed
# If no actions are explicitly requested, assume defaults: prune, create, and check.
if not arguments and '--help' not in unparsed_arguments and '-h' not in unparsed_arguments:
for subparser_name in ('prune', 'create', 'check'):
subparser = subparsers.choices[subparser_name]
parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
arguments[subparser_name] = parsed
return arguments
def parse_global_arguments(unparsed_arguments, top_level_parser, subparsers):
'''
Given a sequence of arguments, a top-level parser (containing subparsers), and a subparsers
object as returned by argparse.ArgumentParser().add_subparsers(), parse and return any global
arguments as a parsed argparse.Namespace instance.
'''
# Ask each subparser, one by one, to greedily consume arguments. Any arguments that remain
# are global arguments.
remaining_arguments = list(unparsed_arguments)
present_subparser_names = set()
for subparser_name, subparser in subparsers.choices.items():
if subparser_name not in remaining_arguments:
continue
present_subparser_names.add(subparser_name)
unused_parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)
# If no actions are explicitly requested, assume defaults: prune, create, and check.
if (
not present_subparser_names
and '--help' not in unparsed_arguments
and '-h' not in unparsed_arguments
):
for subparser_name in ('prune', 'create', 'check'):
subparser = subparsers.choices[subparser_name]
unused_parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)
# Remove the subparser names themselves.
for subparser_name in present_subparser_names:
if subparser_name in remaining_arguments:
remaining_arguments.remove(subparser_name)
return top_level_parser.parse_args(remaining_arguments)
def parse_arguments(*unparsed_arguments):
'''
Given command-line arguments with which this script was invoked, parse the arguments and return
them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
'''
config_paths = collect.get_default_config_paths()
global_parser = ArgumentParser(add_help=False)
global_group = global_parser.add_argument_group('global arguments')
global_group.add_argument(
'-c',
'--config',
nargs='*',
dest='config_paths',
default=config_paths,
help='Configuration filenames or directories, defaults to: {}'.format(
' '.join(config_paths)
),
)
global_group.add_argument(
'--excludes',
dest='excludes_filename',
help='Deprecated in favor of exclude_patterns within configuration',
)
global_group.add_argument(
'-n',
'--dry-run',
dest='dry_run',
action='store_true',
help='Go through the motions, but do not actually write to any repositories',
)
global_group.add_argument(
'-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
)
global_group.add_argument(
'-v',
'--verbosity',
type=int,
choices=range(-1, 3),
default=0,
help='Display verbose progress to the console (from none to lots: 0, 1, or 2) or only errors (-1)',
)
global_group.add_argument(
'--syslog-verbosity',
type=int,
choices=range(-1, 3),
default=0,
help='Log verbose progress to syslog (from none to lots: 0, 1, or 2) or only errors (-1). Ignored when console is interactive or --log-file is given',
)
global_group.add_argument(
'--log-file-verbosity',
type=int,
choices=range(-1, 3),
default=0,
help='Log verbose progress to log file (from none to lots: 0, 1, or 2) or only errors (-1). Only used when --log-file is given',
)
global_group.add_argument(
'--log-file',
type=str,
default=None,
help='Write log messages to this file instead of syslog',
)
global_group.add_argument(
'--version',
dest='version',
default=False,
action='store_true',
help='Display installed version number of borgmatic and exit',
)
top_level_parser = ArgumentParser(
description='''
A simple wrapper script for the Borg backup software that creates and prunes backups.
If none of the action options are given, then borgmatic defaults to: prune, create, and
check archives.
''',
parents=[global_parser],
)
subparsers = top_level_parser.add_subparsers(
title='actions',
metavar='',
help='Specify zero or more actions. Defaults to prune, create, and check. Use --help with action for details:',
)
init_parser = subparsers.add_parser(
'init',
aliases=SUBPARSER_ALIASES['init'],
help='Initialize an empty Borg repository',
description='Initialize an empty Borg repository',
add_help=False,
)
init_group = init_parser.add_argument_group('init arguments')
init_group.add_argument(
'-e',
'--encryption',
dest='encryption_mode',
help='Borg repository encryption mode',
required=True,
)
init_group.add_argument(
'--append-only',
dest='append_only',
action='store_true',
help='Create an append-only repository',
)
init_group.add_argument(
'--storage-quota',
dest='storage_quota',
help='Create a repository with a fixed storage quota',
)
init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
prune_parser = subparsers.add_parser(
'prune',
aliases=SUBPARSER_ALIASES['prune'],
help='Prune archives according to the retention policy',
description='Prune archives according to the retention policy',
add_help=False,
)
prune_group = prune_parser.add_argument_group('prune arguments')
prune_group.add_argument(
'--stats',
dest='stats',
default=False,
action='store_true',
help='Display statistics of archive',
)
prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
create_parser = subparsers.add_parser(
'create',
aliases=SUBPARSER_ALIASES['create'],
help='Create archives (actually perform backups)',
description='Create archives (actually perform backups)',
add_help=False,
)
create_group = create_parser.add_argument_group('create arguments')
create_group.add_argument(
'--progress',
dest='progress',
default=False,
action='store_true',
help='Display progress for each file as it is processed',
)
create_group.add_argument(
'--stats',
dest='stats',
default=False,
action='store_true',
help='Display statistics of archive',
)
create_group.add_argument(
'--json', dest='json', default=False, action='store_true', help='Output results as JSON'
)
create_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
check_parser = subparsers.add_parser(
'check',
aliases=SUBPARSER_ALIASES['check'],
help='Check archives for consistency',
description='Check archives for consistency',
add_help=False,
)
check_group = check_parser.add_argument_group('check arguments')
check_group.add_argument(
'--only',
metavar='CHECK',
choices=('repository', 'archives', 'data', 'extract'),
dest='only',
action='append',
help='Run a particular consistency check (repository, archives, data, or extract) instead of configured checks; can specify flag multiple times',
)
check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
extract_parser = subparsers.add_parser(
'extract',
aliases=SUBPARSER_ALIASES['extract'],
help='Extract files from a named archive to the current directory',
description='Extract a named archive to the current directory',
add_help=False,
)
extract_group = extract_parser.add_argument_group('extract arguments')
extract_group.add_argument(
'--repository',
help='Path of repository to extract, defaults to the configured repository if there is only one',
)
extract_group.add_argument('--archive', help='Name of archive to extract', required=True)
extract_group.add_argument(
'--path',
'--restore-path',
metavar='PATH',
nargs='+',
dest='paths',
help='Paths to extract from archive, defaults to the entire archive',
)
extract_group.add_argument(
'--destination',
metavar='PATH',
dest='destination',
help='Directory to extract files into, defaults to the current directory',
)
extract_group.add_argument(
'--progress',
dest='progress',
default=False,
action='store_true',
help='Display progress for each file as it is processed',
)
extract_group.add_argument(
'-h', '--help', action='help', help='Show this help message and exit'
)
restore_parser = subparsers.add_parser(
'restore',
aliases=SUBPARSER_ALIASES['restore'],
help='Restore database dumps from a named archive',
description='Restore database dumps from a named archive. (To extract files instead, use "borgmatic extract".)',
add_help=False,
)
restore_group = restore_parser.add_argument_group('restore arguments')
restore_group.add_argument(
'--repository',
help='Path of repository to restore from, defaults to the configured repository if there is only one',
)
restore_group.add_argument('--archive', help='Name of archive to restore from', required=True)
restore_group.add_argument(
'--database',
metavar='NAME',
nargs='+',
dest='databases',
help='Names of databases to restore from archive, defaults to all databases. Note that any databases to restore must be defined in borgmatic\'s configuration',
)
restore_group.add_argument(
'--progress',
dest='progress',
default=False,
action='store_true',
help='Display progress for each database dump file as it is extracted from archive',
)
restore_group.add_argument(
'-h', '--help', action='help', help='Show this help message and exit'
)
list_parser = subparsers.add_parser(
'list',
aliases=SUBPARSER_ALIASES['list'],
help='List archives',
description='List archives or the contents of an archive',
add_help=False,
)
list_group = list_parser.add_argument_group('list arguments')
list_group.add_argument(
'--repository',
help='Path of repository to list, defaults to the configured repository if there is only one',
)
list_group.add_argument('--archive', help='Name of archive to list')
list_group.add_argument(
'--short', default=False, action='store_true', help='Output only archive or path names'
)
list_group.add_argument('--format', help='Format for file listing')
list_group.add_argument(
'--json', default=False, action='store_true', help='Output results as JSON'
)
list_group.add_argument(
'-P', '--prefix', help='Only list archive names starting with this prefix'
)
list_group.add_argument(
'-a', '--glob-archives', metavar='GLOB', help='Only list archive names matching this glob'
)
list_group.add_argument(
'--successful',
default=False,
action='store_true',
help='Only list archive names of successful (non-checkpoint) backups',
)
list_group.add_argument(
'--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
)
list_group.add_argument(
'--first', metavar='N', help='List first N archives after other filters are applied'
)
list_group.add_argument(
'--last', metavar='N', help='List last N archives after other filters are applied'
)
list_group.add_argument(
'-e', '--exclude', metavar='PATTERN', help='Exclude paths matching the pattern'
)
list_group.add_argument(
'--exclude-from', metavar='FILENAME', help='Exclude paths from exclude file, one per line'
)
list_group.add_argument('--pattern', help='Include or exclude paths matching a pattern')
list_group.add_argument(
'--patterns-from',
metavar='FILENAME',
help='Include or exclude paths matching patterns from pattern file, one per line',
)
list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
info_parser = subparsers.add_parser(
'info',
aliases=SUBPARSER_ALIASES['info'],
help='Display summary information on archives',
description='Display summary information on archives',
add_help=False,
)
info_group = info_parser.add_argument_group('info arguments')
info_group.add_argument(
'--repository',
help='Path of repository to show info for, defaults to the configured repository if there is only one',
)
info_group.add_argument('--archive', help='Name of archive to show info for')
info_group.add_argument(
'--json', dest='json', default=False, action='store_true', help='Output results as JSON'
)
info_group.add_argument(
'-P', '--prefix', help='Only show info for archive names starting with this prefix'
)
info_group.add_argument(
'-a',
'--glob-archives',
metavar='GLOB',
help='Only show info for archive names matching this glob',
)
info_group.add_argument(
'--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
)
info_group.add_argument(
'--first',
metavar='N',
help='Show info for first N archives after other filters are applied',
)
info_group.add_argument(
'--last', metavar='N', help='Show info for first N archives after other filters are applied'
)
info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
arguments = parse_subparser_arguments(unparsed_arguments, subparsers)
arguments['global'] = parse_global_arguments(unparsed_arguments, top_level_parser, subparsers)
if arguments['global'].excludes_filename:
raise ValueError(
'The --excludes option has been replaced with exclude_patterns in configuration'
)
if 'init' in arguments and arguments['global'].dry_run:
raise ValueError('The init action cannot be used with the --dry-run option')
if 'list' in arguments and arguments['list'].glob_archives and arguments['list'].successful:
raise ValueError('The --glob-archives and --successful options cannot be used together')
if (
'list' in arguments
and 'info' in arguments
and arguments['list'].json
and arguments['info'].json
):
raise ValueError('With the --json option, list and info actions cannot be used together')
return arguments
+549
View File
@@ -0,0 +1,549 @@
import collections
import json
import logging
import os
import sys
from subprocess import CalledProcessError
import colorama
import pkg_resources
from borgmatic.borg import check as borg_check
from borgmatic.borg import create as borg_create
from borgmatic.borg import environment as borg_environment
from borgmatic.borg import extract as borg_extract
from borgmatic.borg import info as borg_info
from borgmatic.borg import init as borg_init
from borgmatic.borg import list as borg_list
from borgmatic.borg import prune as borg_prune
from borgmatic.commands.arguments import parse_arguments
from borgmatic.config import checks, collect, convert, validate
from borgmatic.hooks import command, cronhub, cronitor, dispatch, dump, healthchecks
from borgmatic.logger import configure_logging, should_do_markup
from borgmatic.signals import configure_signals
from borgmatic.verbosity import verbosity_to_log_level
logger = logging.getLogger(__name__)
LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
def run_configuration(config_filename, config, arguments):
'''
Given a config filename, the corresponding parsed config dict, and command-line arguments as a
dict from subparser name to a namespace of parsed arguments, execute its defined pruning,
backups, consistency checks, and/or other actions.
Yield a combination of:
* JSON output strings from successfully executing any actions that produce JSON
* logging.LogRecord instances containing errors from any actions or backup hooks that fail
'''
(location, storage, retention, consistency, hooks) = (
config.get(section_name, {})
for section_name in ('location', 'storage', 'retention', 'consistency', 'hooks')
)
global_arguments = arguments['global']
local_path = location.get('local_path', 'borg')
remote_path = location.get('remote_path')
borg_environment.initialize(storage)
encountered_error = None
error_repository = ''
if 'create' in arguments:
try:
healthchecks.ping_healthchecks(
hooks.get('healthchecks'), config_filename, global_arguments.dry_run, 'start'
)
cronitor.ping_cronitor(
hooks.get('cronitor'), config_filename, global_arguments.dry_run, 'run'
)
cronhub.ping_cronhub(
hooks.get('cronhub'), config_filename, global_arguments.dry_run, 'start'
)
command.execute_hook(
hooks.get('before_backup'),
hooks.get('umask'),
config_filename,
'pre-backup',
global_arguments.dry_run,
)
dispatch.call_hooks(
'dump_databases',
hooks,
config_filename,
dump.DATABASE_HOOK_NAMES,
global_arguments.dry_run,
)
except (OSError, CalledProcessError) as error:
encountered_error = error
yield from make_error_log_records(
'{}: Error running pre-backup hook'.format(config_filename), error
)
if not encountered_error:
for repository_path in location['repositories']:
try:
yield from run_actions(
arguments=arguments,
location=location,
storage=storage,
retention=retention,
consistency=consistency,
hooks=hooks,
local_path=local_path,
remote_path=remote_path,
repository_path=repository_path,
)
except (OSError, CalledProcessError, ValueError) as error:
encountered_error = error
error_repository = repository_path
yield from make_error_log_records(
'{}: Error running actions for repository'.format(repository_path), error
)
if 'create' in arguments and not encountered_error:
try:
dispatch.call_hooks(
'remove_database_dumps',
hooks,
config_filename,
dump.DATABASE_HOOK_NAMES,
global_arguments.dry_run,
)
command.execute_hook(
hooks.get('after_backup'),
hooks.get('umask'),
config_filename,
'post-backup',
global_arguments.dry_run,
)
healthchecks.ping_healthchecks(
hooks.get('healthchecks'), config_filename, global_arguments.dry_run
)
cronitor.ping_cronitor(
hooks.get('cronitor'), config_filename, global_arguments.dry_run, 'complete'
)
cronhub.ping_cronhub(
hooks.get('cronhub'), config_filename, global_arguments.dry_run, 'finish'
)
except (OSError, CalledProcessError) as error:
encountered_error = error
yield from make_error_log_records(
'{}: Error running post-backup hook'.format(config_filename), error
)
if encountered_error:
try:
command.execute_hook(
hooks.get('on_error'),
hooks.get('umask'),
config_filename,
'on-error',
global_arguments.dry_run,
repository=error_repository,
error=encountered_error,
output=getattr(encountered_error, 'output', ''),
)
healthchecks.ping_healthchecks(
hooks.get('healthchecks'), config_filename, global_arguments.dry_run, 'fail'
)
cronitor.ping_cronitor(
hooks.get('cronitor'), config_filename, global_arguments.dry_run, 'fail'
)
cronhub.ping_cronhub(
hooks.get('cronhub'), config_filename, global_arguments.dry_run, 'fail'
)
except (OSError, CalledProcessError) as error:
yield from make_error_log_records(
'{}: Error running on-error hook'.format(config_filename), error
)
def run_actions(
*,
arguments,
location,
storage,
retention,
consistency,
hooks,
local_path,
remote_path,
repository_path
): # pragma: no cover
'''
Given parsed command-line arguments as an argparse.ArgumentParser instance, several different
configuration dicts, local and remote paths to Borg, and a repository name, run all actions
from the command-line arguments on the given repository.
Yield JSON output strings from executing any actions that produce JSON.
Raise OSError or subprocess.CalledProcessError if an error occurs running a command for an
action. Raise ValueError if the arguments or configuration passed to action are invalid.
'''
repository = os.path.expanduser(repository_path)
global_arguments = arguments['global']
dry_run_label = ' (dry run; not making any changes)' if global_arguments.dry_run else ''
if 'init' in arguments:
logger.info('{}: Initializing repository'.format(repository))
borg_init.initialize_repository(
repository,
arguments['init'].encryption_mode,
arguments['init'].append_only,
arguments['init'].storage_quota,
local_path=local_path,
remote_path=remote_path,
)
if 'prune' in arguments:
logger.info('{}: Pruning archives{}'.format(repository, dry_run_label))
borg_prune.prune_archives(
global_arguments.dry_run,
repository,
storage,
retention,
local_path=local_path,
remote_path=remote_path,
stats=arguments['prune'].stats,
)
if 'create' in arguments:
logger.info('{}: Creating archive{}'.format(repository, dry_run_label))
json_output = borg_create.create_archive(
global_arguments.dry_run,
repository,
location,
storage,
local_path=local_path,
remote_path=remote_path,
progress=arguments['create'].progress,
stats=arguments['create'].stats,
json=arguments['create'].json,
)
if json_output:
yield json.loads(json_output)
if 'check' in arguments and checks.repository_enabled_for_checks(repository, consistency):
logger.info('{}: Running consistency checks'.format(repository))
borg_check.check_archives(
repository,
storage,
consistency,
local_path=local_path,
remote_path=remote_path,
only_checks=arguments['check'].only,
)
if 'extract' in arguments:
if arguments['extract'].repository is None or repository == arguments['extract'].repository:
logger.info(
'{}: Extracting archive {}'.format(repository, arguments['extract'].archive)
)
borg_extract.extract_archive(
global_arguments.dry_run,
repository,
arguments['extract'].archive,
arguments['extract'].paths,
location,
storage,
local_path=local_path,
remote_path=remote_path,
destination_path=arguments['extract'].destination,
progress=arguments['extract'].progress,
)
if 'restore' in arguments:
if arguments['restore'].repository is None or repository == arguments['restore'].repository:
logger.info(
'{}: Restoring databases from archive {}'.format(
repository, arguments['restore'].archive
)
)
restore_names = arguments['restore'].databases or []
if 'all' in restore_names:
restore_names = []
# Extract dumps for the named databases from the archive.
dump_patterns = dispatch.call_hooks(
'make_database_dump_patterns',
hooks,
repository,
dump.DATABASE_HOOK_NAMES,
restore_names,
)
borg_extract.extract_archive(
global_arguments.dry_run,
repository,
arguments['restore'].archive,
dump.convert_glob_patterns_to_borg_patterns(
[pattern for patterns in dump_patterns.values() for pattern in patterns]
),
location,
storage,
local_path=local_path,
remote_path=remote_path,
destination_path='/',
progress=arguments['restore'].progress,
# We don't want glob patterns that don't match to error.
error_on_warnings=False,
)
# Map the restore names or detected dumps to the corresponding database configurations.
restore_databases = dump.get_per_hook_database_configurations(
hooks, restore_names, dump_patterns
)
# Finally, restore the databases and cleanup the dumps.
dispatch.call_hooks(
'restore_database_dumps',
restore_databases,
repository,
dump.DATABASE_HOOK_NAMES,
global_arguments.dry_run,
)
dispatch.call_hooks(
'remove_database_dumps',
restore_databases,
repository,
dump.DATABASE_HOOK_NAMES,
global_arguments.dry_run,
)
if 'list' in arguments:
if arguments['list'].repository is None or repository == arguments['list'].repository:
logger.info('{}: Listing archives'.format(repository))
json_output = borg_list.list_archives(
repository,
storage,
list_arguments=arguments['list'],
local_path=local_path,
remote_path=remote_path,
)
if json_output:
yield json.loads(json_output)
if 'info' in arguments:
if arguments['info'].repository is None or repository == arguments['info'].repository:
logger.info('{}: Displaying summary info for archives'.format(repository))
json_output = borg_info.display_archives_info(
repository,
storage,
info_arguments=arguments['info'],
local_path=local_path,
remote_path=remote_path,
)
if json_output:
yield json.loads(json_output)
def load_configurations(config_filenames):
'''
Given a sequence of configuration filenames, load and validate each configuration file. Return
the results as a tuple of: dict of configuration filename to corresponding parsed configuration,
and sequence of logging.LogRecord instances containing any parse errors.
'''
# Dict mapping from config filename to corresponding parsed config dict.
configs = collections.OrderedDict()
logs = []
# Parse and load each configuration file.
for config_filename in config_filenames:
try:
configs[config_filename] = validate.parse_configuration(
config_filename, validate.schema_filename()
)
except (ValueError, OSError, validate.Validation_error) as error:
logs.extend(
[
logging.makeLogRecord(
dict(
levelno=logging.CRITICAL,
levelname='CRITICAL',
msg='{}: Error parsing configuration file'.format(config_filename),
)
),
logging.makeLogRecord(
dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=error)
),
]
)
return (configs, logs)
def make_error_log_records(message, error=None):
'''
Given error message text and an optional exception object, yield a series of logging.LogRecord
instances with error summary information.
'''
if not error:
yield logging.makeLogRecord(
dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=message)
)
return
try:
raise error
except CalledProcessError as error:
yield logging.makeLogRecord(
dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=message)
)
if error.output:
yield logging.makeLogRecord(
dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=error.output)
)
yield logging.makeLogRecord(dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=error))
except (ValueError, OSError) as error:
yield logging.makeLogRecord(
dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=message)
)
yield logging.makeLogRecord(dict(levelno=logging.CRITICAL, levelname='CRITICAL', msg=error))
except: # noqa: E722
# Raising above only as a means of determining the error type. Swallow the exception here
# because we don't want the exception to propagate out of this function.
pass
def collect_configuration_run_summary_logs(configs, arguments):
'''
Given a dict of configuration filename to corresponding parsed configuration, and parsed
command-line arguments as a dict from subparser name to a parsed namespace of arguments, run
each configuration file and yield a series of logging.LogRecord instances containing summary
information about each run.
As a side effect of running through these configuration files, output their JSON results, if
any, to stdout.
'''
# Run cross-file validation checks.
if 'extract' in arguments:
repository = arguments['extract'].repository
elif 'list' in arguments and arguments['list'].archive:
repository = arguments['list'].repository
else:
repository = None
if repository:
try:
validate.guard_configuration_contains_repository(repository, configs)
except ValueError as error:
yield from make_error_log_records(str(error))
return
if not configs:
yield from make_error_log_records(
'{}: No configuration files found'.format(' '.join(arguments['global'].config_paths))
)
return
if 'create' in arguments:
try:
for config_filename, config in configs.items():
hooks = config.get('hooks', {})
command.execute_hook(
hooks.get('before_everything'),
hooks.get('umask'),
config_filename,
'pre-everything',
arguments['global'].dry_run,
)
except (CalledProcessError, ValueError, OSError) as error:
yield from make_error_log_records('Error running pre-everything hook', error)
return
# Execute the actions corresponding to each configuration file.
json_results = []
for config_filename, config in configs.items():
results = list(run_configuration(config_filename, config, arguments))
error_logs = tuple(result for result in results if isinstance(result, logging.LogRecord))
if error_logs:
yield from make_error_log_records(
'{}: Error running configuration file'.format(config_filename)
)
yield from error_logs
else:
yield logging.makeLogRecord(
dict(
levelno=logging.INFO,
levelname='INFO',
msg='{}: Successfully ran configuration file'.format(config_filename),
)
)
if results:
json_results.extend(results)
if json_results:
sys.stdout.write(json.dumps(json_results))
if 'create' in arguments:
try:
for config_filename, config in configs.items():
hooks = config.get('hooks', {})
command.execute_hook(
hooks.get('after_everything'),
hooks.get('umask'),
config_filename,
'post-everything',
arguments['global'].dry_run,
)
except (CalledProcessError, ValueError, OSError) as error:
yield from make_error_log_records('Error running post-everything hook', error)
def exit_with_help_link(): # pragma: no cover
'''
Display a link to get help and exit with an error code.
'''
logger.critical('')
logger.critical('Need some help? https://torsion.org/borgmatic/#issues')
sys.exit(1)
def main(): # pragma: no cover
configure_signals()
try:
arguments = parse_arguments(*sys.argv[1:])
except ValueError as error:
configure_logging(logging.CRITICAL)
logger.critical(error)
exit_with_help_link()
except SystemExit as error:
if error.code == 0:
raise error
configure_logging(logging.CRITICAL)
logger.critical('Error parsing arguments: {}'.format(' '.join(sys.argv)))
exit_with_help_link()
global_arguments = arguments['global']
if global_arguments.version:
print(pkg_resources.require('borgmatic')[0].version)
sys.exit(0)
config_filenames = tuple(collect.collect_config_filenames(global_arguments.config_paths))
configs, parse_logs = load_configurations(config_filenames)
colorama.init(autoreset=True, strip=not should_do_markup(global_arguments.no_color, configs))
try:
configure_logging(
verbosity_to_log_level(global_arguments.verbosity),
verbosity_to_log_level(global_arguments.syslog_verbosity),
verbosity_to_log_level(global_arguments.log_file_verbosity),
global_arguments.log_file,
)
except (FileNotFoundError, PermissionError) as error:
configure_logging(logging.CRITICAL)
logger.critical('Error configuring logging: {}'.format(error))
exit_with_help_link()
logger.debug('Ensuring legacy configuration is upgraded')
convert.guard_configuration_upgraded(LEGACY_CONFIG_PATH, config_filenames)
summary_logs = list(collect_configuration_run_summary_logs(configs, arguments))
logger.info('')
logger.info('summary:')
[
logger.handle(log)
for log in parse_logs + summary_logs
if log.levelno >= logger.getEffectiveLevel()
]
if any(log.levelno == logging.CRITICAL for log in summary_logs):
exit_with_help_link()
+108
View File
@@ -0,0 +1,108 @@
import os
import sys
import textwrap
from argparse import ArgumentParser
from ruamel import yaml
from borgmatic.config import convert, generate, legacy, validate
DEFAULT_SOURCE_CONFIG_FILENAME = '/etc/borgmatic/config'
DEFAULT_SOURCE_EXCLUDES_FILENAME = '/etc/borgmatic/excludes'
DEFAULT_DESTINATION_CONFIG_FILENAME = '/etc/borgmatic/config.yaml'
def parse_arguments(*arguments):
'''
Given command-line arguments with which this script was invoked, parse the arguments and return
them as an ArgumentParser instance.
'''
parser = ArgumentParser(
description='''
Convert legacy INI-style borgmatic configuration and excludes files to a single YAML
configuration file. Note that this replaces any comments from the source files.
'''
)
parser.add_argument(
'-s',
'--source-config',
dest='source_config_filename',
default=DEFAULT_SOURCE_CONFIG_FILENAME,
help='Source INI-style configuration filename. Default: {}'.format(
DEFAULT_SOURCE_CONFIG_FILENAME
),
)
parser.add_argument(
'-e',
'--source-excludes',
dest='source_excludes_filename',
default=DEFAULT_SOURCE_EXCLUDES_FILENAME
if os.path.exists(DEFAULT_SOURCE_EXCLUDES_FILENAME)
else None,
help='Excludes filename',
)
parser.add_argument(
'-d',
'--destination-config',
dest='destination_config_filename',
default=DEFAULT_DESTINATION_CONFIG_FILENAME,
help='Destination YAML configuration filename. Default: {}'.format(
DEFAULT_DESTINATION_CONFIG_FILENAME
),
)
return parser.parse_args(arguments)
TEXT_WRAP_CHARACTERS = 80
def display_result(args): # pragma: no cover
result_lines = textwrap.wrap(
'Your borgmatic configuration has been upgraded. Please review the result in {}.'.format(
args.destination_config_filename
),
TEXT_WRAP_CHARACTERS,
)
delete_lines = textwrap.wrap(
'Once you are satisfied, you can safely delete {}{}.'.format(
args.source_config_filename,
' and {}'.format(args.source_excludes_filename)
if args.source_excludes_filename
else '',
),
TEXT_WRAP_CHARACTERS,
)
print('\n'.join(result_lines))
print()
print('\n'.join(delete_lines))
def main(): # pragma: no cover
try:
args = parse_arguments(*sys.argv[1:])
schema = yaml.round_trip_load(open(validate.schema_filename()).read())
source_config = legacy.parse_configuration(
args.source_config_filename, legacy.CONFIG_FORMAT
)
source_config_file_mode = os.stat(args.source_config_filename).st_mode
source_excludes = (
open(args.source_excludes_filename).read().splitlines()
if args.source_excludes_filename
else []
)
destination_config = convert.convert_legacy_parsed_config(
source_config, source_excludes, schema
)
generate.write_configuration(
args.destination_config_filename, destination_config, mode=source_config_file_mode
)
display_result(args)
except (ValueError, OSError) as error:
print(error, file=sys.stderr)
sys.exit(1)
+60
View File
@@ -0,0 +1,60 @@
import sys
from argparse import ArgumentParser
from borgmatic.config import generate, validate
DEFAULT_DESTINATION_CONFIG_FILENAME = '/etc/borgmatic/config.yaml'
def parse_arguments(*arguments):
'''
Given command-line arguments with which this script was invoked, parse the arguments and return
them as an ArgumentParser instance.
'''
parser = ArgumentParser(description='Generate a sample borgmatic YAML configuration file.')
parser.add_argument(
'-s',
'--source',
dest='source_filename',
help='Optional YAML configuration file to merge into the generated configuration, useful for upgrading your configuration',
)
parser.add_argument(
'-d',
'--destination',
dest='destination_filename',
default=DEFAULT_DESTINATION_CONFIG_FILENAME,
help='Destination YAML configuration file. Default: {}'.format(
DEFAULT_DESTINATION_CONFIG_FILENAME
),
)
return parser.parse_args(arguments)
def main(): # pragma: no cover
try:
args = parse_arguments(*sys.argv[1:])
generate.generate_sample_configuration(
args.source_filename, args.destination_filename, validate.schema_filename()
)
print('Generated a sample configuration file at {}.'.format(args.destination_filename))
print()
if args.source_filename:
print(
'Merged in the contents of configuration file at {}.'.format(args.source_filename)
)
print('To review the changes made, run:')
print()
print(
' diff --unified {} {}'.format(args.source_filename, args.destination_filename)
)
print()
print('Please edit the file to suit your needs. The values are representative.')
print('All fields are optional except where indicated.')
print()
print('If you ever need help: https://torsion.org/borgmatic/#issues')
except (ValueError, OSError) as error:
print(error, file=sys.stderr)
sys.exit(1)
+56
View File
@@ -0,0 +1,56 @@
import logging
import sys
from argparse import ArgumentParser
from borgmatic.config import collect, validate
logger = logging.getLogger(__name__)
def parse_arguments(*arguments):
'''
Given command-line arguments with which this script was invoked, parse the arguments and return
them as an ArgumentParser instance.
'''
config_paths = collect.get_default_config_paths()
parser = ArgumentParser(description='Validate borgmatic configuration file(s).')
parser.add_argument(
'-c',
'--config',
nargs='+',
dest='config_paths',
default=config_paths,
help='Configuration filenames or directories, defaults to: {}'.format(
' '.join(config_paths)
),
)
return parser.parse_args(arguments)
def main(): # pragma: no cover
args = parse_arguments(*sys.argv[1:])
logging.basicConfig(level=logging.INFO, format='%(message)s')
config_filenames = tuple(collect.collect_config_filenames(args.config_paths))
if len(config_filenames) == 0:
logger.critical('No files to validate found')
sys.exit(1)
found_issues = False
for config_filename in config_filenames:
try:
validate.parse_configuration(config_filename, validate.schema_filename())
except (ValueError, OSError, validate.Validation_error) as error:
logging.critical('{}: Error parsing configuration file'.format(config_filename))
logging.critical(error)
found_issues = True
if found_issues:
sys.exit(1)
else:
logger.info(
'All given configuration files are valid: {}'.format(', '.join(config_filenames))
)
+9
View File
@@ -0,0 +1,9 @@
def repository_enabled_for_checks(repository, consistency):
'''
Given a repository name and a consistency configuration dict, return whether the repository
is enabled to have consistency checks run.
'''
if not consistency.get('check_repositories'):
return True
return repository in consistency['check_repositories']
+48
View File
@@ -0,0 +1,48 @@
import os
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.
'''
user_config_directory = os.getenv('XDG_CONFIG_HOME') or os.path.expandvars(
os.path.join('$HOME', '.config')
)
return [
'/etc/borgmatic/config.yaml',
'/etc/borgmatic.d',
'%s/borgmatic/config.yaml' % user_config_directory,
]
def collect_config_filenames(config_paths):
'''
Given a sequence of config paths, both filenames and directories, resolve that to an iterable
of files. Accomplish this by listing any given directories looking for contained config files
(ending with the ".yaml" or ".yml" extension). This is non-recursive, so any directories within the given
directories are ignored.
Return paths even if they don't exist on disk, so the user can find out about missing
configuration paths. However, skip a default config path if it's missing, so the user doesn't
have to create a default config path unless they need it.
'''
real_default_config_paths = set(map(os.path.realpath, get_default_config_paths()))
for path in config_paths:
exists = os.path.exists(path)
if os.path.realpath(path) in real_default_config_paths and not exists:
continue
if not os.path.isdir(path) or not exists:
yield path
continue
for filename in sorted(os.listdir(path)):
full_filename = os.path.join(path, filename)
matching_filetype = full_filename.endswith('.yaml') or full_filename.endswith('.yml')
if matching_filetype and not os.path.isdir(full_filename):
yield full_filename
+95
View File
@@ -0,0 +1,95 @@
import os
from ruamel import yaml
from borgmatic.config import generate
def _convert_section(source_section_config, section_schema):
'''
Given a legacy Parsed_config instance for a single section, convert it to its corresponding
yaml.comments.CommentedMap representation in preparation for actual serialization to YAML.
Where integer types exist in the given section schema, convert their values to integers.
'''
destination_section_config = yaml.comments.CommentedMap(
[
(
option_name,
int(option_value)
if section_schema['map'].get(option_name, {}).get('type') == 'int'
else option_value,
)
for option_name, option_value in source_section_config.items()
]
)
return destination_section_config
def convert_legacy_parsed_config(source_config, source_excludes, schema):
'''
Given a legacy Parsed_config instance loaded from an INI-style config file and a list of exclude
patterns, convert them to a corresponding yaml.comments.CommentedMap representation in
preparation for serialization to a single YAML config file.
Additionally, use the given schema as a source of helpful comments to include within the
returned CommentedMap.
'''
destination_config = yaml.comments.CommentedMap(
[
(section_name, _convert_section(section_config, schema['map'][section_name]))
for section_name, section_config in source_config._asdict().items()
]
)
# Split space-seperated values into actual lists, make "repository" into a list, and merge in
# excludes.
location = destination_config['location']
location['source_directories'] = source_config.location['source_directories'].split(' ')
location['repositories'] = [location.pop('repository')]
location['exclude_patterns'] = source_excludes
if source_config.consistency.get('checks'):
destination_config['consistency']['checks'] = source_config.consistency['checks'].split(' ')
# Add comments to each section, and then add comments to the fields in each section.
generate.add_comments_to_configuration_map(destination_config, schema)
for section_name, section_config in destination_config.items():
generate.add_comments_to_configuration_map(
section_config, schema['map'][section_name], indent=generate.INDENT
)
return destination_config
class Legacy_configuration_not_upgraded(FileNotFoundError):
def __init__(self):
super(Legacy_configuration_not_upgraded, self).__init__(
'''borgmatic changed its configuration file format in version 1.1.0 from INI-style
to YAML. This better supports validation, and has a more natural way to express
lists of values. To upgrade your existing configuration, run:
sudo upgrade-borgmatic-config
That will generate a new YAML configuration file at /etc/borgmatic/config.yaml
(by default) using the values from both your existing configuration and excludes
files. The new version of borgmatic will consume the YAML configuration file
instead of the old one.'''
)
def guard_configuration_upgraded(source_config_filename, destination_config_filenames):
'''
If legacy source configuration exists but no destination upgraded configs do, raise
Legacy_configuration_not_upgraded.
The idea is that we want to alert the user about upgrading their config if they haven't already.
'''
destination_config_exists = any(
os.path.exists(filename) for filename in destination_config_filenames
)
if os.path.exists(source_config_filename) and not destination_config_exists:
raise Legacy_configuration_not_upgraded()
+290
View File
@@ -0,0 +1,290 @@
import collections
import io
import os
import re
from ruamel import yaml
from borgmatic.config import load
INDENT = 4
SEQUENCE_INDENT = 2
def _insert_newline_before_comment(config, field_name):
'''
Using some ruamel.yaml black magic, insert a blank line in the config right before the given
field and its comments.
'''
config.ca.items[field_name][1].insert(
0, yaml.tokens.CommentToken('\n', yaml.error.CommentMark(0), None)
)
def _schema_to_sample_configuration(schema, level=0, parent_is_sequence=False):
'''
Given a loaded configuration schema, generate and return sample config for it. Include comments
for each section based on the schema "desc" description.
'''
example = schema.get('example')
if example is not None:
return example
if 'seq' in schema:
config = yaml.comments.CommentedSeq(
[
_schema_to_sample_configuration(item_schema, level, parent_is_sequence=True)
for item_schema in schema['seq']
]
)
add_comments_to_configuration_sequence(
config, schema, indent=(level * INDENT) + SEQUENCE_INDENT
)
elif 'map' in schema:
config = yaml.comments.CommentedMap(
[
(field_name, _schema_to_sample_configuration(sub_schema, level + 1))
for field_name, sub_schema in schema['map'].items()
]
)
indent = (level * INDENT) + (SEQUENCE_INDENT if parent_is_sequence else 0)
add_comments_to_configuration_map(
config, schema, indent=indent, skip_first=parent_is_sequence
)
else:
raise ValueError('Schema at level {} is unsupported: {}'.format(level, schema))
return config
def _comment_out_line(line):
# If it's already is commented out (or empty), there's nothing further to do!
stripped_line = line.lstrip()
if not stripped_line or stripped_line.startswith('#'):
return line
# Comment out the names of optional sections, inserting the '#' after any indent for aesthetics.
matches = re.match(r'(\s*)', line)
indent_spaces = matches.group(0) if matches else ''
count_indent_spaces = len(indent_spaces)
return '# '.join((indent_spaces, line[count_indent_spaces:]))
def _comment_out_optional_configuration(rendered_config):
'''
Post-process a rendered configuration string to comment out optional key/values, as determined
by a sentinel in the comment before each key.
The idea is that the pre-commented configuration prevents the user from having to comment out a
bunch of configuration they don't care about to get to a minimal viable configuration file.
Ideally ruamel.yaml would support commenting out keys during configuration generation, but it's
not terribly easy to accomplish that way.
'''
lines = []
optional = False
for line in rendered_config.split('\n'):
# Upon encountering an optional configuration option, commenting out lines until the next
# blank line.
if line.strip().startswith('# {}'.format(COMMENTED_OUT_SENTINEL)):
optional = True
continue
# Hit a blank line, so reset commenting.
if not line.strip():
optional = False
lines.append(_comment_out_line(line) if optional else line)
return '\n'.join(lines)
def _render_configuration(config):
'''
Given a config data structure of nested OrderedDicts, render the config as YAML and return it.
'''
dumper = yaml.YAML()
dumper.indent(mapping=INDENT, sequence=INDENT + SEQUENCE_INDENT, offset=INDENT)
rendered = io.StringIO()
dumper.dump(config, rendered)
return rendered.getvalue()
def write_configuration(config_filename, rendered_config, mode=0o600):
'''
Given a target config filename and rendered config YAML, write it out to file. Create any
containing directories as needed.
'''
if os.path.exists(config_filename):
raise FileExistsError('{} already exists. Aborting.'.format(config_filename))
try:
os.makedirs(os.path.dirname(config_filename), mode=0o700)
except (FileExistsError, FileNotFoundError):
pass
with open(config_filename, 'w') as config_file:
config_file.write(rendered_config)
os.chmod(config_filename, mode)
def add_comments_to_configuration_sequence(config, schema, indent=0):
'''
If the given config sequence's items are maps, then mine the schema for the description of the
map's first item, and slap that atop the sequence. Indent the comment the given number of
characters.
Doing this for sequences of maps results in nice comments that look like:
```
things:
# First key description. Added by this function.
- key: foo
# Second key description. Added by add_comments_to_configuration_map().
other: bar
```
'''
if 'map' not in schema['seq'][0]:
return
for field_name in config[0].keys():
field_schema = schema['seq'][0]['map'].get(field_name, {})
description = field_schema.get('desc')
# No description to use? Skip it.
if not field_schema or not description:
return
config[0].yaml_set_start_comment(description, indent=indent)
# We only want the first key's description here, as the rest of the keys get commented by
# add_comments_to_configuration_map().
return
REQUIRED_SECTION_NAMES = {'location', 'retention'}
REQUIRED_KEYS = {'source_directories', 'repositories', 'keep_daily'}
COMMENTED_OUT_SENTINEL = 'COMMENT_OUT'
def add_comments_to_configuration_map(config, schema, indent=0, skip_first=False):
'''
Using descriptions from a schema as a source, add those descriptions as comments to the given
config mapping, before each field. Indent the comment the given number of characters.
'''
for index, field_name in enumerate(config.keys()):
if skip_first and index == 0:
continue
field_schema = schema['map'].get(field_name, {})
description = field_schema.get('desc', '').strip()
# If this is an optional key, add an indicator to the comment flagging it to be commented
# out from the sample configuration. This sentinel is consumed by downstream processing that
# does the actual commenting out.
if field_name not in REQUIRED_SECTION_NAMES and field_name not in REQUIRED_KEYS:
description = (
'\n'.join((description, COMMENTED_OUT_SENTINEL))
if description
else COMMENTED_OUT_SENTINEL
)
# No description to use? Skip it.
if not field_schema or not description: # pragma: no cover
continue
config.yaml_set_comment_before_after_key(key=field_name, before=description, indent=indent)
if index > 0:
_insert_newline_before_comment(config, field_name)
RUAMEL_YAML_COMMENTS_INDEX = 1
def remove_commented_out_sentinel(config, field_name):
'''
Given a configuration CommentedMap and a top-level field name in it, remove any "commented out"
sentinel found at the end of its YAML comments. This prevents the given field name from getting
commented out by downstream processing that consumes the sentinel.
'''
try:
last_comment_value = config.ca.items[field_name][RUAMEL_YAML_COMMENTS_INDEX][-1].value
except KeyError:
return
if last_comment_value == '# {}\n'.format(COMMENTED_OUT_SENTINEL):
config.ca.items[field_name][RUAMEL_YAML_COMMENTS_INDEX].pop()
def merge_source_configuration_into_destination(destination_config, source_config):
'''
Deep merge the given source configuration dict into the destination configuration CommentedMap,
favoring values from the source when there are collisions.
The purpose of this is to upgrade configuration files from old versions of borgmatic by adding
new
configuration keys and comments.
'''
if not source_config:
return destination_config
if not destination_config or not isinstance(source_config, collections.abc.Mapping):
return source_config
for field_name, source_value in source_config.items():
# Since this key/value is from the source configuration, leave it uncommented and remove any
# sentinel that would cause it to get commented out.
remove_commented_out_sentinel(destination_config, field_name)
# This is a mapping. Recurse for this key/value.
if isinstance(source_value, collections.abc.Mapping):
destination_config[field_name] = merge_source_configuration_into_destination(
destination_config[field_name], source_value
)
continue
# This is a sequence. Recurse for each item in it.
if isinstance(source_value, collections.abc.Sequence) and not isinstance(source_value, str):
destination_value = destination_config[field_name]
destination_config[field_name] = yaml.comments.CommentedSeq(
[
merge_source_configuration_into_destination(
destination_value[index] if index < len(destination_value) else None,
source_item,
)
for index, source_item in enumerate(source_value)
]
)
continue
# This is some sort of scalar. Simply set it into the destination.
destination_config[field_name] = source_config[field_name]
return destination_config
def generate_sample_configuration(source_filename, destination_filename, schema_filename):
'''
Given an optional source configuration filename, and a required destination configuration
filename, and the path to a schema filename in pykwalify YAML schema format, write out a
sample configuration file based on that schema. If a source filename is provided, merge the
parsed contents of that configuration into the generated configuration.
'''
schema = yaml.round_trip_load(open(schema_filename))
source_config = None
if source_filename:
source_config = load.load_configuration(source_filename)
destination_config = merge_source_configuration_into_destination(
_schema_to_sample_configuration(schema), source_config
)
write_configuration(
destination_filename,
_comment_out_optional_configuration(_render_configuration(destination_config)),
)
@@ -1,12 +1,5 @@
from collections import OrderedDict, namedtuple
try:
# Python 2
from ConfigParser import RawConfigParser
except ImportError:
# Python 3
from configparser import RawConfigParser
from configparser import RawConfigParser
Section_format = namedtuple('Section_format', ('name', 'options'))
Config_option = namedtuple('Config_option', ('name', 'value_type', 'required'))
@@ -20,6 +13,42 @@ def option(name, value_type=str, required=True):
return Config_option(name, value_type, required)
CONFIG_FORMAT = (
Section_format(
'location',
(
option('source_directories'),
option('one_file_system', value_type=bool, required=False),
option('remote_path', required=False),
option('repository'),
),
),
Section_format(
'storage',
(
option('encryption_passphrase', required=False),
option('compression', required=False),
option('umask', required=False),
),
),
Section_format(
'retention',
(
option('keep_within', required=False),
option('keep_hourly', int, required=False),
option('keep_daily', int, required=False),
option('keep_weekly', int, required=False),
option('keep_monthly', int, required=False),
option('keep_yearly', int, required=False),
option('prefix', required=False),
),
),
Section_format(
'consistency', (option('checks', required=False), option('check_last', required=False))
),
)
def validate_configuration_format(parser, config_format):
'''
Given an open RawConfigParser and an expected config file format, validate that the parsed
@@ -32,7 +61,8 @@ def validate_configuration_format(parser, config_format):
'''
section_names = set(parser.sections())
required_section_names = tuple(
section.name for section in config_format
section.name
for section in config_format
if any(option.required for option in section.options)
)
@@ -46,9 +76,7 @@ def validate_configuration_format(parser, config_format):
missing_section_names = set(required_section_names) - section_names
if missing_section_names:
raise ValueError(
'Missing config sections: {}'.format(', '.join(missing_section_names))
)
raise ValueError('Missing config sections: {}'.format(', '.join(missing_section_names)))
for section_format in config_format:
if section_format.name not in section_names:
@@ -57,26 +85,28 @@ def validate_configuration_format(parser, config_format):
option_names = parser.options(section_format.name)
expected_options = section_format.options
unexpected_option_names = set(option_names) - set(option.name for option in expected_options)
unexpected_option_names = set(option_names) - set(
option.name for option in expected_options
)
if unexpected_option_names:
raise ValueError(
'Unexpected options found in config section {}: {}'.format(
section_format.name,
', '.join(sorted(unexpected_option_names)),
section_format.name, ', '.join(sorted(unexpected_option_names))
)
)
missing_option_names = tuple(
option.name for option in expected_options if option.required
option.name
for option in expected_options
if option.required
if option.name not in option_names
)
if missing_option_names:
raise ValueError(
'Required options missing from config section {}: {}'.format(
section_format.name,
', '.join(missing_option_names)
section_format.name, ', '.join(missing_option_names)
)
)
@@ -89,10 +119,7 @@ def parse_section_options(parser, section_format):
Raise ValueError if any option values cannot be coerced to the expected Python data type.
'''
type_getter = {
str: parser.get,
int: parser.getint,
}
type_getter = {str: parser.get, int: parser.getint, bool: parser.getboolean}
return OrderedDict(
(option.name, type_getter[option.value_type](section_format.name, option.name))
@@ -116,11 +143,10 @@ def parse_configuration(config_filename, config_format):
# Describes a parsed configuration, where each attribute is the name of a configuration file
# section and each value is a dict of that section's parsed options.
Parsed_config = namedtuple('Parsed_config', (section_format.name for section_format in config_format))
Parsed_config = namedtuple(
'Parsed_config', (section_format.name for section_format in config_format)
)
return Parsed_config(
*(
parse_section_options(parser, section_format)
for section_format in config_format
)
*(parse_section_options(parser, section_format) for section_format in config_format)
)
+59
View File
@@ -0,0 +1,59 @@
import logging
import os
import ruamel.yaml
logger = logging.getLogger(__name__)
def load_configuration(filename):
'''
Load the given configuration file and return its contents as a data structure of nested dicts
and lists.
Raise ruamel.yaml.error.YAMLError if something goes wrong parsing the YAML, or RecursionError
if there are too many recursive includes.
'''
yaml = ruamel.yaml.YAML(typ='safe')
yaml.Constructor = Include_constructor
return yaml.load(open(filename))
def include_configuration(loader, filename_node):
'''
Load the given YAML filename (ignoring the given loader so we can use our own), and return its
contents as a data structure of nested dicts and lists.
'''
return load_configuration(os.path.expanduser(filename_node.value))
class Include_constructor(ruamel.yaml.SafeConstructor):
'''
A YAML "constructor" (a ruamel.yaml concept) that supports a custom "!include" tag for including
separate YAML configuration files. Example syntax: `retention: !include common.yaml`
'''
def __init__(self, preserve_quotes=None, loader=None):
super(Include_constructor, self).__init__(preserve_quotes, loader)
self.add_constructor('!include', include_configuration)
def flatten_mapping(self, node):
'''
Support the special case of shallow merging included configuration into an existing mapping
using the YAML '<<' merge key. Example syntax:
```
retention:
keep_daily: 1
<<: !include common.yaml
```
'''
representer = ruamel.yaml.representer.SafeRepresenter()
for index, (key_node, value_node) in enumerate(node.value):
if key_node.tag == u'tag:yaml.org,2002:merge' and value_node.tag == '!include':
included_value = representer.represent_data(self.construct_object(value_node))
node.value[index] = (key_node, included_value)
super(Include_constructor, self).flatten_mapping(node)
+532
View File
@@ -0,0 +1,532 @@
name: Borgmatic configuration file schema
version: 1
map:
location:
desc: |
Where to look for files to backup, and where to store those backups. See
https://borgbackup.readthedocs.io/en/stable/quickstart.html and
https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create for details.
required: true
map:
source_directories:
required: true
seq:
- type: str
desc: |
List of source directories to backup (required). Globs and tildes are expanded.
example:
- /home
- /etc
- /var/log/syslog*
repositories:
required: true
seq:
- type: str
desc: |
Paths to local or remote repositories (required). Tildes are expanded. Multiple
repositories are backed up to in sequence. See ssh_command for SSH options like
identity file or port.
example:
- user@backupserver:sourcehostname.borg
one_file_system:
type: bool
desc: Stay in same file system (do not cross mount points). Defaults to false.
example: true
numeric_owner:
type: bool
desc: Only store/extract numeric user and group identifiers. Defaults to false.
example: true
atime:
type: bool
desc: Store atime into archive. Defaults to true.
example: false
ctime:
type: bool
desc: Store ctime into archive. Defaults to true.
example: false
birthtime:
type: bool
desc: Store birthtime (creation date) into archive. Defaults to true.
example: false
read_special:
type: bool
desc: |
Use Borg's --read-special flag to allow backup of block and other special
devices. Use with caution, as it will lead to problems if used when
backing up special devices such as /dev/zero. Defaults to false.
example: false
bsd_flags:
type: bool
desc: Record bsdflags (e.g. NODUMP, IMMUTABLE) in archive. Defaults to true.
example: true
files_cache:
type: str
desc: |
Mode in which to operate the files cache. See
https://borgbackup.readthedocs.io/en/stable/usage/create.html#description for
details. Defaults to "ctime,size,inode".
example: ctime,size,inode
local_path:
type: str
desc: Alternate Borg local executable. Defaults to "borg".
example: borg1
remote_path:
type: str
desc: Alternate Borg remote executable. Defaults to "borg".
example: borg1
patterns:
seq:
- type: str
desc: |
Any paths matching these patterns are included/excluded from backups. Globs are
expanded. (Tildes are not.) Note that Borg considers this option experimental.
See the output of "borg help patterns" for more details. Quote any value if it
contains leading punctuation, so it parses correctly.
example:
- 'R /'
- '- /home/*/.cache'
- '+ /home/susan'
- '- /home/*'
patterns_from:
seq:
- type: str
desc: |
Read include/exclude patterns from one or more separate named files, one pattern
per line. Note that Borg considers this option experimental. See the output of
"borg help patterns" for more details.
example:
- /etc/borgmatic/patterns
exclude_patterns:
seq:
- type: str
desc: |
Any paths matching these patterns are excluded from backups. Globs and tildes
are expanded. See the output of "borg help patterns" for more details.
example:
- '*.pyc'
- ~/*/.cache
- /etc/ssl
exclude_from:
seq:
- type: str
desc: |
Read exclude patterns from one or more separate named files, one pattern per
line. See the output of "borg help patterns" for more details.
example:
- /etc/borgmatic/excludes
exclude_caches:
type: bool
desc: |
Exclude directories that contain a CACHEDIR.TAG file. See
http://www.brynosaurus.com/cachedir/spec.html for details. Defaults to false.
example: true
exclude_if_present:
type: str
desc: |
Exclude directories that contain a file with the given filename. Defaults to not
set.
example: .nobackup
keep_exclude_tags:
type: bool
desc: |
If true, the exclude_if_present filename is included in backups. Defaults to
false, meaning that the exclude_if_present filename is omitted from backups.
example: true
exclude_nodump:
type: bool
desc: |
Exclude files with the NODUMP flag. Defaults to false.
example: true
storage:
desc: |
Repository storage options. See
https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create and
https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for
details.
map:
encryption_passcommand:
type: str
desc: |
The standard output of this command is used to unlock the encryption key. Only
use on repositories that were initialized with passcommand/repokey encryption.
Note that if both encryption_passcommand and encryption_passphrase are set,
then encryption_passphrase takes precedence. Defaults to not set.
example: "secret-tool lookup borg-repository repo-name"
encryption_passphrase:
type: str
desc: |
Passphrase to unlock the encryption key with. Only use on repositories that were
initialized with passphrase/repokey encryption. Quote the value if it contains
punctuation, so it parses correctly. And backslash any quote or backslash
literals as well. Defaults to not set.
example: "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
checkpoint_interval:
type: int
desc: |
Number of seconds between each checkpoint during a long-running backup. See
https://borgbackup.readthedocs.io/en/stable/faq.html#if-a-backup-stops-mid-way-does-the-already-backed-up-data-stay-there
for details. Defaults to checkpoints every 1800 seconds (30 minutes).
example: 1800
chunker_params:
type: str
desc: |
Specify the parameters passed to then chunker (CHUNK_MIN_EXP, CHUNK_MAX_EXP,
HASH_MASK_BITS, HASH_WINDOW_SIZE). See https://borgbackup.readthedocs.io/en/stable/internals.html
for details. Defaults to "19,23,21,4095".
example: 19,23,21,4095
compression:
type: str
desc: |
Type of compression to use when creating archives. See
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-create for details.
Defaults to "lz4".
example: lz4
remote_rate_limit:
type: int
desc: Remote network upload rate limit in kiBytes/second. Defaults to unlimited.
example: 100
ssh_command:
type: str
desc: |
Command to use instead of "ssh". This can be used to specify ssh options.
Defaults to not set.
example: ssh -i /path/to/private/key
borg_base_directory:
type: str
desc: |
Base path used for various Borg directories. Defaults to $HOME, ~$USER, or ~.
See https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for details.
example: /path/to/base
borg_config_directory:
type: str
desc: |
Path for Borg configuration files. Defaults to $borg_base_directory/.config/borg
example: /path/to/base/config
borg_cache_directory:
type: str
desc: |
Path for Borg cache files. Defaults to $borg_base_directory/.cache/borg
example: /path/to/base/cache
borg_security_directory:
type: str
desc: |
Path for Borg security and encryption nonce files. Defaults to $borg_base_directory/.config/borg/security
example: /path/to/base/config/security
borg_keys_directory:
type: str
desc: |
Path for Borg encryption key files. Defaults to $borg_base_directory/.config/borg/keys
example: /path/to/base/config/keys
umask:
type: scalar
desc: Umask to be used for borg create. Defaults to 0077.
example: 0077
lock_wait:
type: int
desc: Maximum seconds to wait for acquiring a repository/cache lock. Defaults to 1.
example: 5
archive_name_format:
type: str
desc: |
Name of the archive. Borg placeholders can be used. See the output of
"borg help placeholders" for details. Defaults to
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this option, you must
also specify a prefix in the retention section to avoid accidental pruning of
archives with a different archive name format. And you should also specify a
prefix in the consistency section as well.
example: "{hostname}-documents-{now}"
relocated_repo_access_is_ok:
type: bool
desc: Bypass Borg error about a repository that has been moved. Defaults to false.
example: true
unknown_unencrypted_repo_access_is_ok:
type: bool
desc: |
Bypass Borg error about a previously unknown unencrypted repository. Defaults to
false.
example: true
retention:
desc: |
Retention policy for how many backups to keep in each category. See
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-prune for details.
At least one of the "keep" options is required for pruning to work. See
https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/
if you'd like to skip pruning entirely.
map:
keep_within:
type: str
desc: Keep all archives within this time interval.
example: 3H
keep_secondly:
type: int
desc: Number of secondly archives to keep.
example: 60
keep_minutely:
type: int
desc: Number of minutely archives to keep.
example: 60
keep_hourly:
type: int
desc: Number of hourly archives to keep.
example: 24
keep_daily:
type: int
desc: Number of daily archives to keep.
example: 7
keep_weekly:
type: int
desc: Number of weekly archives to keep.
example: 4
keep_monthly:
type: int
desc: Number of monthly archives to keep.
example: 6
keep_yearly:
type: int
desc: Number of yearly archives to keep.
example: 1
prefix:
type: str
desc: |
When pruning, only consider archive names starting with this prefix.
Borg placeholders can be used. See the output of "borg help placeholders" for
details. Defaults to "{hostname}-". Use an empty value to disable the default.
example: sourcehostname
consistency:
desc: |
Consistency checks to run after backups. See
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-check and
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-extract for details.
map:
checks:
seq:
- type: str
enum: ['repository', 'archives', 'data', 'extract', 'disabled']
unique: true
desc: |
List of one or more consistency checks to run: "repository", "archives", "data",
and/or "extract". Defaults to "repository" and "archives". Set to "disabled" to
disable all consistency checks. "repository" checks the consistency of the
repository, "archives" checks all of the archives, "data" verifies the integrity
of the data within the archives, and "extract" does an extraction dry-run of the
most recent archive. Note that "data" implies "archives".
example:
- repository
- archives
check_repositories:
seq:
- type: str
desc: |
Paths to a subset of the repositories in the location section on which to run
consistency checks. Handy in case some of your repositories are very large, and
so running consistency checks on them would take too long. Defaults to running
consistency checks on all repositories configured in the location section.
example:
- user@backupserver:sourcehostname.borg
check_last:
type: int
desc: Restrict the number of checked archives to the last n. Applies only to the
"archives" check. Defaults to checking all archives.
example: 3
prefix:
type: str
desc: |
When performing the "archives" check, only consider archive names starting with
this prefix. Borg placeholders can be used. See the output of
"borg help placeholders" for details. Defaults to "{hostname}-". Use an empty
value to disable the default.
example: sourcehostname
output:
desc: |
Options for customizing borgmatic's own output and logging.
map:
color:
type: bool
desc: |
Apply color to console output. Can be overridden with --no-color command-line
flag. Defaults to true.
example: false
hooks:
desc: |
Shell commands, scripts, or integrations to execute at various points during a borgmatic
run. IMPORTANT: All provided commands and scripts are executed with user permissions of
borgmatic. Do not forget to set secure permissions on this configuration file (chmod
0600) as well as on any script called from a hook (chmod 0700) to prevent potential
shell injection or privilege escalation.
map:
before_backup:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute before creating a
backup, run once per configuration file.
example:
- echo "Starting a backup."
after_backup:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute after creating a
backup, run once per configuration file.
example:
- echo "Created a backup."
on_error:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute when an exception
occurs during a backup or when running a before_backup or after_backup hook.
example:
- echo "Error while creating a backup or running a backup hook."
postgresql_databases:
seq:
- map:
name:
required: true
type: str
desc: |
Database name (required if using this hook). Or "all" to dump all
databases on the host.
example: users
hostname:
type: str
desc: |
Database hostname to connect to. Defaults to connecting via local
Unix socket.
example: database.example.org
port:
type: int
desc: Port to connect to. Defaults to 5432.
example: 5433
username:
type: str
desc: |
Username with which to connect to the database. Defaults to the
username of the current user. You probably want to specify the
"postgres" superuser here when the database name is "all".
example: dbuser
password:
type: str
desc: |
Password with which to connect to the database. Omitting a password
will only work if PostgreSQL is configured to trust the configured
username without a password, or you create a ~/.pgpass file.
example: trustsome1
format:
type: str
enum: ['plain', 'custom', 'directory', 'tar']
desc: |
Database dump output format. One of "plain", "custom", "directory",
or "tar". Defaults to "custom" (unlike raw pg_dump). See
https://www.postgresql.org/docs/current/app-pgdump.html for details.
Note that format is ignored when the database name is "all".
example: directory
options:
type: str
desc: |
Additional pg_dump/pg_dumpall options to pass directly to the dump
command, without performing any validation on them. See
https://www.postgresql.org/docs/current/app-pgdump.html for details.
example: --role=someone
desc: |
List of one or more PostgreSQL databases to dump before creating a backup,
run once per configuration file. The database dumps are added to your source
directories at runtime, backed up, and then removed afterwards. Requires
pg_dump/pg_dumpall/pg_restore commands. See
https://www.postgresql.org/docs/current/app-pgdump.html for details.
mysql_databases:
seq:
- map:
name:
required: true
type: str
desc: |
Database name (required if using this hook). Or "all" to dump all
databases on the host.
example: users
hostname:
type: str
desc: |
Database hostname to connect to. Defaults to connecting via local
Unix socket.
example: database.example.org
port:
type: int
desc: Port to connect to. Defaults to 3306.
example: 3307
username:
type: str
desc: |
Username with which to connect to the database. Defaults to the
username of the current user.
example: dbuser
password:
type: str
desc: |
Password with which to connect to the database. Omitting a password
will only work if MySQL is configured to trust the configured
username without a password.
example: trustsome1
options:
type: str
desc: |
Additional mysqldump options to pass directly to the dump command,
without performing any validation on them. See
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html or
https://mariadb.com/kb/en/library/mysqldump/ for details.
example: --skip-comments
desc: |
List of one or more MySQL/MariaDB databases to dump before creating a backup,
run once per configuration file. The database dumps are added to your source
directories at runtime, backed up, and then removed afterwards. Requires
mysqldump/mysql commands (from either MySQL or MariaDB). See
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html or
https://mariadb.com/kb/en/library/mysqldump/ for details.
healthchecks:
type: str
desc: |
Healthchecks ping URL or UUID to notify when a backup begins, ends, or errors.
Create an account at https://healthchecks.io if you'd like to use this service.
See
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#healthchecks-hook
for details.
example:
https://hc-ping.com/your-uuid-here
cronitor:
type: str
desc: |
Cronitor ping URL to notify when a backup begins, ends, or errors. Create an
account at https://cronitor.io if you'd like to use this service. See
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronitor-hook
for details.
example:
https://cronitor.link/d3x0c1
cronhub:
type: str
desc: |
Cronhub ping URL to notify when a backup begins, ends, or errors. Create an
account at https://cronhub.io if you'd like to use this service. See
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronhub-hook for
details.
example:
https://cronhub.io/start/1f5e3410-254c-11e8-b61d-55875966d031
before_everything:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute before running all
actions (if one of them is "create"), run once before all configuration files.
example:
- echo "Starting actions."
after_everything:
seq:
- type: str
desc: |
List of one or more shell commands or scripts to execute after running all
actions (if one of them is "create"), run once after all configuration files.
example:
- echo "Completed actions."
umask:
type: scalar
desc: Umask used when executing hooks. Defaults to the umask that borgmatic is run with.
example: 0077
+155
View File
@@ -0,0 +1,155 @@
import logging
import pkg_resources
import pykwalify.core
import pykwalify.errors
import ruamel.yaml
from borgmatic.config import load
def schema_filename():
'''
Path to the installed YAML configuration schema file, used to validate and parse the
configuration.
'''
return pkg_resources.resource_filename('borgmatic', 'config/schema.yaml')
class Validation_error(ValueError):
'''
A collection of error message strings generated when attempting to validate a particular
configurartion file.
'''
def __init__(self, config_filename, error_messages):
self.config_filename = config_filename
self.error_messages = error_messages
def __str__(self):
'''
Render a validation error as a user-facing string.
'''
return 'An error occurred while parsing a configuration file at {}:\n'.format(
self.config_filename
) + '\n'.join(self.error_messages)
def apply_logical_validation(config_filename, parsed_configuration):
'''
Given a parsed and schematically valid configuration as a data structure of nested dicts (see
below), run through any additional logical validation checks. If there are any such validation
problems, raise a Validation_error.
'''
archive_name_format = parsed_configuration.get('storage', {}).get('archive_name_format')
prefix = parsed_configuration.get('retention', {}).get('prefix')
if archive_name_format and not prefix:
raise Validation_error(
config_filename,
('If you provide an archive_name_format, you must also specify a retention prefix.',),
)
location_repositories = parsed_configuration.get('location', {}).get('repositories')
check_repositories = parsed_configuration.get('consistency', {}).get('check_repositories', [])
for repository in check_repositories:
if repository not in location_repositories:
raise Validation_error(
config_filename,
(
'Unknown repository in the consistency section\'s check_repositories: {}'.format(
repository
),
),
)
def remove_examples(schema):
'''
pykwalify gets angry if the example field is not a string. So rather than bend to its will,
remove all examples from the given schema before passing the schema to pykwalify.
'''
if 'map' in schema:
for item_name, item_schema in schema['map'].items():
item_schema.pop('example', None)
remove_examples(item_schema)
elif 'seq' in schema:
for item_schema in schema['seq']:
item_schema.pop('example', None)
remove_examples(item_schema)
return schema
def parse_configuration(config_filename, schema_filename):
'''
Given the path to a config filename in YAML format and the path to a schema filename in
pykwalify YAML schema format, return the parsed configuration as a data structure of nested
dicts and lists corresponding to the schema. Example return value:
{'location': {'source_directories': ['/home', '/etc'], 'repository': 'hostname.borg'},
'retention': {'keep_daily': 7}, 'consistency': {'checks': ['repository', 'archives']}}
Raise FileNotFoundError if the file does not exist, PermissionError if the user does not
have permissions to read the file, or Validation_error if the config does not match the schema.
'''
logging.getLogger('pykwalify').setLevel(logging.ERROR)
try:
config = load.load_configuration(config_filename)
schema = load.load_configuration(schema_filename)
except (ruamel.yaml.error.YAMLError, RecursionError) as error:
raise Validation_error(config_filename, (str(error),))
validator = pykwalify.core.Core(source_data=config, schema_data=remove_examples(schema))
parsed_result = validator.validate(raise_exception=False)
if validator.validation_errors:
raise Validation_error(config_filename, validator.validation_errors)
apply_logical_validation(config_filename, parsed_result)
return parsed_result
def guard_configuration_contains_repository(repository, configurations):
'''
Given a repository path and a dict mapping from config filename to corresponding parsed config
dict, ensure that the repository is declared exactly once in all of the configurations.
If no repository is given, then error if there are multiple configured repositories.
Raise ValueError if the repository is not found in a configuration, or is declared multiple
times.
'''
if not repository:
count = len(
tuple(
config_repository
for config in configurations.values()
for config_repository in config['location']['repositories']
)
)
if count > 1:
raise ValueError(
'Can\'t determine which repository to use. Use --repository option to disambiguate'.format(
repository
)
)
return
count = len(
tuple(
config_repository
for config in configurations.values()
for config_repository in config['location']['repositories']
if repository == config_repository
)
)
if count == 0:
raise ValueError('Repository {} not found in configuration files'.format(repository))
if count > 1:
raise ValueError('Repository {} found in multiple configuration files'.format(repository))
+128
View File
@@ -0,0 +1,128 @@
import logging
import os
import subprocess
logger = logging.getLogger(__name__)
ERROR_OUTPUT_MAX_LINE_COUNT = 25
BORG_ERROR_EXIT_CODE = 2
def exit_code_indicates_error(command, exit_code, error_on_warnings=False):
'''
Return True if the given exit code from running the command corresponds to an error.
'''
# If we're running something other than Borg, treat all non-zero exit codes as errors.
if 'borg' in command[0] and not error_on_warnings:
return bool(exit_code >= BORG_ERROR_EXIT_CODE)
return bool(exit_code != 0)
def log_output(command, process, output_buffer, output_log_level, error_on_warnings):
'''
Given a command already executed, its process opened by subprocess.Popen(), and the process'
relevant output buffer (stderr or stdout), log its output with the requested log level.
Additionally, raise a CalledProcessException if the process exits with an error (or a warning,
if error on warnings is True).
'''
last_lines = []
while process.poll() is None:
line = output_buffer.readline().rstrip().decode()
if not line:
continue
# Keep the last few lines of output in case the command errors, and we need the output for
# the exception below.
last_lines.append(line)
if len(last_lines) > ERROR_OUTPUT_MAX_LINE_COUNT:
last_lines.pop(0)
logger.log(output_log_level, line)
remaining_output = output_buffer.read().rstrip().decode()
if remaining_output: # pragma: no cover
logger.log(output_log_level, remaining_output)
exit_code = process.poll()
if exit_code_indicates_error(command, exit_code, error_on_warnings):
# If an error occurs, include its output in the raised exception so that we don't
# inadvertently hide error output.
if len(last_lines) == ERROR_OUTPUT_MAX_LINE_COUNT:
last_lines.insert(0, '...')
raise subprocess.CalledProcessError(exit_code, ' '.join(command), '\n'.join(last_lines))
def execute_command(
full_command,
output_log_level=logging.INFO,
output_file=None,
input_file=None,
shell=False,
extra_environment=None,
working_directory=None,
error_on_warnings=False,
):
'''
Execute the given command (a sequence of command/argument strings) and log its output at the
given log level. If output log level is None, instead capture and return the output. If an
open output file object is given, then write stdout to the file and only log stderr (but only
if an output log level is set). If an open input file object is given, then read stdin from the
file. If shell is True, execute the command within a shell. If an extra environment dict is
given, then use it to augment the current environment, and pass the result into the command. If
a working directory is given, use that as the present working directory when running the
command.
Raise subprocesses.CalledProcessError if an error occurs while running the command.
'''
logger.debug(
' '.join(full_command)
+ (' < {}'.format(input_file.name) if input_file else '')
+ (' > {}'.format(output_file.name) if output_file else '')
)
environment = {**os.environ, **extra_environment} if extra_environment else None
if output_log_level is None:
output = subprocess.check_output(
full_command, shell=shell, env=environment, cwd=working_directory
)
return output.decode() if output is not None else None
else:
process = subprocess.Popen(
full_command,
stdin=input_file,
stdout=output_file or subprocess.PIPE,
stderr=subprocess.PIPE if output_file else subprocess.STDOUT,
shell=shell,
env=environment,
cwd=working_directory,
)
log_output(
full_command,
process,
process.stderr if output_file else process.stdout,
output_log_level,
error_on_warnings,
)
def execute_command_without_capture(full_command, working_directory=None, error_on_warnings=False):
'''
Execute the given command (a sequence of command/argument strings), but don't capture or log its
output in any way. This is necessary for commands that monkey with the terminal (e.g. progress
display) or provide interactive prompts.
If a working directory is given, use that as the present working directory when running the
command.
'''
logger.debug(' '.join(full_command))
try:
subprocess.check_call(full_command, cwd=working_directory)
except subprocess.CalledProcessError as error:
if exit_code_indicates_error(full_command, error.returncode, error_on_warnings):
raise
+71
View File
@@ -0,0 +1,71 @@
import logging
import os
from borgmatic import execute
logger = logging.getLogger(__name__)
def interpolate_context(command, context):
'''
Given a single hook command and a dict of context names/values, interpolate the values by
"{name}" into the command and return the result.
'''
for name, value in context.items():
command = command.replace('{%s}' % name, str(value))
return command
def execute_hook(commands, umask, config_filename, description, dry_run, **context):
'''
Given a list of hook commands to execute, a umask to execute with (or None), a config filename,
a hook description, and whether this is a dry run, run the given commands. Or, don't run them
if this is a dry run.
The context contains optional values interpolated by name into the hook commands. Currently,
this only applies to the on_error hook.
Raise ValueError if the umask cannot be parsed.
Raise subprocesses.CalledProcessError if an error occurs in a hook.
'''
if not commands:
logger.debug('{}: No commands to run for {} hook'.format(config_filename, description))
return
dry_run_label = ' (dry run; not actually running hooks)' if dry_run else ''
context['configuration_filename'] = config_filename
commands = [interpolate_context(command, context) for command in commands]
if len(commands) == 1:
logger.info(
'{}: Running command for {} hook{}'.format(config_filename, description, dry_run_label)
)
else:
logger.info(
'{}: Running {} commands for {} hook{}'.format(
config_filename, len(commands), description, dry_run_label
)
)
if umask:
parsed_umask = int(str(umask), 8)
logger.debug('{}: Set hook umask to {}'.format(config_filename, oct(parsed_umask)))
original_umask = os.umask(parsed_umask)
else:
original_umask = None
try:
for command in commands:
if not dry_run:
execute.execute_command(
[command],
output_log_level=logging.ERROR
if description == 'on-error'
else logging.WARNING,
shell=True,
)
finally:
if original_umask:
os.umask(original_umask)
+26
View File
@@ -0,0 +1,26 @@
import logging
import requests
logger = logging.getLogger(__name__)
def ping_cronhub(ping_url, config_filename, dry_run, state):
'''
Ping the given Cronhub URL, substituting in the state string. Use the given configuration
filename in any log entries. If this is a dry run, then don't actually ping anything.
'''
if not ping_url:
logger.debug('{}: No Cronhub hook set'.format(config_filename))
return
dry_run_label = ' (dry run; not actually pinging)' if dry_run else ''
formatted_state = '/{}/'.format(state)
ping_url = ping_url.replace('/start/', formatted_state).replace('/ping/', formatted_state)
logger.info('{}: Pinging Cronhub {}{}'.format(config_filename, state, dry_run_label))
logger.debug('{}: Using Cronhub ping URL {}'.format(config_filename, ping_url))
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
requests.get(ping_url)
+25
View File
@@ -0,0 +1,25 @@
import logging
import requests
logger = logging.getLogger(__name__)
def ping_cronitor(ping_url, config_filename, dry_run, append):
'''
Ping the given Cronitor URL, appending the append string. Use the given configuration filename
in any log entries. If this is a dry run, then don't actually ping anything.
'''
if not ping_url:
logger.debug('{}: No Cronitor hook set'.format(config_filename))
return
dry_run_label = ' (dry run; not actually pinging)' if dry_run else ''
ping_url = '{}/{}'.format(ping_url, append)
logger.info('{}: Pinging Cronitor {}{}'.format(config_filename, append, dry_run_label))
logger.debug('{}: Using Cronitor ping URL {}'.format(config_filename, ping_url))
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
requests.get(ping_url)
+55
View File
@@ -0,0 +1,55 @@
import logging
from borgmatic.hooks import mysql, postgresql
logger = logging.getLogger(__name__)
HOOK_NAME_TO_MODULE = {'postgresql_databases': postgresql, 'mysql_databases': mysql}
def call_hook(function_name, hooks, log_prefix, hook_name, *args, **kwargs):
'''
Given the hooks configuration dict and a prefix to use in log entries, call the requested
function of the Python module corresponding to the given hook name. Supply that call with the
configuration for this hook, the log prefix, and any given args and kwargs. Return any return
value.
If the hook name is not present in the hooks configuration, then bail without calling anything.
Raise ValueError if the hook name is unknown.
Raise AttributeError if the function name is not found in the module.
Raise anything else that the called function raises.
'''
config = hooks.get(hook_name)
if not config:
logger.debug('{}: No {} hook configured.'.format(log_prefix, hook_name))
return
try:
module = HOOK_NAME_TO_MODULE[hook_name]
except KeyError:
raise ValueError('Unknown hook name: {}'.format(hook_name))
logger.debug('{}: Calling {} hook function {}'.format(log_prefix, hook_name, function_name))
return getattr(module, function_name)(config, log_prefix, *args, **kwargs)
def call_hooks(function_name, hooks, log_prefix, hook_names, *args, **kwargs):
'''
Given the hooks configuration dict and a prefix to use in log entries, call the requested
function of the Python module corresponding to each given hook name. Supply each call with the
configuration for that hook, the log prefix, and any given args and kwargs. Collect any return
values into a dict from hook name to return value.
If the hook name is not present in the hooks configuration, then don't call the function for it,
and omit it from the return values.
Raise ValueError if the hook name is unknown.
Raise AttributeError if the function name is not found in the module.
Raise anything else that a called function raises. An error stops calls to subsequent functions.
'''
return {
hook_name: call_hook(function_name, hooks, log_prefix, hook_name, *args, **kwargs)
for hook_name in hook_names
if hook_name in hooks
}
+138
View File
@@ -0,0 +1,138 @@
import glob
import logging
import os
logger = logging.getLogger(__name__)
DATABASE_HOOK_NAMES = ('postgresql_databases', 'mysql_databases')
def make_database_dump_filename(dump_path, name, hostname=None):
'''
Based on the given dump directory path, database name, and hostname, return a filename to use
for the database dump. The hostname defaults to localhost.
Raise ValueError if the database name is invalid.
'''
if os.path.sep in name:
raise ValueError('Invalid database name {}'.format(name))
return os.path.join(os.path.expanduser(dump_path), hostname or 'localhost', name)
def remove_database_dumps(dump_path, databases, database_type_name, log_prefix, dry_run):
'''
Remove the database dumps for the given databases in the dump directory path. The databases are
supplied as a sequence of dicts, one dict describing each database as per the configuration
schema. Use the name of the database type and the log prefix in any log entries. If this is a
dry run, then don't actually remove anything.
'''
if not databases:
logger.debug('{}: No {} databases configured'.format(log_prefix, database_type_name))
return
dry_run_label = ' (dry run; not actually removing anything)' if dry_run else ''
logger.info(
'{}: Removing {} database dumps{}'.format(log_prefix, database_type_name, dry_run_label)
)
for database in databases:
dump_filename = make_database_dump_filename(
dump_path, database['name'], database.get('hostname')
)
logger.debug(
'{}: Removing {} database dump {} from {}{}'.format(
log_prefix, database_type_name, database['name'], dump_filename, dry_run_label
)
)
if dry_run:
continue
os.remove(dump_filename)
dump_path = os.path.dirname(dump_filename)
if len(os.listdir(dump_path)) == 0:
os.rmdir(dump_path)
def convert_glob_patterns_to_borg_patterns(patterns):
'''
Convert a sequence of shell glob patterns like "/etc/*" to the corresponding Borg archive
patterns like "sh:etc/*".
'''
return ['sh:{}'.format(pattern.lstrip(os.path.sep)) for pattern in patterns]
def get_database_names_from_dumps(patterns):
'''
Given a sequence of database dump patterns, find the corresponding database dumps on disk and
return the database names from their filenames.
'''
return [os.path.basename(dump_path) for pattern in patterns for dump_path in glob.glob(pattern)]
def get_database_configurations(databases, names):
'''
Given the full database configuration dicts as per the configuration schema, and a sequence of
database names, filter down and yield the configuration for just the named databases.
Additionally, if a database configuration is named "all", project out that configuration for
each named database.
'''
named_databases = {database['name']: database for database in databases}
for name in names:
database = named_databases.get(name)
if database:
yield database
continue
if 'all' in named_databases:
yield {**named_databases['all'], **{'name': name}}
continue
def get_per_hook_database_configurations(hooks, names, dump_patterns):
'''
Given the hooks configuration dict as per the configuration schema, a sequence of database
names to restore, and a dict from database hook name to glob patterns for matching dumps,
filter down the configuration for just the named databases.
If there are no named databases given, then find the corresponding database dumps on disk and
use the database names from their filenames. Additionally, if a database configuration is named
"all", project out that configuration for each named database.
Return the results as a dict from database hook name to a sequence of database configuration
dicts for that database type.
Raise ValueError if one of the database names cannot be matched to a database in borgmatic's
database configuration.
'''
# TODO: Need to filter names by database type? Maybe take a database --type argument to disambiguate.
hook_databases = {
hook_name: list(
get_database_configurations(
hooks.get(hook_name),
names or get_database_names_from_dumps(dump_patterns[hook_name]),
)
)
for hook_name in DATABASE_HOOK_NAMES
if hook_name in hooks
}
if not names or 'all' in names:
return hook_databases
found_names = {
database['name'] for databases in hook_databases.values() for database in databases
}
missing_names = sorted(set(names) - found_names)
if missing_names:
raise ValueError(
'Cannot restore database(s) {} missing from borgmatic\'s configuration'.format(
', '.join(missing_names)
)
)
return hook_databases
+37
View File
@@ -0,0 +1,37 @@
import logging
import requests
logger = logging.getLogger(__name__)
def ping_healthchecks(ping_url_or_uuid, config_filename, dry_run, append=None):
'''
Ping the given Healthchecks URL or UUID, appending the append string if any. Use the given
configuration filename in any log entries. If this is a dry run, then don't actually ping
anything.
'''
if not ping_url_or_uuid:
logger.debug('{}: No Healthchecks hook set'.format(config_filename))
return
ping_url = (
ping_url_or_uuid
if ping_url_or_uuid.startswith('http')
else 'https://hc-ping.com/{}'.format(ping_url_or_uuid)
)
dry_run_label = ' (dry run; not actually pinging)' if dry_run else ''
if append:
ping_url = '{}/{}'.format(ping_url, append)
logger.info(
'{}: Pinging Healthchecks{}{}'.format(
config_filename, ' ' + append if append else '', dry_run_label
)
)
logger.debug('{}: Using Healthchecks ping URL {}'.format(config_filename, ping_url))
if not dry_run:
logging.getLogger('urllib3').setLevel(logging.ERROR)
requests.get(ping_url)
+94
View File
@@ -0,0 +1,94 @@
import logging
import os
from borgmatic.execute import execute_command
from borgmatic.hooks import dump
DUMP_PATH = '~/.borgmatic/mysql_databases'
logger = logging.getLogger(__name__)
def dump_databases(databases, log_prefix, dry_run):
'''
Dump the given MySQL/MariaDB databases to disk. The databases are supplied as a sequence of
dicts, one dict describing each database as per the configuration schema. Use the given log
prefix in any log entries. If this is a dry run, then don't actually dump anything.
'''
dry_run_label = ' (dry run; not actually dumping anything)' if dry_run else ''
logger.info('{}: Dumping MySQL databases{}'.format(log_prefix, dry_run_label))
for database in databases:
name = database['name']
dump_filename = dump.make_database_dump_filename(DUMP_PATH, name, database.get('hostname'))
command = (
('mysqldump', '--add-drop-database')
+ (('--host', database['hostname']) if 'hostname' in database else ())
+ (('--port', str(database['port'])) if 'port' in database else ())
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
+ (('--user', database['username']) if 'username' in database else ())
+ (tuple(database['options'].split(' ')) if 'options' in database else ())
+ (('--all-databases',) if name == 'all' else ('--databases', name))
)
extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
logger.debug(
'{}: Dumping MySQL database {} to {}{}'.format(
log_prefix, name, dump_filename, dry_run_label
)
)
if not dry_run:
os.makedirs(os.path.dirname(dump_filename), mode=0o700, exist_ok=True)
execute_command(
command, output_file=open(dump_filename, 'w'), extra_environment=extra_environment
)
def remove_database_dumps(databases, log_prefix, dry_run): # pragma: no cover
'''
Remove the database dumps for the given databases. The databases are supplied as a sequence of
dicts, one dict describing each database as per the configuration schema. Use the log prefix in
any log entries. If this is a dry run, then don't actually remove anything.
'''
dump.remove_database_dumps(DUMP_PATH, databases, 'MySQL', log_prefix, dry_run)
def make_database_dump_patterns(databases, log_prefix, names):
'''
Given a sequence of configurations dicts, a prefix to log with, and a sequence of database
names to match, return the corresponding glob patterns to match the database dumps in an
archive. An empty sequence of names indicates that the patterns should match all dumps.
'''
return [
dump.make_database_dump_filename(DUMP_PATH, name, hostname='*') for name in (names or ['*'])
]
def restore_database_dumps(databases, log_prefix, dry_run):
'''
Restore the given MySQL/MariaDB databases from disk. The databases are supplied as a sequence of
dicts, one dict describing each database as per the configuration schema. Use the given log
prefix in any log entries. If this is a dry run, then don't actually restore anything.
'''
dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
for database in databases:
dump_filename = dump.make_database_dump_filename(
DUMP_PATH, database['name'], database.get('hostname')
)
restore_command = (
('mysql', '--batch')
+ (('--host', database['hostname']) if 'hostname' in database else ())
+ (('--port', str(database['port'])) if 'port' in database else ())
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
+ (('--user', database['username']) if 'username' in database else ())
)
extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
logger.debug(
'{}: Restoring MySQL database {}{}'.format(log_prefix, database['name'], dry_run_label)
)
if not dry_run:
execute_command(
restore_command, input_file=open(dump_filename), extra_environment=extra_environment
)
+104
View File
@@ -0,0 +1,104 @@
import logging
import os
from borgmatic.execute import execute_command
from borgmatic.hooks import dump
DUMP_PATH = '~/.borgmatic/postgresql_databases'
logger = logging.getLogger(__name__)
def dump_databases(databases, log_prefix, dry_run):
'''
Dump the given PostgreSQL databases to disk. The databases are supplied as a sequence of dicts,
one dict describing each database as per the configuration schema. Use the given log prefix in
any log entries. If this is a dry run, then don't actually dump anything.
'''
dry_run_label = ' (dry run; not actually dumping anything)' if dry_run else ''
logger.info('{}: Dumping PostgreSQL databases{}'.format(log_prefix, dry_run_label))
for database in databases:
name = database['name']
dump_filename = dump.make_database_dump_filename(DUMP_PATH, name, database.get('hostname'))
all_databases = bool(name == 'all')
command = (
('pg_dumpall' if all_databases else 'pg_dump', '--no-password', '--clean')
+ ('--file', dump_filename)
+ (('--host', database['hostname']) if 'hostname' in database else ())
+ (('--port', str(database['port'])) if 'port' in database else ())
+ (('--username', database['username']) if 'username' in database else ())
+ (() if all_databases else ('--format', database.get('format', 'custom')))
+ (tuple(database['options'].split(' ')) if 'options' in database else ())
+ (() if all_databases else (name,))
)
extra_environment = {'PGPASSWORD': database['password']} if 'password' in database else None
logger.debug(
'{}: Dumping PostgreSQL database {} to {}{}'.format(
log_prefix, name, dump_filename, dry_run_label
)
)
if not dry_run:
os.makedirs(os.path.dirname(dump_filename), mode=0o700, exist_ok=True)
execute_command(command, extra_environment=extra_environment)
def remove_database_dumps(databases, log_prefix, dry_run): # pragma: no cover
'''
Remove the database dumps for the given databases. The databases are supplied as a sequence of
dicts, one dict describing each database as per the configuration schema. Use the log prefix in
any log entries. If this is a dry run, then don't actually remove anything.
'''
dump.remove_database_dumps(DUMP_PATH, databases, 'PostgreSQL', log_prefix, dry_run)
def make_database_dump_patterns(databases, log_prefix, names):
'''
Given a sequence of configurations dicts, a prefix to log with, and a sequence of database
names to match, return the corresponding glob patterns to match the database dumps in an
archive. An empty sequence of names indicates that the patterns should match all dumps.
'''
return [
dump.make_database_dump_filename(DUMP_PATH, name, hostname='*') for name in (names or ['*'])
]
def restore_database_dumps(databases, log_prefix, dry_run):
'''
Restore the given PostgreSQL databases from disk. The databases are supplied as a sequence of
dicts, one dict describing each database as per the configuration schema. Use the given log
prefix in any log entries. If this is a dry run, then don't actually restore anything.
'''
dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
for database in databases:
dump_filename = dump.make_database_dump_filename(
DUMP_PATH, database['name'], database.get('hostname')
)
restore_command = (
('pg_restore', '--no-password', '--clean', '--if-exists', '--exit-on-error')
+ (('--host', database['hostname']) if 'hostname' in database else ())
+ (('--port', str(database['port'])) if 'port' in database else ())
+ (('--username', database['username']) if 'username' in database else ())
+ ('--dbname', database['name'])
+ (dump_filename,)
)
extra_environment = {'PGPASSWORD': database['password']} if 'password' in database else None
analyze_command = (
('psql', '--no-password', '--quiet')
+ (('--host', database['hostname']) if 'hostname' in database else ())
+ (('--port', str(database['port'])) if 'port' in database else ())
+ (('--username', database['username']) if 'username' in database else ())
+ ('--dbname', database['name'])
+ ('--command', 'ANALYZE')
)
logger.debug(
'{}: Restoring PostgreSQL database {}{}'.format(
log_prefix, database['name'], dry_run_label
)
)
if not dry_run:
execute_command(restore_command, extra_environment=extra_environment)
execute_command(analyze_command, extra_environment=extra_environment)
+116
View File
@@ -0,0 +1,116 @@
import logging
import os
import sys
import colorama
def to_bool(arg):
'''
Return a boolean value based on `arg`.
'''
if arg is None or isinstance(arg, bool):
return arg
if isinstance(arg, str):
arg = arg.lower()
if arg in ('yes', 'on', '1', 'true', 1):
return True
return False
def interactive_console():
'''
Return whether the current console is "interactive". Meaning: Capable of
user input and not just something like a cron job.
'''
return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb'
def should_do_markup(no_color, configs):
'''
Given the value of the command-line no-color argument, and a dict of configuration filename to
corresponding parsed configuration, determine if we should enable colorama marking up.
'''
if no_color:
return False
if any(config.get('output', {}).get('color') is False for config in configs.values()):
return False
py_colors = os.environ.get('PY_COLORS', None)
if py_colors is not None:
return to_bool(py_colors)
return interactive_console()
LOG_LEVEL_TO_COLOR = {
logging.CRITICAL: colorama.Fore.RED,
logging.ERROR: colorama.Fore.RED,
logging.WARN: colorama.Fore.YELLOW,
logging.INFO: colorama.Fore.GREEN,
logging.DEBUG: colorama.Fore.CYAN,
}
class Console_color_formatter(logging.Formatter):
def format(self, record):
color = LOG_LEVEL_TO_COLOR.get(record.levelno)
return color_text(color, record.msg)
def color_text(color, message):
'''
Give colored text.
'''
if not color:
return message
return '{}{}{}'.format(color, message, colorama.Style.RESET_ALL)
def configure_logging(
console_log_level, syslog_log_level=None, log_file_log_level=None, log_file=None
):
'''
Configure logging to go to both the console and (syslog or log file). Use the given log levels,
respectively.
Raise FileNotFoundError or PermissionError if the log file could not be opened for writing.
'''
if syslog_log_level is None:
syslog_log_level = console_log_level
if log_file_log_level is None:
log_file_log_level = console_log_level
console_handler = logging.StreamHandler()
console_handler.setFormatter(Console_color_formatter())
console_handler.setLevel(console_log_level)
syslog_path = None
if log_file is None:
if os.path.exists('/dev/log'):
syslog_path = '/dev/log'
elif os.path.exists('/var/run/syslog'):
syslog_path = '/var/run/syslog'
if syslog_path and not interactive_console():
syslog_handler = logging.handlers.SysLogHandler(address=syslog_path)
syslog_handler.setFormatter(logging.Formatter('borgmatic: %(levelname)s %(message)s'))
syslog_handler.setLevel(syslog_log_level)
handlers = (console_handler, syslog_handler)
elif log_file:
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s'))
file_handler.setLevel(log_file_log_level)
handlers = (console_handler, file_handler)
else:
handlers = (console_handler,)
logging.basicConfig(
level=min(console_log_level, syslog_log_level, log_file_log_level), handlers=handlers
)
+18
View File
@@ -0,0 +1,18 @@
import os
import signal
def _handle_signal(signal_number, frame): # pragma: no cover
'''
Send the signal to all processes in borgmatic's process group, which includes child process.
'''
os.killpg(os.getpgrp(), signal_number)
def configure_signals(): # pragma: no cover
'''
Configure borgmatic's signal handlers to pass relevant signals through to any child processes
like Borg. Note that SIGINT gets passed through even without these changes.
'''
for signal_number in (signal.SIGHUP, signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2):
signal.signal(signal_number, _handle_signal)
+18
View File
@@ -0,0 +1,18 @@
import logging
VERBOSITY_ERROR = -1
VERBOSITY_WARNING = 0
VERBOSITY_SOME = 1
VERBOSITY_LOTS = 2
def verbosity_to_log_level(verbosity):
'''
Given a borgmatic verbosity value, return the corresponding Python log level.
'''
return {
VERBOSITY_ERROR: logging.ERROR,
VERBOSITY_WARNING: logging.WARNING,
VERBOSITY_SOME: logging.INFO,
VERBOSITY_LOTS: logging.DEBUG,
}.get(verbosity, logging.WARNING)
+31
View File
@@ -0,0 +1,31 @@
FROM python:3.7.4-alpine3.10 as borgmatic
COPY . /app
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
RUN borgmatic --help > /command-line.txt \
&& for action in init prune create check extract restore list info; do \
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
&& borgmatic "$action" --help >> /command-line.txt; done
FROM node:12.10.0-alpine as html
ARG ENVIRONMENT=production
WORKDIR /source
RUN npm install @11ty/eleventy \
@11ty/eleventy-plugin-syntaxhighlight \
@11ty/eleventy-plugin-inclusive-language \
markdown-it \
markdown-it-anchor \
markdown-it-replace-link
COPY --from=borgmatic /etc/borgmatic/config.yaml /source/docs/_includes/borgmatic/config.yaml
COPY --from=borgmatic /command-line.txt /source/docs/_includes/borgmatic/command-line.txt
COPY . /source
RUN NODE_ENV=${ENVIRONMENT} npx eleventy --input=/source/docs --output=/output/docs \
&& mv /output/docs/index.html /output/index.html
FROM nginx:1.16.1-alpine
COPY --from=html /output /usr/share/nginx/html
COPY --from=borgmatic /etc/borgmatic/config.yaml /usr/share/nginx/html/docs/reference/config.yaml
+1
View File
@@ -0,0 +1 @@
../README.md
+1
View File
@@ -0,0 +1 @@
"layouts/main.njk"
+3
View File
@@ -0,0 +1,3 @@
.asciicast > iframe {
width: 100% !important;
}
@@ -0,0 +1,12 @@
/* External links */
a[href^="http://"]:not(.minilink):not(.elv-externalexempt),
a[href^="https://"]:not(.minilink):not(.elv-externalexempt),
a[href^="//"]:not(.minilink):not(.elv-externalexempt) {
text-decoration-color: inherit;
}
/* External link hovers */
a[href^="http://"]:not(.minilink):not(.elv-externalexempt):hover,
a[href^="https://"]:not(.minilink):not(.elv-externalexempt):hover,
a[href^="//"]:not(.minilink):not(.elv-externalexempt):hover {
text-decoration-color: #00bcd4;
}
+34
View File
@@ -0,0 +1,34 @@
/* Warning */
.elv-info {
line-height: 1.5;
padding: 0.8125em 1em 0.75em; /* 13px 16px 12px /16 */
margin-left: -1rem;
margin-right: -1rem;
margin-bottom: 2em;
background-color: #dff7ff;
}
.elv-info:before {
content: "️ ";
}
.elv-info-warn {
background-color: #ffa;
}
.elv-info-warn:before {
content: "⚠️ ";
}
.elv-info:first-child {
margin-top: 0;
}
body > .elv-info {
margin-left: 0;
margin-right: 0;
padding: .5rem 1rem;
}
@media (min-width: 37.5em) and (min-height: 25em) { /* 600px / 400px */
body > .elv-info-sticky {
position: sticky;
top: 0;
z-index: 2;
box-shadow: 0 3px 0 0 rgba(0,0,0,.08);
}
}
+126
View File
@@ -0,0 +1,126 @@
/* Buzzwords */
@keyframes rainbow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.buzzword-list,
.inlinelist {
padding: 0;
}
.inlinelist:first-child:last-child {
margin: 0;
}
.buzzword,
.buzzword-list li,
.inlinelist .inlinelist-item {
display: inline;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
font-family: Georgia, serif;
font-size: 116%;
white-space: normal;
line-height: 1.85;
padding: .2em .5em;
margin: 4px 4px 4px 0;
transition: .15s linear outline;
}
.inlinelist .inlinelist-item.active {
background-color: #222;
color: #fff;
font-weight: inherit;
}
.inlinelist .inlinelist-item.active :link,
.inlinelist .inlinelist-item.active :visited {
color: #fff;
}
.inlinelist .inlinelist-item code {
background-color: transparent;
}
a.buzzword {
text-decoration: underline;
}
.buzzword-list a,
.inlinelist a {
text-decoration: none;
}
.inlinelist .inlinelist-item {
font-size: 100%;
line-height: 2;
}
@supports not(-webkit-box-decoration-break: clone) {
.buzzword,
.buzzword-list li,
.inlinelist .inlinelist-item {
display: inline-block;
}
}
.buzzword-list li,
.buzzword {
background-color: #f7f7f7;
}
.inlinelist .inlinelist-item {
background-color: #e9e9e9;
}
.inlinelist .inlinelist-item:hover,
.inlinelist .inlinelist-item:focus,
.buzzword-list li:hover,
.buzzword-list li:focus,
.buzzword:hover,
.buzzword:focus {
position: relative;
background-image: linear-gradient(238deg, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff0080);
background-size: 1200% 1200%;
color: #fff;
text-shadow: 0 0 2px rgba(0,0,0,.9);
animation: rainbow 1.6s infinite;
}
.inlinelist .inlinelist-item:hover a,
.inlinelist .inlinelist-item:focus a,
.buzzword-list li:hover a,
.buzzword-list li:focus a,
a.buzzword:hover,
a.buzzword:focus {
color: #fff;
text-decoration: none;
}
/*
I wish there were a PE friendly way to do this but media queries dont work work with @supports
@media (prefers-reduced-motion: no-preference) {
.buzzword:hover,
.buzzword:focus {
animation: rainbow 1s infinite;
}
}*/
.buzzword-list li:hover:after,
.buzzword-list li:focus:after,
.buzzword:hover:after,
.buzzword:focus:after {
font-family: system-ui, sans-serif;
content: "Buzzword alert!!!";
position: absolute;
left: 0;
top: 0;
max-width: 8em;
color: #f00;
font-weight: 700;
text-transform: uppercase;
transform: rotate(-10deg) translate(-25%, -125%);
text-shadow: 1px 1px 5px rgba(0,0,0,.6);
line-height: 1.2;
pointer-events: none;
}
main h2 .buzzword,
main h3 .buzzword,
main p .buzzword {
padding: 0px 7px;
font-size: 1em; /* 18px /18 */
margin: 0;
line-height: 1.444444444444; /* 26px /18 */
font-family: inherit;
}
main h2 a.buzzword,
main h3 a.buzzword,
main p a.buzzword {
text-decoration: underline;
}
+40
View File
@@ -0,0 +1,40 @@
/* Mini link */
.minilink {
display: inline-block;
padding: .125em .375em;
text-transform: uppercase;
font-size: 0.875rem; /* 14px /16 */
text-decoration: none;
background-color: #ddd;
border-radius: 0.1875em; /* 3px /16 */
font-weight: 500;
margin: 0 0.4285714285714em 0.07142857142857em 0; /* 0 6px 1px 0 /14 */
line-height: 1.285714285714; /* 18px /14 */
font-family: system-ui, sans-serif;
}
.minilink[href] {
box-shadow: 0 1px 1px 0 rgba(0,0,0,.5);
}
.minilink[href]:hover,
.minilink[href]:focus {
background-color: #bbb;
}
pre + .minilink {
color: #fff;
border-radius: 0 0 0.2857142857143em 0.2857142857143em; /* 4px /14 */
float: right;
background-color: #444;
color: #fff;
}
pre[class*=language-] + .minilink {
position: relative;
top: -0.7142857142857em; /* -10px /14 */
}
p.minilink {
float: right;
margin-left: 2em;
margin-bottom: 2em;
}
.minilink + pre[class*=language-] {
clear: both;
}
@@ -0,0 +1,18 @@
#suggestion-form textarea {
font-family: sans-serif;
width: 100%;
}
#suggestion-form label {
font-weight: bold;
}
#suggestion-form input[type=email] {
font-size: 16px;
width: 100%;
}
#suggestion-form .form-error {
color: red;
}
@@ -0,0 +1,33 @@
<h2>Improve this documentation</h2>
<p>Have an idea on how to make this documentation even better? Send your
feedback below! (But if you need help installing or using borgmatic, please
use our <a href="https://torsion.org/borgmatic/#issues">issue tracker</a>
instead.)</p>
<form id="suggestion-form">
<div><label for="suggestion">Suggestion</label></div>
<textarea id="suggestion" rows="8" cols="60" name="suggestion"></textarea>
<div data-sk-error="suggestion" class="form-error"></div>
<input id="_page" type="hidden" name="_page">
<input id="_subject" type="hidden" name="_subject" value="borgmatic documentation suggestion">
<br />
<label for="email">Email address</label>
<div><input id="email" type="email" name="email" placeholder="Only required if you want a response!"></div>
<div data-sk-error="email" class="form-error"></div>
<br />
<div><button type="submit">Send</button></div>
<br />
</form>
<script>
document.getElementById('_page').value = window.location.href;
window.sk=window.sk||function(){(sk.q=sk.q||[]).push(arguments)};
sk('form', 'init', {
id: '1d536680ab96',
element: '#suggestion-form'
});
</script>
<script defer src="https://js.statickit.com/statickit.js"></script>
+63
View File
@@ -0,0 +1,63 @@
.elv-toc {
font-size: 1rem; /* Reset */
}
@media (min-width: 64em) { /* 1024px */
.elv-toc {
position: absolute;
left: -17rem;
width: 16rem;
}
}
.elv-toc-list {
padding-left: 0;
padding-right: 0;
list-style: none;
}
/* Nested lists */
.elv-toc-list ul {
padding: 0;
display: none;
margin-bottom: 1.5em;
list-style: none;
}
.elv-toc-list ul li {
padding-left: 0.875em; /* 14px /16 */
}
@media (min-width: 64em) and (min-height: 48em) { /* 1024 x 768px */
.elv-toc-list ul {
display: block;
}
}
/* List items */
.elv-toc-list a:not(:hover) {
text-decoration: none;
}
.elv-toc-list li {
padding-top: 0;
padding-bottom: 0;
margin: .1em 0 .5em;
}
/* Top level links */
.elv-toc-list > li > a {
font-weight: 400;
font-size: 1.0625em; /* 17px /16 */
color: #222;
}
/* Active links */
.elv-toc-list li.elv-toc-active > a {
font-weight: 700;
text-decoration: underline;
}
.elv-toc-active > a:after {
content: " ⬅";
line-height: .5;
}
/* Show only active nested lists */
.elv-toc-list ul.elv-toc-active,
.elv-toc-list li.elv-toc-active > ul {
display: block;
}
+4
View File
@@ -0,0 +1,4 @@
<header class="elv-layout elv-layout-full elv-header{% if headerClass %} {{ headerClass }}{% endif %}">
{% if page.url != '/' %}<h3><a href="https://torsion.org/borgmatic/">borgmatic</a></h3>{% endif %}
<h1 class="elv-hed">{{ title | safe }}</h1>
</header>
File diff suppressed because one or more lines are too long
+28
View File
@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en"{% if templateClass %} class="{{ templateClass }}"{% endif %}>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ subtitle + ' - ' if subtitle}}{{ title }}</title>
{%- set css %}
{% include 'index.css' %}
{% include 'components/lists.css' %}
{% include 'components/external-links.css' %}
{% include 'components/minilink.css' %}
{% include 'components/toc.css' %}
{% include 'components/info-blocks.css' %}
{% include 'components/suggestion-form.css' %}
{% include 'prism-theme.css' %}
{% include 'asciinema.css' %}
{% endset %}
<style>{{ css | safe }}</style>
{% if feedTitle and feedUrl %}
<link rel="alternate" href="{{ feedUrl }}" title="{{ feedTitle }}" type="application/atom+xml">
{% endif %}
</head>
<body>
{{ content | safe }}
</body>
</html>
+14
View File
@@ -0,0 +1,14 @@
---
layout: layouts/base.njk
templateClass: elv-default
headerClass: elv-header-default
---
{% include "header.njk" %}
<main class="elv-layout{% if layoutClass %} {{ layoutClass }}{% endif %}">
<article>
{{ content | safe }}
{% include 'components/suggestion-form.html' %}
</article>
</main>
+172
View File
@@ -0,0 +1,172 @@
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: #ABB2BF;
background: none;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #383e49;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #9aa2b1;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #282c34;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #5C6370;
}
.token.punctuation {
color: #abb2bf;
}
.token.selector,
.token.tag {
color: #e06c75;
}
.token.property,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.attr-name,
.token.deleted {
color: #d19a66;
}
.token.string,
.token.char,
.token.attr-value,
.token.builtin,
.token.inserted {
color: #98c379;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #56b6c2;
}
.token.atrule,
.token.keyword {
color: #e06c75;
}
.token.function {
color: #61afef;
}
.token.regex,
.token.important,
.token.variable {
color: #c678dd;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
pre.line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre.line-numbers > code {
position: relative;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #5C6370;
display: block;
padding-right: 0.8em;
text-align: right;
}
@@ -0,0 +1,79 @@
---
title: How to add preparation and cleanup steps to backups
---
## Preparation and cleanup hooks
If you find yourself performing prepraration tasks before your backup runs, or
cleanup work afterwards, borgmatic hooks may be of interest. Hooks are shell
commands that borgmatic executes for you at various points, and they're
configured in the `hooks` section of your configuration file. But if you're
looking to backup a database, it's probably easier to use the [database backup
feature](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
instead.
You can specify `before_backup` hooks to perform preparation steps before
running backups, and specify `after_backup` hooks to perform cleanup steps
afterwards. Here's an example:
```yaml
hooks:
before_backup:
- mount /some/filesystem
after_backup:
- umount /some/filesystem
```
The `before_backup` and `after_backup` hooks each run once per configuration
file. `before_backup` hooks run prior to backups of all repositories in a
configuration file, right before the `create` action. `after_backup` hooks run
afterwards, but not if an error occurs in a previous hook or in the backups
themselves.
You can also use `before_everything` and `after_everything` hooks to perform
global setup or cleanup:
```yaml
hooks:
before_everything:
- set-up-stuff-globally
after_everything:
- clean-up-stuff-globally
```
`before_everything` hooks collected from all borgmatic configuration files run
once before all configuration files (prior to all actions), but only if there
is a `create` action. An error encountered during a `before_everything` hook
causes borgmatic to exit without creating backups.
`after_everything` hooks run once after all configuration files and actions,
but only if there is a `create` action. It runs even if an error occurs during
a backup or a backup hook, but not if an error occurs during a
`before_everything` hook.
borgmatic also runs `on_error` hooks if an error occurs, either when creating
a backup or running a backup hook. See the [monitoring and alerting
documentation](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
for more information.
## Hook output
Any output produced by your hooks shows up both at the console and in syslog
(when run in a non-interactive console). For more information, read about <a
href="https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/">inspecting
your backups</a>.
## Security
An important security note about hooks: borgmatic executes all hook commands
with the user permissions of borgmatic itself. So to prevent potential shell
injection or privilege escalation, do not forget to set secure permissions
on borgmatic configuration files (`chmod 0600`) and scripts (`chmod 0700`)
invoked by hooks.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Backup your databases](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
+181
View File
@@ -0,0 +1,181 @@
---
title: How to backup your databases
---
## Database dump hooks
If you want to backup a database, it's best practice with most database
systems to backup an exported database dump, rather than backing up your
database's internal file storage. That's because the internal storage can
change while you're reading from it. In contrast, a database dump creates a
consistent snapshot that is more suited for backups.
Fortunately, borgmatic includes built-in support for creating database dumps
prior to running backups. For example, here is everything you need to dump and
backup a couple of local PostgreSQL databases and a MySQL/MariaDB database:
```yaml
hooks:
postgresql_databases:
- name: users
- name: orders
mysql_databases:
- name: posts
```
Prior to each backup, borgmatic dumps each configured database to a file
(located in `~/.borgmatic/`) and includes it in the backup. After the backup
completes, borgmatic removes the database dump files to recover disk space.
Here's a more involved example that connects to remote databases:
```yaml
hooks:
postgresql_databases:
- name: users
hostname: database1.example.org
port: 5433
username: postgres
password: trustsome1
format: tar
options: "--role=someone"
mysql_databases:
- name: posts
hostname: database2.example.org
port: 3307
username: root
password: trustsome1
options: "--skip-comments"
```
If you want to dump all databases on a host, use `all` for the database name:
```yaml
hooks:
postgresql_databases:
- name: all
mysql_databases:
- name: all
```
Note that you may need to use a `username` of the `postgres` superuser for
this to work with PostgreSQL.
### Configuration backups
An important note about this database configuration: You'll need the
configuration to be present in order for borgmatic to restore a database. So
to prepare for this situation, it's a good idea to include borgmatic's own
configuration files as part of your regular backups. That way, you can always
bring back any missing configuration files in order to restore a database.
## Supported databases
As of now, borgmatic supports PostgreSQL and MySQL/MariaDB databases
directly. But see below about general-purpose preparation and cleanup hooks as
a work-around with other database systems. Also, please [file a
ticket](https://torsion.org/borgmatic/#issues) for additional database systems
that you'd like supported.
## Database restoration
To restore a database dump from an archive, use the `borgmatic restore`
action. But the first step is to figure out which archive to restore from. A
good way to do that is to use the `list` action:
```bash
borgmatic list
```
(No borgmatic `list` action? Try the old-style `--list`, or upgrade
borgmatic!)
That should yield output looking something like:
```text
host-2019-01-01T04:05:06.070809 Tue, 2019-01-01 04:05:06 [...]
host-2019-01-02T04:06:07.080910 Wed, 2019-01-02 04:06:07 [...]
```
Assuming that you want to restore all database dumps from the archive with the
most up-to-date files and therefore the latest timestamp, run a command like:
```bash
borgmatic restore --archive host-2019-01-02T04:06:07.080910
```
(No borgmatic `restore` action? Upgrade borgmatic!)
The `--archive` value is the name of the archive to restore from. This
restores all databases dumps that borgmatic originally backed up to that
archive.
This is a destructive action! `borgmatic restore` replaces live databases by
restoring dumps from the selected archive. So be very careful when and where
you run it.
### Repository selection
If you have a single repository in your borgmatic configuration file(s), no
problem: the `restore` action figures out which repository to use.
But if you have multiple repositories configured, then you'll need to specify
the repository path containing the archive to restore. Here's an example:
```bash
borgmatic restore --repository repo.borg --archive host-2019-...
```
### Restore particular databases
If you've backed up multiple databases into an archive, and you'd only like to
restore one of them, use the `--database` flag to select one or more
databases. For instance:
```bash
borgmatic restore --archive host-2019-... --database users
```
### Limitations
There are a few important limitations with borgmatic's current database
restoration feature that you should know about:
1. You must restore as the same Unix user that created the archive containing
the database dump. That's because the user's home directory path is encoded
into the path of the database dump within the archive.
2. As mentioned above, borgmatic can only restore a database that's defined in
borgmatic's own configuration file. So include your configuration file in
backups to avoid getting caught without a way to restore a database.
3. borgmatic does not currently support backing up or restoring multiple
databases that share the exact same name on different hosts.
### Manual restoration
If you prefer to restore a database without the help of borgmatic, first
[extract](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/) an
archive containing a database dump, and then manually restore the dump file
found within the extracted `~/.borgmatic/` path (e.g. with `pg_restore` or
`mysql` commands).
## Preparation and cleanup hooks
If this database integration is too limited for needs, borgmatic also supports
general-purpose [preparation and cleanup
hooks](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/).
These hooks allows you to trigger arbitrary commands or scripts before and
after backups. So if necessary, you can use these hooks to create database
dumps with any database system.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Extract a backup](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/)
+109
View File
@@ -0,0 +1,109 @@
---
title: How to deal with very large backups
---
## Biggish data
Borg itself is great for efficiently de-duplicating data across successive
backup archives, even when dealing with very large repositories. But you may
find that while borgmatic's default mode of "prune, create, and check" works
well on small repositories, it's not so great on larger ones. That's because
running the default consistency checks takes a long time on large
repositories.
### A la carte actions
If you find yourself in this situation, you have some options. First, you can
run borgmatic's pruning, creating, or checking actions separately. For
instance, the the following optional actions are available:
```bash
borgmatic prune
borgmatic create
borgmatic check
```
(No borgmatic `prune`, `create`, or `check` actions? Try the old-style
`--prune`, `--create`, or `--check`. Or upgrade borgmatic!)
You can run with only one of these actions provided, or you can mix and match
any number of them in a single borgmatic run. This supports approaches like
making backups with `create` on a frequent schedule, while only running
expensive consistency checks with `check` on a much less frequent basis from
a separate cron job.
### Consistency check configuration
Another option is to customize your consistency checks. The default
consistency checks run both full-repository checks and per-archive checks
within each repository.
But if you find that archive checks are too slow, for example, you can
configure borgmatic to run repository checks only. Configure this in the
`consistency` section of borgmatic configuration:
```yaml
consistency:
checks:
- repository
```
If that's still too slow, you can disable consistency checks entirely,
either for a single repository or for all repositories.
Disabling all consistency checks looks like this:
```yaml
consistency:
checks:
- disabled
```
Or, if you have multiple repositories in your borgmatic configuration file,
you can keep running consistency checks, but only against a subset of the
repositories:
```yaml
consistency:
check_repositories:
- path/of/repository_to_check.borg
```
Finally, you can override your configuration file's consistency checks, and
run particular checks via the command-line. For instance:
```bash
borgmatic check --only data --only extract
```
This is useful for running slow consistency checks on an infrequent basis,
separate from your regular checks.
## Troubleshooting
### Broken pipe with remote repository
When running borgmatic on a large remote repository, you may receive errors
like the following, particularly while "borg check" is validating backups for
consistency:
```text
Write failed: Broken pipe
borg: Error: Connection closed by remote host
```
This error can be caused by an ssh timeout, which you can rectify by adding
the following to the `~/.ssh/config` file on the client:
```text
Host *
ServerAliveInterval 120
```
This should make the client keep the connection alive while validating
backups.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
+137
View File
@@ -0,0 +1,137 @@
---
title: How to develop on borgmatic
---
## Source code
To get set up to hack on borgmatic, first clone master via HTTPS or SSH:
```bash
git clone https://projects.torsion.org/witten/borgmatic.git
```
Or:
```bash
git clone ssh://git@projects.torsion.org:3022/witten/borgmatic.git
```
Then, install borgmatic
"[editable](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs)"
so that you can run borgmatic commands while you're hacking on them to
make sure your changes work.
```bash
cd borgmatic/
pip3 install --editable --user .
```
Note that this will typically install the borgmatic commands into
`~/.local/bin`, which may or may not be on your PATH. There are other ways to
install borgmatic editable as well, for instance into the system Python
install (so without `--user`, as root), or even into a
[virtualenv](https://virtualenv.pypa.io/en/stable/). How or where you install
borgmatic is up to you, but generally an editable install makes development
and testing easier.
## Automated tests
Assuming you've cloned the borgmatic source code as described above, and
you're in the `borgmatic/` working copy, install tox, which is used for
setting up testing environments:
```bash
pip3 install --user tox
```
Finally, to actually run tests, run:
```bash
cd borgmatic
tox
```
### Code formatting
If when running tests, you get an error from the
[Black](https://black.readthedocs.io/en/stable/) code formatter about files
that would be reformatted, you can ask Black to format them for you via the
following:
```bash
tox -e black
```
Note that Black requires at minimum Python 3.6.
And if you get a complaint from the
[isort](https://github.com/timothycrosley/isort) Python import orderer, you
can ask isort to order your imports for you:
```bash
tox -e isort
```
### End-to-end tests
borgmatic additionally includes some end-to-end tests that integration test
with Borg for a few representative scenarios. These tests don't run by default
because they're relatively slow and depend on Borg. If you would like to run
them:
```bash
tox -e end-to-end
```
## Code style
Start with [PEP 8](https://www.python.org/dev/peps/pep-0008/). But then, apply
the following deviations from it:
* For strings, prefer single quotes over double quotes.
* Limit all lines to a maximum of 100 characters.
* Use trailing commas within multiline values or argument lists.
* For multiline constructs, put opening and closing delimeters on lines
separate from their contents.
* Within multiline constructs, use standard four-space indentation. Don't align
indentation with an opening delimeter.
borgmatic code uses the [Black](https://black.readthedocs.io/en/stable/) code
formatter, the [Flake8](http://flake8.pycqa.org/en/latest/) code checker, and
the [isort](https://github.com/timothycrosley/isort) import orderer, so
certain code style requirements will be enforced when running automated tests.
See the Black, Flake8, and isort documentation for more information.
## Continuous integration
Each pull request triggers a continuous integration build which runs the test
suite. You can view these builds on
[build.torsion.org](https://build.torsion.org/witten/borgmatic), and they're
also linked from the commits list on each pull request.
## Documentation development
Updates to borgmatic's documentation are welcome. It's formatted in Markdown
and located in the `docs/` directory in borgmatic's source, plus the
`README.md` file at the root.
To build and view a copy of the documentation with your local changes, run the
following from the root of borgmatic's source code:
```bash
sudo scripts/dev-docs
```
This requires Docker to be installed on your system. You may not need to use
sudo if your non-root user has permissions to run Docker.
After you run the script, you can point your web browser at
http://localhost:8080 to view the documentation with your changes.
To close the documentation server, ctrl-C the script. Note that it does not
currently auto-reload, so you'll need to stop it and re-run it for any
additional documentation changes to take effect.
## Related documentation
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
+95
View File
@@ -0,0 +1,95 @@
---
title: How to extract a backup
---
## Extract
When the worst happens—or you want to test your backups—the first step is
to figure out which archive to extract. A good way to do that is to use the
`list` action:
```bash
borgmatic list
```
(No borgmatic `list` action? Try the old-style `--list`, or upgrade
borgmatic!)
That should yield output looking something like:
```text
host-2019-01-01T04:05:06.070809 Tue, 2019-01-01 04:05:06 [...]
host-2019-01-02T04:06:07.080910 Wed, 2019-01-02 04:06:07 [...]
```
Assuming that you want to extract the archive with the most up-to-date files
and therefore the latest timestamp, run a command like:
```bash
borgmatic extract --archive host-2019-01-02T04:06:07.080910
```
(No borgmatic `extract` action? Try the old-style `--extract`, or upgrade
borgmatic!)
The `--archive` value is the name of the archive to extract. This extracts the
entire contents of the archive to the current directory, so make sure you're
in the right place before running the command.
## Repository selection
If you have a single repository in your borgmatic configuration file(s), no
problem: the `extract` action figures out which repository to use.
But if you have multiple repositories configured, then you'll need to specify
the repository path containing the archive to extract. Here's an example:
```bash
borgmatic extract --repository repo.borg --archive host-2019-...
```
## Extract particular files
Sometimes, you want to extract a single deleted file, rather than extracting
everything from an archive. To do that, tack on one or more `--path` values.
For instance:
```bash
borgmatic extract --archive host-2019-... --path path/1 path/2
```
Note that the specified restore paths should not have a leading slash. Like a
whole-archive extract, this also extracts into the current directory. So for
example, if you happen to be in the directory `/var` and you run the `extract`
command above, borgmatic will extract `/var/path/1` and `/var/path/2`.
## Extract to a particular destination
By default, borgmatic extracts files into the current directory. To instead
extract files to a particular destination directory, use the `--destination`
flag:
```bash
borgmatic extract --archive host-2019-... --destination /tmp
```
When using the `--destination` flag, be careful not to overwrite your system's
files with extracted files unless that is your intent.
## Database restoration
The `borgmatic extract` command only extracts files. To restore a database,
please see the [documentation on database backups and
restores](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/).
borgmatic does not perform database restoration as part of `borgmatic extract`
so that you can extract files from your archive without impacting your live
databases.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Backup your databases](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/)
+106
View File
@@ -0,0 +1,106 @@
---
title: How to inspect your backups
---
## Backup progress
By default, borgmatic runs proceed silently except in the case of errors. But
if you'd like to to get additional information about the progress of the
backup as it proceeds, use the verbosity option:
```bash
borgmatic --verbosity 1
```
This lists the files that borgmatic is archiving, which are those that are new
or changed since the last backup.
Or, for even more progress and debug spew:
```bash
borgmatic --verbosity 2
```
## Backup summary
If you're less concerned with progress during a backup, and you only want to
see the summary of archive statistics at the end, you can use the stats
option when performing a backup:
```bash
borgmatic --stats
```
## Existing backups
borgmatic provides convenient actions for Borg's
[list](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and
[info](https://borgbackup.readthedocs.io/en/stable/usage/info.html)
functionality:
```bash
borgmatic list
borgmatic info
```
(No borgmatic `list` or `info` actions? Try the old-style `--list` or
`--info`. Or upgrade borgmatic!)
## Logging
By default, borgmatic logs to a local syslog-compatible daemon if one is
present and borgmatic is running in a non-interactive console. Where those
logs show up depends on your particular system. If you're using systemd, try
running `journalctl -xe`. Otherwise, try viewing `/var/log/syslog` or
similiar.
You can customize the log level used for syslog logging with the
`--syslog-verbosity` flag, and this is independent from the console logging
`--verbosity` flag described above. For instance, to get additional
information about the progress of the backup as it proceeds:
```bash
borgmatic --syslog-verbosity 1
```
Or to increase syslog logging to include debug spew:
```bash
borgmatic --syslog-verbosity 2
```
### Logging to file
If you don't want to use syslog, and you'd rather borgmatic log to a plain
file, use the `--log-file` flag:
```bash
borgmatic --log-file /path/to/file.log
```
Note that if you use the `--log-file` flag, you are responsible for rotating
the log file so it doesn't grow too large. Also, there is a
`--log-file-verbosity` flag to customize the log file's log level.
### systemd journal
If your local syslog daemon is systemd's journal, be aware that journald by
default throttles the rate at which a particular program can log. So you may
need to [change the journald rate
limit](https://www.freedesktop.org/software/systemd/man/journald.conf.html#RateLimitIntervalSec=)
in `/etc/systemd/journald.conf` if you're finding that borgmatic journald logs
are missing.
Note that the [sample borgmatic systemd service
file](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#systemd)
already has this rate limit disabled.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
+115
View File
@@ -0,0 +1,115 @@
---
title: How to make per-application backups
---
## Multiple backup configurations
You may find yourself wanting to create different backup policies for
different applications on your system. For instance, you may want one backup
configuration for your database data directory, and a different configuration
for your user home directories.
The way to accomplish that is pretty simple: Create multiple separate
configuration files and place each one in a `/etc/borgmatic.d/` directory. For
instance:
```bash
sudo mkdir /etc/borgmatic.d
sudo generate-borgmatic-config --destination /etc/borgmatic.d/app1.yaml
sudo generate-borgmatic-config --destination /etc/borgmatic.d/app2.yaml
```
When you set up multiple configuration files like this, borgmatic will run
each one in turn from a single borgmatic invocation. This includes, by
default, the traditional `/etc/borgmatic/config.yaml` as well.
And if you need even more customizability, you can specify alternate
configuration paths on the command-line with borgmatic's `--config` option.
See `borgmatic --help` for more information.
## Configuration includes
Once you have multiple different configuration files, you might want to share
common configuration options across these files with having to copy and paste
them. To achieve this, you can put fragments of common configuration options
into a file, and then include or inline that file into one or more borgmatic
configuration files.
Let's say that you want to include common retention configuration across all
of your configuration files. You could do that in each configuration file with
the following:
```yaml
location:
...
retention:
!include /etc/borgmatic/common_retention.yaml
```
And then the contents of `common_retention.yaml` could be:
```yaml
keep_hourly: 24
keep_daily: 7
```
To prevent borgmatic from trying to load these configuration fragments by
themselves and complaining that they are not valid configuration files, you
should put them in a directory other than `/etc/borgmatic.d/`. (A subdirectory
is fine.)
Note that this form of include must be a YAML value rather than a key. For
example, this will not work:
```yaml
location:
...
# Don't do this. It won't work!
!include /etc/borgmatic/common_retention.yaml
```
But if you do want to merge in a YAML key and its values, keep reading!
## Include merging
If you need to get even fancier and pull in common configuration options while
potentially overriding individual options, you can perform a YAML merge of
included configuration using the YAML `<<` key. For instance, here's an
example of a main configuration file that pulls in two retention options via
an include, and then overrides one of them locally:
```yaml
location:
...
retention:
keep_daily: 5
<<: !include /etc/borgmatic/common_retention.yaml
```
This is what `common_retention.yaml` might look like:
```yaml
keep_hourly: 24
keep_daily: 7
```
Once this include gets merged in, the resulting configuration would have a
`keep_hourly` value of `24` and an overridden `keep_daily` value of `5`.
When there is a collision of an option between the local file and the merged
include, the local file's option takes precedent. And note that this is a
shallow merge rather than a deep merge, so the merging does not descend into
nested values.
Note that this `<<` include merging syntax is only for merging in mappings
(keys/values). If you'd like to include other types like scalars or lists
directly, please see the section above about standard includes.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
+226
View File
@@ -0,0 +1,226 @@
---
title: How to monitor your backups
---
## Monitoring and alerting
Having backups is great, but they won't do you a lot of good unless you have
confidence that they're running on a regular basis. That's where monitoring
and alerting comes in.
There are several different ways you can monitor your backups and find out
whether they're succeeding. Which of these you choose to do is up to you and
your particular infrastructure:
1. **Job runner alerts**: The easiest place to start is with failure alerts
from the [scheduled job
runner](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot) (cron,
systemd, etc.) that's running borgmatic. But note that if the job doesn't even
get scheduled (e.g. due to the job runner not running), you probably won't get
an alert at all! Still, this is a decent first line of defense, especially
when combined with some of the other approaches below.
2. **borgmatic error hooks**: The `on_error` hook allows you to run an arbitrary
command or script when borgmatic itself encounters an error running your
backups. So for instance, you can run a script to send yourself a text message
alert. But note that if borgmatic doesn't actually run, this alert won't fire.
See [error
hooks](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#error-hooks)
below for how to configure this.
4. **borgmatic monitoring hooks**: This feature integrates with monitoring
services like [Healthchecks](https://healthchecks.io/),
[Cronitor](https://cronitor.io), and [Cronhub](https://cronhub.io), and pings
these services whenever borgmatic runs. That way, you'll receive an alert when
something goes wrong or the service doesn't hear from borgmatic for a
configured interval. See
[Healthchecks
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#healthchecks-hook), [Cronitor
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronitor-hook), and [Cronhub
hook](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronhub-hook)
below for how to configure this.
3. **Third-party monitoring software**: You can use traditional monitoring
software to consume borgmatic JSON output and track when the last
successful backup occurred. See [scripting
borgmatic](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#scripting-borgmatic)
below for how to configure this.
5. **Borg hosting providers**: Most [Borg hosting
providers](https://torsion.org/borgmatic/#hosting-providers) include
monitoring and alerting as part of their offering. This gives you a dashboard
to check on all of your backups, and can alert you if the service doesn't hear
from borgmatic for a configured interval.
6. **borgmatic consistency checks**: While not strictly part of monitoring, if you
really want confidence that your backups are not only running but are
restorable as well, you can configure particular [consistency
checks](https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#consistency-check-configuration)
or even script full [extract
tests](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/).
## Error hooks
When an error occurs during a backup, borgmatic can run configurable shell
commands to fire off custom error notifications or take other actions, so you
can get alerted as soon as something goes wrong. Here's a not-so-useful
example:
```yaml
hooks:
on_error:
- echo "Error while creating a backup or running a backup hook."
```
The `on_error` hook supports interpolating particular runtime variables into
the hook command. Here's an example that assumes you provide a separate shell
script to handle the alerting:
```yaml
hooks:
on_error:
- send-text-message.sh "{configuration_filename}" "{repository}"
```
In this example, when the error occurs, borgmatic interpolates a few runtime
values into the hook command: the borgmatic configuration filename, and the
path of the repository. Here's the full set of supported variables you can use
here:
* `configuration_filename`: borgmatic configuration filename in which the
error occurred
* `repository`: path of the repository in which the error occurred (may be
blank if the error occurs in a hook)
* `error`: the error message itself
* `output`: output of the command that failed (may be blank if an error
occurred without running a command)
Note that borgmatic does not run `on_error` hooks if an error occurs within a
`before_everything` or `after_everything` hook. For more about hooks, see the
[borgmatic hooks
documentation](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/),
especially the security information.
## Healthchecks hook
[Healthchecks](https://healthchecks.io/) is a service that provides "instant
alerts when your cron jobs fail silently", and borgmatic has built-in
integration with it. Once you create a Healthchecks account and project on
their site, all you need to do is configure borgmatic with the unique "Ping
URL" for your project. Here's an example:
```yaml
hooks:
healthchecks: https://hc-ping.com/addffa72-da17-40ae-be9c-ff591afb942a
```
With this hook in place, borgmatic pings your Healthchecks project when a
backup begins, ends, or errors. Specifically, before the <a
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
hooks</a> run, borgmatic lets Healthchecks know that a backup has started.
Then, if the backup completes successfully, borgmatic notifies Healthchecks of
the success after the `after_backup` hooks run. And if an error occurs during
the backup, borgmatic notifies Healthchecks after the `on_error` hooks run.
You can configure Healthchecks to notify you by a [variety of
mechanisms](https://healthchecks.io/#welcome-integrations) when backups fail
or it doesn't hear from borgmatic for a certain period of time.
## Cronitor hook
[Cronitor](https://cronitor.io/) provides "Cron monitoring and uptime healthchecks
for websites, services and APIs", and borgmatic has built-in
integration with it. Once you create a Cronitor account and cron job monitor on
their site, all you need to do is configure borgmatic with the unique "Ping
API URL" for your monitor. Here's an example:
```yaml
hooks:
cronitor: https://cronitor.link/d3x0c1
```
With this hook in place, borgmatic pings your Cronitor monitor when a backup
begins, ends, or errors. Specifically, before the <a
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
hooks</a> run, borgmatic lets Cronitor know that a backup has started. Then,
if the backup completes successfully, borgmatic notifies Cronitor of the
success after the `after_backup` hooks run. And if an error occurs during the
backup, borgmatic notifies Cronitor after the `on_error` hooks run.
You can configure Cronitor to notify you by a [variety of
mechanisms](https://cronitor.io/docs/cron-job-notifications) when backups fail
or it doesn't hear from borgmatic for a certain period of time.
## Cronhub hook
[Cronhub](https://cronhub.io/) provides "instant alerts when any of your
background jobs fail silently or run longer than expected", and borgmatic has
built-in integration with it. Once you create a Cronhub account and monitor on
their site, all you need to do is configure borgmatic with the unique "Ping
URL" for your monitor. Here's an example:
```yaml
hooks:
cronhub: https://cronhub.io/start/1f5e3410-254c-11e8-b61d-55875966d031
```
With this hook in place, borgmatic pings your Cronhub monitor when a backup
begins, ends, or errors. Specifically, before the <a
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
hooks</a> run, borgmatic lets Cronhub know that a backup has started. Then,
if the backup completes successfully, borgmatic notifies Cronhub of the
success after the `after_backup` hooks run. And if an error occurs during the
backup, borgmatic notifies Cronhub after the `on_error` hooks run.
Note that even though you configure borgmatic with the "start" variant of the
ping URL, borgmatic substitutes the correct state into the URL when pinging
Cronhub ("start", "finish", or "fail").
You can configure Cronhub to notify you by a [variety of
mechanisms](https://docs.cronhub.io/integrations.html) when backups fail
or it doesn't hear from borgmatic for a certain period of time.
## Scripting borgmatic
To consume the output of borgmatic in other software, you can include an
optional `--json` flag with `create`, `list`, or `info` to get the output
formatted as JSON.
Note that when you specify the `--json` flag, Borg's other non-JSON output is
suppressed so as not to interfere with the captured JSON. Also note that JSON
output only shows up at the console, and not in syslog.
### Successful backups
`borgmatic list` includes support for a `--successful` flag that only lists
successful (non-checkpoint) backups. This flag works via a basic heuristic: It
assumes that non-checkpoint archive names end with a digit (e.g. from a
timestamp), while checkpoint archive names do not. This means that if you're
using custom archive names that do not end in a digit, the `--successful` flag
will not work as expected.
Combined with a built-in Borg flag like `--last`, you can list the last
successful backup for use in your monitoring scripts. Here's an example
combined with `--json`:
```bash
borgmatic list --successful --last 1 --json
```
Note that this particular combination will only work if you've got a single
backup "series" in your repository. If you're instead backing up, say, from
multiple different hosts into a single repository, then you'll need to get
fancier with your archive listing. See `borg list --help` for more flags.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Add preparation and cleanup steps to backups](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/)
* [Extract a backup](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/)
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
+3
View File
@@ -0,0 +1,3 @@
<head>
<meta http-equiv='refresh' content='0; URL=https://torsion.org/borgmatic/docs/how-to/extract-a-backup/'>
</head>
@@ -0,0 +1,3 @@
<head>
<meta http-equiv='refresh' content='0; URL=https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/'>
</head>
+262
View File
@@ -0,0 +1,262 @@
---
title: How to set up backups with borgmatic
---
## Installation
To get up and running, first [install
Borg](https://borgbackup.readthedocs.io/en/stable/installation.html), at
least version 1.1.
By default, borgmatic looks for its configuration files in `/etc/borgmatic/`
and `/etc/borgmatic.d/`, where the root user typically has read access.
So, to download and install borgmatic as the root user, run the following
commands:
```bash
sudo pip3 install --user --upgrade borgmatic
```
This is a [recommended user site
installation](https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site).
You will need to ensure that `/root/.local/bin` is available on your `$PATH`
so
that the borgmatic executable is available. For instance, adding this to
root's `~/.profile` or `~/.bash_profile` may do the trick:
```bash
export PATH="$PATH:~/.local/bin"
```
Note that your pip binary may have a different name than "pip3". Make sure
you're using Python 3, as borgmatic does not support Python 2.
### Other ways to install
Along with the above process, you have several other options for installing
borgmatic:
* [Docker base image](https://hub.docker.com/r/monachus/borgmatic/)
* [Docker image with support for scheduled backups](https://hub.docker.com/r/b3vis/borgmatic/)
* [Debian](https://tracker.debian.org/pkg/borgmatic)
* [Ubuntu](https://launchpad.net/ubuntu/+source/borgmatic)
* [Fedora](https://bodhi.fedoraproject.org/updates/?search=borgmatic)
* [Arch Linux](https://www.archlinux.org/packages/community/any/borgmatic/)
* [OpenBSD](http://ports.su/sysutils/borgmatic)
* [openSUSE](https://software.opensuse.org/package/borgmatic)
* [stand-alone binary](https://github.com/cmarquardt/borgmatic-binary)
* [virtualenv](https://virtualenv.pypa.io/en/stable/)
## Hosting providers
Need somewhere to store your encrypted offsite backups? The following hosting
providers include specific support for Borg/borgmatic. Using these links and
services helps support borgmatic development and hosting. (These are referral
links, but without any tracking scripts or cookies.)
<ul>
<li class="referral"><a href="https://www.rsync.net/cgi-bin/borg.cgi?campaign=borg&adgroup=borgmatic">rsync.net</a>: Cloud Storage provider with full support for borg and any other SSH/SFTP tool</li>
<li class="referral"><a href="https://www.borgbase.com/?utm_source=borgmatic">BorgBase</a>: Borg hosting service with support for monitoring, 2FA, and append-only repos</li>
</ul>
## Configuration
After you install borgmatic, generate a sample configuration file:
```bash
sudo generate-borgmatic-config
```
If that command is not found, then it may be installed in a location that's
not in your system `PATH` (see above). Try looking in `~/.local/bin/`.
This generates a sample configuration file at /etc/borgmatic/config.yaml (by
default). You should edit the file to suit your needs, as the values are
representative. All options are optional except where indicated, so feel free
to ignore anything you don't need.
Note that the configuration file is organized into distinct sections, each
with a section name like `location:` or `storage:`. So take care that if you
uncomment a particular option, also uncomment its containing section name, or
else borgmatic won't recognize the option. Also be sure to use spaces rather
than tabs for indentation; YAML does not allow tabs.
You can also get the same sample configuration file from the [configuration
reference](https://torsion.org/borgmatic/docs/reference/configuration/), the
authoritative set of all configuration options. This is handy if borgmatic has
added new options
since you originally created your configuration file. Also check out how to
[upgrade your
configuration](https://torsion.org/borgmatic/docs/how-to/upgrade/#upgrading-your-configuration).
### Encryption
Note that if you plan to run borgmatic on a schedule with cron, and you
encrypt your Borg repository with a passphrase instead of a key file, you'll
either need to set the borgmatic `encryption_passphrase` configuration
variable or set the `BORG_PASSPHRASE` environment variable. See the
[repository encryption
section](https://borgbackup.readthedocs.io/en/stable/quickstart.html#repository-encryption)
of the Borg Quick Start for more info.
Alternatively, you can specify the passphrase programatically by setting
either the borgmatic `encryption_passcommand` configuration variable or the
`BORG_PASSCOMMAND` environment variable. See the [Borg Security
FAQ](http://borgbackup.readthedocs.io/en/stable/faq.html#how-can-i-specify-the-encryption-passphrase-programmatically)
for more info.
### Validation
If you'd like to validate that your borgmatic configuration is valid, the
following command is available for that:
```bash
sudo validate-borgmatic-config
```
This command's exit status (`$?` in Bash) is zero when configuration is valid
and non-zero otherwise.
Validating configuration can be useful if you generate your configuration
files via configuration management, or you want to double check that your hand
edits are valid.
## Initialization
Before you can create backups with borgmatic, you first need to initialize 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:
```bash
borgmatic init --encryption repokey
```
(No borgmatic `init` action? Try the old-style `--init` flag, or upgrade
borgmatic!)
This uses the borgmatic configuration file you created above to determine
which local or remote repository to create, and encrypts it with the
encryption passphrase specified there if one is provided. Read about [Borg
encryption
modes](https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-modes)
for the menu of available encryption modes.
Also, optionally check out the [Borg Quick
Start](https://borgbackup.readthedocs.org/en/stable/quickstart.html) for more
background about repository initialization.
Note that borgmatic skips repository initialization if the repository already
exists. This supports use cases like ensuring a repository exists prior to
performing a backup.
If the repository is on a remote host, make sure that your local user has
key-based SSH access to the desired user account on the remote host.
## Backups
Now that you've configured borgmatic and initialized a repository, it's a
good idea to test that borgmatic is working. So to run borgmatic and start a
backup, you can invoke it like this:
```bash
borgmatic --verbosity 1
```
By default, this will also prune any old backups as per the configured
retention policy, and check backups for consistency problems due to things
like file damage.
The verbosity flag makes borgmatic list the files that it's archiving, which
are those that are new or changed since the last backup. Eyeball the list and
see if it matches your expectations based on the configuration.
## Autopilot
Running backups manually is good for validating your configuration, but I'm
guessing that you want to run borgmatic automatically, say once a day. To do
that, you can configure a separate job runner to invoke it periodically.
### cron
If you're using cron, download the [sample cron
file](https://projects.torsion.org/witten/borgmatic/src/master/sample/cron/borgmatic).
Then, from the directory where you downloaded it:
```bash
sudo mv borgmatic /etc/cron.d/borgmatic
sudo chmod +x /etc/cron.d/borgmatic
```
You can modify the cron file if you'd like to run borgmatic more or less frequently.
### systemd
If you're using systemd instead of cron to run jobs, download the [sample
systemd service
file](https://projects.torsion.org/witten/borgmatic/raw/branch/master/sample/systemd/borgmatic.service)
and the [sample systemd timer
file](https://projects.torsion.org/witten/borgmatic/raw/branch/master/sample/systemd/borgmatic.timer).
Then, from the directory where you downloaded them:
```bash
sudo mv borgmatic.service borgmatic.timer /etc/systemd/system/
sudo systemctl enable borgmatic.timer
sudo systemctl start borgmatic.timer
```
Feel free to modify the timer file based on how frequently you'd like
borgmatic to run.
## Colored output
Borgmatic produces colored terminal output by default. It is disabled when a
non-interactive terminal is detected (like a cron job). Otherwise, you can
disable it by passing the `--no-color` flag, setting the environment variable
`PY_COLORS=False`, or setting the `color` option to `false` in the `output`
section of configuration.
## Troubleshooting
### "found character that cannot start any token" error
If you run borgmatic and see an error looking something like this, it probably
means you've used tabs instead of spaces:
```
test.yaml: Error parsing configuration file
An error occurred while parsing a configuration file at config.yaml:
while scanning for the next token
found character that cannot start any token
in "config.yaml", line 230, column 1
```
YAML does not allow tabs. So to fix this, replace any tabs in your
configuration file with the requisite number of spaces.
### libyaml compilation errors
borgmatic depends on a Python YAML library (ruamel.yaml) that will optionally
use a C YAML library (libyaml) if present. But if it's not installed, then
when installing or upgrading borgmatic, you may see errors about compiling the
YAML library. If so, not to worry. borgmatic should install and function
correctly even without the C YAML library. And borgmatic won't be any faster
with the C library present, so you don't need to go out of your way to install
it.
## Related documentation
* [Make per-application backups](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/)
* [Deal with very large backups](https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/)
* [Inspect your backups](https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/)
* [Monitor your backups](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/)
* [Upgrade borgmatic](https://torsion.org/borgmatic/docs/how-to/upgrade/)
* [borgmatic configuration reference](https://torsion.org/borgmatic/docs/reference/configuration/)
* [borgmatic command-line reference](https://torsion.org/borgmatic/docs/reference/command-line/)
+118
View File
@@ -0,0 +1,118 @@
---
title: How to upgrade borgmatic
---
## Upgrading
In general, all you should need to do to upgrade borgmatic is run the
following:
```bash
sudo pip3 install --user --upgrade borgmatic
```
See below about special cases with old versions of borgmatic. Additionally, if
you installed borgmatic [without using `pip3 install
--user`](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#other-ways-to-install),
then your upgrade process may be different.
### Upgrading your configuration
The borgmatic configuration file format is almost always backwards-compatible
from release to release without any changes, but you may still want to update
your configuration file when you upgrade to take advantage of new
configuration options. This is completely optional. If you prefer, you can add
new configuration options manually.
If you do want to upgrade your configuration file to include new options, use
the `generate-borgmatic-config` script with its optional `--source` flag that
takes the path to your original configuration file. If provided with this
path, `generate-borgmatic-config` merges your original configuration into the
generated configuration file, so you get all the newest options and comments.
Here's an example:
```bash
generate-borgmatic-config --source config.yaml --destination config-new.yaml
```
New options start as commented out, so you can edit the file and decide
whether you want to use each one.
There are a few caveats to this process, however. First, when generating the
new configuration file, `generate-borgmatic-config` replaces any comments
you've written in your original configuration file with the newest generated
comments. Second, the script adds back any options you had originally deleted,
although it does so with the options commented out. And finally, any YAML
includes you've used in the source configuration get flattened out into a
single generated file.
As a safety measure, `generate-borgmatic-config` refuses to modify
configuration files in-place. So it's up to you to review the generated file
and, if desired, replace your original configuration file with it.
### Upgrading from borgmatic 1.0.x
borgmatic changed its configuration file format in version 1.1.0 from
INI-style to YAML. This better supports validation, and has a more natural way
to express lists of values. To upgrade your existing configuration, first
upgrade to the new version of borgmatic.
As of version 1.1.0, borgmatic no longer supports Python 2. If you were
already running borgmatic with Python 3, then you can upgrade borgmatic
in-place:
```bash
sudo pip3 install --user --upgrade borgmatic
```
But if you were running borgmatic with Python 2, uninstall and reinstall instead:
```bash
sudo pip uninstall borgmatic
sudo pip3 install --user borgmatic
```
The pip binary names for different versions of Python can differ, so the above
commands may need some tweaking to work on your machine.
Once borgmatic is upgraded, run:
```bash
sudo upgrade-borgmatic-config
```
That will generate a new YAML configuration file at /etc/borgmatic/config.yaml
(by default) using the values from both your existing configuration and
excludes files. The new version of borgmatic will consume the YAML
configuration file instead of the old one.
### Upgrading from atticmatic
You can ignore this section if you're not an atticmatic user (the former name
of borgmatic).
borgmatic only supports Borg now and no longer supports Attic. So if you're
an Attic user, consider switching to Borg. See the [Borg upgrade
command](https://borgbackup.readthedocs.io/en/stable/usage.html#borg-upgrade)
for more information. Then, follow the instructions above about setting up
your borgmatic configuration files.
If you were already using Borg with atticmatic, then you can upgrade
from atticmatic to borgmatic by running the following commands:
```bash
sudo pip3 uninstall atticmatic
sudo pip3 install --user borgmatic
```
That's it! borgmatic will continue using your /etc/borgmatic configuration
files.
## Related documentation
* [Develop on borgmatic](https://torsion.org/borgmatic/docs/how-to/develop-on-borgmatic/)
+17
View File
@@ -0,0 +1,17 @@
---
title: borgmatic command-line reference
---
## borgmatic options
Here are all of the available borgmatic command-line options. This includes the separate options for
each action sub-command:
```
{% include borgmatic/command-line.txt %}
```
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [borgmatic configuration reference](https://torsion.org/borgmatic/docs/reference/configuration/)
+19
View File
@@ -0,0 +1,19 @@
---
title: borgmatic configuration reference
---
## Configuration file
Here is a full sample borgmatic configuration file including all available options:
```yaml
{% include borgmatic/config.yaml %}
```
Note that you can also [download this configuration
file](https://torsion.org/borgmatic/docs/reference/config.yaml) for use locally.
## Related documentation
* [Set up backups with borgmatic](https://torsion.org/borgmatic/docs/how-to/set-up-backups/)
* [borgmatic command-line reference](https://torsion.org/borgmatic/docs/reference/command-line/)
+3
View File
@@ -0,0 +1,3 @@
[tool.black]
line-length = 100
skip-string-normalization = true
-3
View File
@@ -1,3 +0,0 @@
# You can drop this file into /etc/cron.d/ to run atticmatic nightly.
0 3 * * * root PATH=$PATH:/usr/local/bin /usr/local/bin/atticmatic
-3
View File
@@ -1,3 +0,0 @@
# You can drop this file into /etc/cron.d/ to run borgmatic nightly.
0 3 * * * root PATH=$PATH:/usr/local/bin /usr/local/bin/borgmatic
-36
View File
@@ -1,36 +0,0 @@
[location]
# Space-separated list of source directories to backup.
source_directories: /home /etc
# Path to local or remote repository.
repository: user@backupserver:sourcehostname.attic
[storage]
# Passphrase to unlock the encryption key with. Only use on repositories that
# were initialized with passphrase/repokey encryption.
#encryption_passphrase: foo
# For Borg only, you can specify the type of compression to use when creating
# archives. See https://borgbackup.github.io/borgbackup/usage.html#borg-create
# for details. Defaults to no compression.
#compression: lz4
[retention]
# Retention policy for how many backups to keep in each category. See
# https://attic-backup.org/usage.html#attic-prune or
# https://borgbackup.github.io/borgbackup/usage.html#borg-prune for details.
#keep_within: 3H
#keep_hourly: 24
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
keep_yearly: 1
#prefix: sourcehostname
[consistency]
# Space-separated list of consistency checks to run: "repository", "archives",
# or both. Defaults to both. Set to "disabled" to disable all consistency
# checks. See https://attic-backup.org/usage.html#attic-check or
# https://borgbackup.github.io/borgbackup/usage.html#borg-check for details.
checks: repository archives
# For Borg only, you can restrict the number of checked archives to the last n.
#check_last: 3
+3
View File
@@ -0,0 +1,3 @@
# You can drop this file into /etc/cron.d/ to run borgmatic nightly.
0 3 * * * root PATH=$PATH:/usr/bin:/usr/local/bin /root/.local/bin/borgmatic --syslog-verbosity 1
-3
View File
@@ -1,3 +0,0 @@
*.pyc
/home/*/.cache
/etc/ssl
+22
View File
@@ -0,0 +1,22 @@
[Unit]
Description=borgmatic backup
Wants=network-online.target
After=network-online.target
ConditionACPower=true
[Service]
Type=oneshot
# Lower CPU and I/O priority.
Nice=19
CPUSchedulingPolicy=batch
IOSchedulingClass=best-effort
IOSchedulingPriority=7
IOWeight=100
Restart=no
LogRateLimitIntervalSec=0
# Delay start to prevent backups running during boot.
ExecStartPre=sleep 1m
ExecStart=systemd-inhibit --who="borgmatic" --why="Prevent interrupting scheduled backup" /root/.local/bin/borgmatic --syslog-verbosity 1
+9
View File
@@ -0,0 +1,9 @@
[Unit]
Description=Run borgmatic backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
docker build --tag borgmatic-docs --build-arg ENVIRONMENT=dev --file docs/Dockerfile .
echo
echo "You can view dev docs at http://localhost:8080"
echo
docker run --interactive --tty --publish 8080:80 --rm borgmatic-docs

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