diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 53d9582cb..8bcdf82ae 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -954,6 +954,21 @@ def exit_with_help_link(): # pragma: no cover sys.exit(1) +def check_and_show_help_on_no_args(configs): + """ + Check if the 'borgmatic' command is run without any arguments. If the configuration option + 'default_actions' is set to False, show the help message. Otherwise, trigger the + default backup behavior. + """ + if len(sys.argv) == 1: # No arguments provided + default_actions = any( + config.get('default_actions', True) for config in configs.values() + ) + if not default_actions: + parse_arguments('--help') + sys.exit(0) + + def main(extra_summary_logs=[]): # pragma: no cover configure_signals() configure_delayed_logging() @@ -989,6 +1004,10 @@ def main(extra_summary_logs=[]): # pragma: no cover global_arguments.overrides, resolve_env=global_arguments.resolve_env and not validate, ) + + # Use the helper function to check and show help on no arguments, passing the preloaded configs + check_and_show_help_on_no_args(configs) + configuration_parse_errors = ( (max(log.levelno for log in parse_logs) >= logging.CRITICAL) if parse_logs else False ) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 7dd4e51c2..aaabe1e57 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -2685,3 +2685,11 @@ properties: example: /usr/local/bin/keepassxc-cli description: | Configuration for integration with the KeePassXC password manager. + default_actions: + type: boolean + description: | + Whether to apply default actions (e.g., backup) when no arguments + are supplied to the borgmatic command. If set to true, borgmatic + will trigger the default actions(create, prune, compact and check). + If set to false, borgmatic will display the help message instead. + example: true diff --git a/docs/how-to/set-up-backups.md b/docs/how-to/set-up-backups.md index c90b9136e..5871bd6bd 100644 --- a/docs/how-to/set-up-backups.md +++ b/docs/how-to/set-up-backups.md @@ -296,6 +296,20 @@ skip_actions: - compact ``` +### Disabling default actions + +By default, running `borgmatic` without any arguments will perform the default +backup actions (create, prune, compact and check). If you want to disable this +behavior and require explicit actions to be specified, add the following to +your configuration: + +```yaml +default_actions: false +``` + +With this setting, running `borgmatic` without arguments will show the help +message instead of performing any actions. + ## Autopilot diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index 481a856af..296bcbbf7 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -2120,3 +2120,53 @@ def test_collect_configuration_run_summary_logs_outputs_merged_json_results(): arguments=arguments, ) ) +def test_check_and_show_help_on_no_args_shows_help_when_no_args_and_default_actions_false(): + flexmock(module.sys).should_receive('argv').and_return(['borgmatic']) + flexmock(module).should_receive('parse_arguments').with_args('--help').once() + flexmock(module.sys).should_receive('exit').with_args(0).once() + module.check_and_show_help_on_no_args({'test.yaml': {'default_actions': False}}) + + +def test_check_and_show_help_on_no_args_does_not_show_help_when_no_args_and_default_actions_true(): + flexmock(module.sys).should_receive('argv').and_return(['borgmatic']) + flexmock(module).should_receive('parse_arguments').never() + flexmock(module.sys).should_receive('exit').never() + module.check_and_show_help_on_no_args({'test.yaml': {'default_actions': True}}) + + +def test_check_and_show_help_on_no_args_does_not_show_help_when_args_provided(): + flexmock(module.sys).should_receive('argv').and_return(['borgmatic', '--create']) + flexmock(module).should_receive('parse_arguments').never() + flexmock(module.sys).should_receive('exit').never() + module.check_and_show_help_on_no_args({'test.yaml': {'default_actions': False}}) + +def test_check_and_show_help_on_no_args_with_no_default_actions_in_all_configs(): + flexmock(module.sys).should_receive('argv').and_return(['borgmatic']) + + # Both configs have default_actions set to False, so help should be shown + configs = { + 'config1.yaml': {'default_actions': False}, + 'config2.yaml': {'default_actions': False} + } + + # Expect help to be shown + flexmock(module).should_receive('parse_arguments').with_args('--help').once() + flexmock(module.sys).should_receive('exit').with_args(0).once() + + module.check_and_show_help_on_no_args(configs) + +def test_check_and_show_help_on_no_args_with_conflicting_configs(): + flexmock(module.sys).should_receive('argv').and_return(['borgmatic']) + + # Simulate two config files with conflicting 'default_actions' values + configs = { + 'config1.yaml': {'default_actions': True}, + 'config2.yaml': {'default_actions': False} + } + + # Expect help not to be shown because at least one config enables default actions + flexmock(module).should_receive('parse_arguments').never() + flexmock(module.sys).should_receive('exit').never() + + module.check_and_show_help_on_no_args(configs) +