Browse Source

Update the logic that probes for the borgmatic streaming database dump, bootstrap metadata, and check state directories to support more platforms and use cases (#934).

Dan Helfman 6 months ago
parent
commit
295bfb0c57

+ 4 - 0
NEWS

@@ -1,5 +1,9 @@
 1.9.2.dev0
 1.9.2.dev0
  * #932: Fix missing build backend setting in pyproject.toml to allow Fedora builds.
  * #932: Fix missing build backend setting in pyproject.toml to allow Fedora builds.
+ * #934: Update the logic that probes for the borgmatic streaming database dump, bootstrap
+   metadata, and check state directories to support more platforms and use cases.
+ * #934: Add the "RuntimeDirectory" and "StateDirectory" options to the sample systemd service
+   file to support the new runtime and state directory logic.
 
 
 1.9.1
 1.9.1
  * #928: Fix the user runtime directory location on macOS (and possibly Cygwin).
  * #928: Fix the user runtime directory location on macOS (and possibly Cygwin).

+ 25 - 15
borgmatic/actions/check.py

@@ -403,16 +403,22 @@ BORG_DIRECTORY_FILE_TYPE = 'd'
 
 
 
 
 def collect_spot_check_archive_paths(
 def collect_spot_check_archive_paths(
-    repository, archive, config, local_borg_version, global_arguments, local_path, remote_path
+    repository,
+    archive,
+    config,
+    local_borg_version,
+    global_arguments,
+    local_path,
+    remote_path,
+    borgmatic_runtime_directory,
 ):
 ):
     '''
     '''
     Given a repository configuration dict, the name of the latest archive, a configuration dict, the
     Given a repository configuration dict, the name of the latest archive, a configuration dict, the
-    local Borg version, global arguments as an argparse.Namespace instance, the local Borg path, and
-    the remote Borg path, collect the paths from the given archive (but only include files and
-    symlinks and exclude borgmatic runtime directories).
+    local Borg version, global arguments as an argparse.Namespace instance, the local Borg path, the
+    remote Borg path, and the borgmatic runtime directory, collect the paths from the given archive
+    (but only include files and symlinks and exclude borgmatic runtime directories).
     '''
     '''
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
-    borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
 
 
     return tuple(
     return tuple(
         path
         path
@@ -546,11 +552,12 @@ def spot_check(
     global_arguments,
     global_arguments,
     local_path,
     local_path,
     remote_path,
     remote_path,
+    borgmatic_runtime_directory,
 ):
 ):
     '''
     '''
     Given a repository dict, a loaded configuration dict, the local Borg version, global arguments
     Given a repository dict, a loaded configuration dict, the local Borg version, global arguments
-    as an argparse.Namespace instance, the local Borg path, and the remote Borg path, perform a spot
-    check for the latest archive in the given repository.
+    as an argparse.Namespace instance, the local Borg path, the remote Borg path, and the borgmatic
+    runtime directory, perform a spot check for the latest archive in the given repository.
 
 
     A spot check compares file counts and also the hashes for a random sampling of source files on
     A spot check compares file counts and also the hashes for a random sampling of source files on
     disk to those stored in the latest archive. If any differences are beyond configured tolerances,
     disk to those stored in the latest archive. If any differences are beyond configured tolerances,
@@ -600,6 +607,7 @@ def spot_check(
         global_arguments,
         global_arguments,
         local_path,
         local_path,
         remote_path,
         remote_path,
+        borgmatic_runtime_directory,
     )
     )
     logger.debug(f'{log_label}: {len(archive_paths)} total archive paths for spot check')
     logger.debug(f'{log_label}: {len(archive_paths)} total archive paths for spot check')
 
 
@@ -730,14 +738,16 @@ def run_check(
         write_check_time(make_check_time_path(config, repository_id, 'extract'))
         write_check_time(make_check_time_path(config, repository_id, 'extract'))
 
 
     if 'spot' in checks:
     if 'spot' in checks:
-        spot_check(
-            repository,
-            config,
-            local_borg_version,
-            global_arguments,
-            local_path,
-            remote_path,
-        )
+        with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
+            spot_check(
+                repository,
+                config,
+                local_borg_version,
+                global_arguments,
+                local_path,
+                remote_path,
+                borgmatic_runtime_directory,
+            )
         write_check_time(make_check_time_path(config, repository_id, 'spot'))
         write_check_time(make_check_time_path(config, repository_id, 'spot'))
 
 
     borgmatic.hooks.command.execute_hook(
     borgmatic.hooks.command.execute_hook(

+ 30 - 26
borgmatic/actions/config/bootstrap.py

@@ -38,37 +38,41 @@ def get_config_paths(archive_name, bootstrap_arguments, global_arguments, local_
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(
         {'borgmatic_source_directory': bootstrap_arguments.borgmatic_source_directory}
         {'borgmatic_source_directory': bootstrap_arguments.borgmatic_source_directory}
     )
     )
-    borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(
-        {'user_runtime_directory': bootstrap_arguments.user_runtime_directory}
-    )
     config = make_bootstrap_config(bootstrap_arguments)
     config = make_bootstrap_config(bootstrap_arguments)
 
 
     # Probe for the manifest file in multiple locations, as the default location has moved to the
     # Probe for the manifest file in multiple locations, as the default location has moved to the
     # borgmatic runtime directory (which get stored as just "/borgmatic" with Borg 1.4+). But we
     # borgmatic runtime directory (which get stored as just "/borgmatic" with Borg 1.4+). But we
     # still want to support reading the manifest from previously created archives as well.
     # still want to support reading the manifest from previously created archives as well.
-    for base_directory in ('borgmatic', borgmatic_runtime_directory, borgmatic_source_directory):
-        borgmatic_manifest_path = os.path.join(base_directory, 'bootstrap', 'manifest.json')
-
-        extract_process = borgmatic.borg.extract.extract_archive(
-            global_arguments.dry_run,
-            bootstrap_arguments.repository,
-            archive_name,
-            [borgmatic_manifest_path],
-            config,
-            local_borg_version,
-            global_arguments,
-            local_path=bootstrap_arguments.local_path,
-            remote_path=bootstrap_arguments.remote_path,
-            extract_to_stdout=True,
-        )
-        manifest_json = extract_process.stdout.read()
-
-        if manifest_json:
-            break
-    else:
-        raise ValueError(
-            'Cannot read configuration paths from archive due to missing bootstrap manifest'
-        )
+    with borgmatic.config.paths.Runtime_directory(
+        {'user_runtime_directory': bootstrap_arguments.user_runtime_directory}
+    ) as borgmatic_runtime_directory:
+        for base_directory in (
+            'borgmatic',
+            borgmatic_runtime_directory,
+            borgmatic_source_directory,
+        ):
+            borgmatic_manifest_path = os.path.join(base_directory, 'bootstrap', 'manifest.json')
+
+            extract_process = borgmatic.borg.extract.extract_archive(
+                global_arguments.dry_run,
+                bootstrap_arguments.repository,
+                archive_name,
+                [borgmatic_manifest_path],
+                config,
+                local_borg_version,
+                global_arguments,
+                local_path=bootstrap_arguments.local_path,
+                remote_path=bootstrap_arguments.remote_path,
+                extract_to_stdout=True,
+            )
+            manifest_json = extract_process.stdout.read()
+
+            if manifest_json:
+                break
+        else:
+            raise ValueError(
+                'Cannot read configuration paths from archive due to missing bootstrap manifest'
+            )
 
 
     try:
     try:
         manifest_data = json.loads(manifest_json)
         manifest_data = json.loads(manifest_json)

+ 56 - 46
borgmatic/actions/create.py

@@ -14,15 +14,15 @@ import borgmatic.hooks.dump
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def create_borgmatic_manifest(config, config_paths, dry_run):
+def create_borgmatic_manifest(config, config_paths, borgmatic_runtime_directory, dry_run):
     '''
     '''
-    Create a borgmatic manifest file to store the paths to the configuration files used to create
-    the archive.
+    Given a configuration dict, a sequence of config file paths, the borgmatic runtime directory,
+    and whether this is a dry run, create a borgmatic manifest file to store the paths to the
+    configuration files used to create the archive.
     '''
     '''
     if dry_run:
     if dry_run:
         return
         return
 
 
-    borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
     borgmatic_manifest_path = os.path.join(
     borgmatic_manifest_path = os.path.join(
         borgmatic_runtime_directory, 'bootstrap', 'manifest.json'
         borgmatic_runtime_directory, 'bootstrap', 'manifest.json'
     )
     )
@@ -72,53 +72,63 @@ def run_create(
         **hook_context,
         **hook_context,
     )
     )
     logger.info(f'{repository.get("label", repository["path"])}: Creating archive{dry_run_label}')
     logger.info(f'{repository.get("label", repository["path"])}: Creating archive{dry_run_label}')
-    borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
-        'remove_data_source_dumps',
-        config,
-        repository['path'],
-        borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
-        global_arguments.dry_run,
-    )
-    active_dumps = borgmatic.hooks.dispatch.call_hooks(
-        'dump_data_sources',
-        config,
-        repository['path'],
-        borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
-        global_arguments.dry_run,
-    )
-    if config.get('store_config_files', True):
-        create_borgmatic_manifest(
+
+    with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
+        borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+            'remove_data_source_dumps',
             config,
             config,
-            config_paths,
+            repository['path'],
+            borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+            borgmatic_runtime_directory,
             global_arguments.dry_run,
             global_arguments.dry_run,
         )
         )
-    stream_processes = [process for processes in active_dumps.values() for process in processes]
+        active_dumps = borgmatic.hooks.dispatch.call_hooks(
+            'dump_data_sources',
+            config,
+            repository['path'],
+            borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+            borgmatic_runtime_directory,
+            global_arguments.dry_run,
+        )
+        stream_processes = [process for processes in active_dumps.values() for process in processes]
 
 
-    json_output = borgmatic.borg.create.create_archive(
-        global_arguments.dry_run,
-        repository['path'],
-        config,
-        config_paths,
-        local_borg_version,
-        global_arguments,
-        local_path=local_path,
-        remote_path=remote_path,
-        progress=create_arguments.progress,
-        stats=create_arguments.stats,
-        json=create_arguments.json,
-        list_files=create_arguments.list_files,
-        stream_processes=stream_processes,
-    )
-    if json_output:
-        yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+        if config.get('store_config_files', True):
+            create_borgmatic_manifest(
+                config,
+                config_paths,
+                borgmatic_runtime_directory,
+                global_arguments.dry_run,
+            )
+
+        json_output = borgmatic.borg.create.create_archive(
+            global_arguments.dry_run,
+            repository['path'],
+            config,
+            config_paths,
+            local_borg_version,
+            global_arguments,
+            borgmatic_runtime_directory,
+            local_path=local_path,
+            remote_path=remote_path,
+            progress=create_arguments.progress,
+            stats=create_arguments.stats,
+            json=create_arguments.json,
+            list_files=create_arguments.list_files,
+            stream_processes=stream_processes,
+        )
+
+        if json_output:
+            yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+
+        borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+            'remove_data_source_dumps',
+            config,
+            config_filename,
+            borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+            borgmatic_runtime_directory,
+            global_arguments.dry_run,
+        )
 
 
-    borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
-        'remove_data_source_dumps',
-        config,
-        config_filename,
-        borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
-        global_arguments.dry_run,
-    )
     borgmatic.hooks.command.execute_hook(
     borgmatic.hooks.command.execute_hook(
         config.get('after_backup'),
         config.get('after_backup'),
         config.get('umask'),
         config.get('umask'),

+ 107 - 100
borgmatic/actions/restore.py

@@ -108,6 +108,7 @@ def restore_single_data_source(
     hook_name,
     hook_name,
     data_source,
     data_source,
     connection_params,
     connection_params,
+    borgmatic_runtime_directory,
 ):
 ):
     '''
     '''
     Given (among other things) an archive name, a data source hook name, the hostname, port,
     Given (among other things) an archive name, a data source hook name, the hostname, port,
@@ -123,9 +124,9 @@ def restore_single_data_source(
         config,
         config,
         repository['path'],
         repository['path'],
         borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
         borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+        borgmatic_runtime_directory,
         data_source['name'],
         data_source['name'],
     )[hook_name]
     )[hook_name]
-    borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
 
 
     destination_path = (
     destination_path = (
         tempfile.mkdtemp(dir=borgmatic_runtime_directory)
         tempfile.mkdtemp(dir=borgmatic_runtime_directory)
@@ -135,7 +136,7 @@ def restore_single_data_source(
 
 
     try:
     try:
         # Kick off a single data source extract. If using a directory format, extract to a temporary
         # Kick off a single data source extract. If using a directory format, extract to a temporary
-        # directory. Otheriwes extract the single dump file to stdout.
+        # directory. Otherwise extract the single dump file to stdout.
         extract_process = borgmatic.borg.extract.extract_archive(
         extract_process = borgmatic.borg.extract.extract_archive(
             dry_run=global_arguments.dry_run,
             dry_run=global_arguments.dry_run,
             repository=repository['path'],
             repository=repository['path'],
@@ -181,17 +182,17 @@ def collect_archive_data_source_names(
     global_arguments,
     global_arguments,
     local_path,
     local_path,
     remote_path,
     remote_path,
+    borgmatic_runtime_directory,
 ):
 ):
     '''
     '''
     Given a local or remote repository path, a resolved archive name, a configuration dict, the
     Given a local or remote repository path, a resolved archive name, a configuration dict, the
-    local Borg version, global_arguments an argparse.Namespace, and local and remote Borg paths,
-    query the archive for the names of data sources it contains as dumps and return them as a dict
-    from hook name to a sequence of data source names.
+    local Borg version, global_arguments an argparse.Namespace, local and remote Borg paths, and the
+    borgmatic runtime directory, query the archive for the names of data sources it contains as
+    dumps and return them as a dict from hook name to a sequence of data source names.
     '''
     '''
     borgmatic_source_directory = str(
     borgmatic_source_directory = str(
         pathlib.Path(borgmatic.config.paths.get_borgmatic_source_directory(config))
         pathlib.Path(borgmatic.config.paths.get_borgmatic_source_directory(config))
     )
     )
-    borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
 
 
     # Probe for the data source dumps in multiple locations, as the default location has moved to
     # Probe for the data source dumps in multiple locations, as the default location has moved to
     # the borgmatic runtime directory (which get stored as just "/borgmatic" with Borg 1.4+). But we
     # the borgmatic runtime directory (which get stored as just "/borgmatic" with Borg 1.4+). But we
@@ -330,6 +331,7 @@ def run_restore(
     global_arguments,
     global_arguments,
     local_path,
     local_path,
     remote_path,
     remote_path,
+    borgmatic_runtime_directory,
 ):
 ):
     '''
     '''
     Run the "restore" action for the given repository, but only if the repository matches the
     Run the "restore" action for the given repository, but only if the repository matches the
@@ -346,105 +348,110 @@ def run_restore(
         f'{repository.get("label", repository["path"])}: Restoring data sources from archive {restore_arguments.archive}'
         f'{repository.get("label", repository["path"])}: Restoring data sources from archive {restore_arguments.archive}'
     )
     )
 
 
-    borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
-        'remove_data_source_dumps',
-        config,
-        repository['path'],
-        borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
-        global_arguments.dry_run,
-    )
+    with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
+        borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+            'remove_data_source_dumps',
+            config,
+            repository['path'],
+            borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+            borgmatic_runtime_directory,
+            global_arguments.dry_run,
+        )
 
 
-    archive_name = borgmatic.borg.repo_list.resolve_archive_name(
-        repository['path'],
-        restore_arguments.archive,
-        config,
-        local_borg_version,
-        global_arguments,
-        local_path,
-        remote_path,
-    )
-    archive_data_source_names = collect_archive_data_source_names(
-        repository['path'],
-        archive_name,
-        config,
-        local_borg_version,
-        global_arguments,
-        local_path,
-        remote_path,
-    )
-    restore_names = find_data_sources_to_restore(
-        restore_arguments.data_sources, archive_data_source_names
-    )
-    found_names = set()
-    remaining_restore_names = {}
-    connection_params = {
-        'hostname': restore_arguments.hostname,
-        'port': restore_arguments.port,
-        'username': restore_arguments.username,
-        'password': restore_arguments.password,
-        'restore_path': restore_arguments.restore_path,
-    }
-
-    for hook_name, data_source_names in restore_names.items():
-        for data_source_name in data_source_names:
-            found_hook_name, found_data_source = get_configured_data_source(
-                config, archive_data_source_names, hook_name, data_source_name
-            )
+        archive_name = borgmatic.borg.repo_list.resolve_archive_name(
+            repository['path'],
+            restore_arguments.archive,
+            config,
+            local_borg_version,
+            global_arguments,
+            local_path,
+            remote_path,
+        )
+        archive_data_source_names = collect_archive_data_source_names(
+            repository['path'],
+            archive_name,
+            config,
+            local_borg_version,
+            global_arguments,
+            local_path,
+            remote_path,
+        )
+        restore_names = find_data_sources_to_restore(
+            restore_arguments.data_sources, archive_data_source_names
+        )
+        found_names = set()
+        remaining_restore_names = {}
+        connection_params = {
+            'hostname': restore_arguments.hostname,
+            'port': restore_arguments.port,
+            'username': restore_arguments.username,
+            'password': restore_arguments.password,
+            'restore_path': restore_arguments.restore_path,
+        }
 
 
-            if not found_data_source:
-                remaining_restore_names.setdefault(found_hook_name or hook_name, []).append(
-                    data_source_name
+        for hook_name, data_source_names in restore_names.items():
+            for data_source_name in data_source_names:
+                found_hook_name, found_data_source = get_configured_data_source(
+                    config, archive_data_source_names, hook_name, data_source_name
                 )
                 )
-                continue
-
-            found_names.add(data_source_name)
-            restore_single_data_source(
-                repository,
-                config,
-                local_borg_version,
-                global_arguments,
-                local_path,
-                remote_path,
-                archive_name,
-                found_hook_name or hook_name,
-                dict(found_data_source, **{'schemas': restore_arguments.schemas}),
-                connection_params,
-            )
 
 
-    # For any data sources that weren't found via exact matches in the configuration, try to
-    # fallback to "all" entries.
-    for hook_name, data_source_names in remaining_restore_names.items():
-        for data_source_name in data_source_names:
-            found_hook_name, found_data_source = get_configured_data_source(
-                config, archive_data_source_names, hook_name, data_source_name, 'all'
-            )
+                if not found_data_source:
+                    remaining_restore_names.setdefault(found_hook_name or hook_name, []).append(
+                        data_source_name
+                    )
+                    continue
+
+                found_names.add(data_source_name)
+                restore_single_data_source(
+                    repository,
+                    config,
+                    local_borg_version,
+                    global_arguments,
+                    local_path,
+                    remote_path,
+                    archive_name,
+                    found_hook_name or hook_name,
+                    dict(found_data_source, **{'schemas': restore_arguments.schemas}),
+                    connection_params,
+                    borgmatic_runtime_directory,
+                )
 
 
-            if not found_data_source:
-                continue
-
-            found_names.add(data_source_name)
-            data_source = copy.copy(found_data_source)
-            data_source['name'] = data_source_name
-
-            restore_single_data_source(
-                repository,
-                config,
-                local_borg_version,
-                global_arguments,
-                local_path,
-                remote_path,
-                archive_name,
-                found_hook_name or hook_name,
-                dict(data_source, **{'schemas': restore_arguments.schemas}),
-                connection_params,
-            )
+        # For any data sources that weren't found via exact matches in the configuration, try to
+        # fallback to "all" entries.
+        for hook_name, data_source_names in remaining_restore_names.items():
+            for data_source_name in data_source_names:
+                found_hook_name, found_data_source = get_configured_data_source(
+                    config, archive_data_source_names, hook_name, data_source_name, 'all'
+                )
 
 
-    borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
-        'remove_data_source_dumps',
-        config,
-        repository['path'],
-        borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
-        global_arguments.dry_run,
-    )
+                if not found_data_source:
+                    continue
+
+                found_names.add(data_source_name)
+                data_source = copy.copy(found_data_source)
+                data_source['name'] = data_source_name
+
+                restore_single_data_source(
+                    repository,
+                    config,
+                    local_borg_version,
+                    global_arguments,
+                    local_path,
+                    remote_path,
+                    archive_name,
+                    found_hook_name or hook_name,
+                    dict(data_source, **{'schemas': restore_arguments.schemas}),
+                    connection_params,
+                    borgmatic_runtime_directory,
+                )
+
+        borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+            'remove_data_source_dumps',
+            config,
+            repository['path'],
+            borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+            borgmatic_runtime_directory,
+            global_arguments.dry_run,
+        )
 
 
     ensure_data_sources_found(restore_names, remaining_restore_names, found_names)
     ensure_data_sources_found(restore_names, remaining_restore_names, found_names)

+ 2 - 3
borgmatic/borg/create.py

@@ -504,6 +504,7 @@ def create_archive(
     config_paths,
     config_paths,
     local_borg_version,
     local_borg_version,
     global_arguments,
     global_arguments,
+    borgmatic_runtime_directory,
     local_path='borg',
     local_path='borg',
     remote_path=None,
     remote_path=None,
     progress=False,
     progress=False,
@@ -524,9 +525,7 @@ def create_archive(
 
 
     working_directory = borgmatic.config.paths.get_working_directory(config)
     working_directory = borgmatic.config.paths.get_working_directory(config)
     borgmatic_runtime_directories = expand_directories(
     borgmatic_runtime_directories = expand_directories(
-        collect_borgmatic_runtime_directories(
-            borgmatic.config.paths.get_borgmatic_runtime_directory(config)
-        ),
+        collect_borgmatic_runtime_directories(borgmatic_runtime_directory),
         working_directory=working_directory,
         working_directory=working_directory,
     )
     )
 
 

+ 2 - 2
borgmatic/commands/borgmatic.py

@@ -275,8 +275,8 @@ def run_actions(
     '''
     '''
     Given parsed command-line arguments as an argparse.ArgumentParser instance, the configuration
     Given parsed command-line arguments as an argparse.ArgumentParser instance, the configuration
     filename, a configuration dict, a sequence of loaded configuration paths, local and remote paths
     filename, a configuration dict, a sequence of loaded configuration paths, local and remote paths
-    to Borg, a local Borg version string, and a repository name, run all actions from the
-    command-line arguments on the given repository.
+    to Borg, a local Borg version string, a repository name, and the borgmatic runtime directory,
+    run all actions from the command-line arguments on the given repository.
 
 
     Yield JSON output strings from executing any actions that produce JSON.
     Yield JSON output strings from executing any actions that produce JSON.
 
 

+ 67 - 22
borgmatic/config/paths.py

@@ -1,4 +1,8 @@
+import logging
 import os
 import os
+import tempfile
+
+logger = logging.getLogger(__name__)
 
 
 
 
 def expand_user_in_path(path):
 def expand_user_in_path(path):
@@ -26,28 +30,70 @@ def get_borgmatic_source_directory(config):
     return expand_user_in_path(config.get('borgmatic_source_directory') or '~/.borgmatic')
     return expand_user_in_path(config.get('borgmatic_source_directory') or '~/.borgmatic')
 
 
 
 
-def get_borgmatic_runtime_directory(config):
+class Runtime_directory:
     '''
     '''
-    Given a configuration dict, get the borgmatic runtime directory used for storing temporary
-    runtime data like streaming database dumps and bootstrap metadata. Defaults to
-    $XDG_RUNTIME_DIR/./borgmatic or $TMPDIR/./borgmatic or $TEMP/./borgmatic or
-    /run/user/$UID/./borgmatic.
-
-    The "/./" is taking advantage of a Borg feature such that the part of the path before the "/./"
-    does not get stored in the file path within an archive. That way, the path of the runtime
-    directory can change without leaving database dumps within an archive inaccessible.
+    A Python context manager for creating and cleaning up the borgmatic runtime directory used for
+    storing temporary runtime data like streaming database dumps and bootstrap metadata.
+
+    Example use as a context manager:
+
+        with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
+            do_something_with(borgmatic_runtime_directory)
+
+    For the scope of that "with" statement, the runtime directory is available. Afterwards, it
+    automatically gets cleaned up as necessary.
     '''
     '''
-    return expand_user_in_path(
-        os.path.join(
+
+    def __init__(self, config):
+        '''
+        Given a configuration dict, determine the borgmatic runtime directory, creating a secure,
+        temporary directory within it if necessary. Defaults to $XDG_RUNTIME_DIR/./borgmatic or
+        $RUNTIME_DIRECTORY/./borgmatic or $TMPDIR/borgmatic-[random]/./borgmatic or
+        $TEMP/borgmatic-[random]/./borgmatic or /tmp/borgmatic-[random]/./borgmatic where "[random]"
+        is a randomly generated string intended to avoid path collisions.
+
+        If XDG_RUNTIME_DIR or RUNTIME_DIRECTORY is set and already ends in "/borgmatic", then don't
+        tack on a second "/borgmatic" path component.
+
+        The "/./" is taking advantage of a Borg feature such that the part of the path before the "/./"
+        does not get stored in the file path within an archive. That way, the path of the runtime
+        directory can change without leaving database dumps within an archive inaccessible.
+        '''
+
+        runtime_directory = (
             config.get('user_runtime_directory')
             config.get('user_runtime_directory')
-            or os.environ.get('XDG_RUNTIME_DIR')
-            or os.environ.get('TMPDIR')
-            or os.environ.get('TEMP')
-            or f'/run/user/{os.getuid()}',
-            '.',
-            'borgmatic',
+            or os.environ.get('XDG_RUNTIME_DIR')  # Set by PAM on Linux.
+            or os.environ.get('RUNTIME_DIRECTORY')  # Set by systemd if configured.
         )
         )
-    )
+
+        if runtime_directory:
+            self.temporary_directory = None
+        else:
+            self.temporary_directory = tempfile.TemporaryDirectory(
+                prefix='borgmatic', dir=os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp'
+            )
+            runtime_directory = self.temporary_directory.name
+
+        (base_path, final_directory) = os.path.split(runtime_directory.rstrip(os.path.sep))
+
+        self.runtime_path = expand_user_in_path(
+            os.path.join(
+                base_path if final_directory == 'borgmatic' else runtime_directory, '.', 'borgmatic'
+            )
+        )
+
+    def __enter__(self):
+        '''
+        Return the borgmatic runtime path as a string.
+        '''
+        return self.runtime_path
+
+    def __exit__(self, exception, value, traceback):
+        '''
+        Delete any temporary directory that was created as part of initialization.
+        '''
+        if self.temporary_directory:
+            self.temporary_directory.cleanup()
 
 
 
 
 def get_borgmatic_state_directory(config):
 def get_borgmatic_state_directory(config):
@@ -59,10 +105,9 @@ def get_borgmatic_state_directory(config):
     return expand_user_in_path(
     return expand_user_in_path(
         os.path.join(
         os.path.join(
             config.get('user_state_directory')
             config.get('user_state_directory')
-            or os.environ.get(
-                'XDG_STATE_HOME',
-                '~/.local/state',
-            ),
+            or os.environ.get('XDG_STATE_HOME')
+            or os.environ.get('STATE_DIRECTORY')  # Set by systemd if configured.
+            or '~/.local/state',
             'borgmatic',
             'borgmatic',
         )
         )
     )
     )

+ 25 - 23
borgmatic/hooks/mariadb.py

@@ -14,15 +14,11 @@ from borgmatic.hooks import dump
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def make_dump_path(config, base_directory=None):  # pragma: no cover
+def make_dump_path(base_directory):  # pragma: no cover
     '''
     '''
-    Given a configuration dict and an optional base directory, make the corresponding dump path. If
-    a base directory isn't provided, use the borgmatic runtime directory.
+    Given a base directory, make the corresponding dump path.
     '''
     '''
-    return dump.make_data_source_dump_path(
-        base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
-        'mariadb_databases',
-    )
+    return dump.make_data_source_dump_path(base_directory, 'mariadb_databases')
 
 
 
 
 SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
 SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
@@ -126,12 +122,12 @@ def use_streaming(databases, config, log_prefix):
     return any(databases)
     return any(databases)
 
 
 
 
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
     '''
     '''
     Dump the given MariaDB databases to a named pipe. The databases are supplied as a sequence of
     Dump the given MariaDB databases to a named pipe. The databases are supplied as a sequence of
     dicts, one dict describing each database as per the configuration schema. Use the given
     dicts, one dict describing each database as per the configuration schema. Use the given
-    configuration dict to construct the destination path and the given log prefix in any log
-    entries.
+    borgmatic runtime directory to construct the destination path and the given log prefix in any
+    log entries.
 
 
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -142,7 +138,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
     logger.info(f'{log_prefix}: Dumping MariaDB databases{dry_run_label}')
     logger.info(f'{log_prefix}: Dumping MariaDB databases{dry_run_label}')
 
 
     for database in databases:
     for database in databases:
-        dump_path = make_dump_path(config)
+        dump_path = make_dump_path(borgmatic_runtime_directory)
         extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
         extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
         dump_database_names = database_names_to_dump(
         dump_database_names = database_names_to_dump(
             database, extra_environment, log_prefix, dry_run
             database, extra_environment, log_prefix, dry_run
@@ -185,30 +181,36 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
     return [process for process in processes if process]
     return [process for process in processes if process]
 
 
 
 
-def remove_data_source_dumps(databases, config, log_prefix, dry_run):  # pragma: no cover
+def remove_data_source_dumps(
+    databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+):  # pragma: no cover
     '''
     '''
-    Remove all database dump files for this hook regardless of the given databases. Use the given
-    configuration dict to construct the destination path and the log prefix in any log entries. If
-    this is a dry run, then don't actually remove anything.
+    Remove all database dump files for this hook regardless of the given databases. Use the
+    borgmatic_runtime_directory to construct the destination path and the log prefix in any log
+    entries. If this is a dry run, then don't actually remove anything.
     '''
     '''
-    dump.remove_data_source_dumps(make_dump_path(config), 'MariaDB', log_prefix, dry_run)
+    dump.remove_data_source_dumps(
+        make_dump_path(borgmatic_runtime_directory), 'MariaDB', log_prefix, dry_run
+    )
 
 
 
 
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None):  # pragma: no cover
+def make_data_source_dump_patterns(
+    databases, config, log_prefix, borgmatic_runtime_directory, name=None
+):  # pragma: no cover
     '''
     '''
-    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, and a
-    database name to match, return the corresponding glob patterns to match the database dump in an
-    archive.
+    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+    borgmatic runtime directory, and a database name to match, return the corresponding glob
+    patterns to match the database dump in an archive.
     '''
     '''
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, 'borgmatic'), name, hostname='*'
+            make_dump_path(borgmatic_runtime_directory), name, hostname='*'
         ),
         ),
-        dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+            make_dump_path(borgmatic_source_directory), name, hostname='*'
         ),
         ),
     )
     )
 
 

+ 25 - 23
borgmatic/hooks/mongodb.py

@@ -8,15 +8,11 @@ from borgmatic.hooks import dump
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def make_dump_path(config, base_directory=None):  # pragma: no cover
+def make_dump_path(base_directory):  # pragma: no cover
     '''
     '''
-    Given a configuration dict and an optional base directory, make the corresponding dump path. If
-    a base directory isn't provided, use the borgmatic runtime directory.
+    Given a base directory, make the corresponding dump path.
     '''
     '''
-    return dump.make_data_source_dump_path(
-        base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
-        'mongodb_databases',
-    )
+    return dump.make_data_source_dump_path(base_directory, 'mongodb_databases')
 
 
 
 
 def use_streaming(databases, config, log_prefix):
 def use_streaming(databases, config, log_prefix):
@@ -27,11 +23,11 @@ def use_streaming(databases, config, log_prefix):
     return any(database.get('format') != 'directory' for database in databases)
     return any(database.get('format') != 'directory' for database in databases)
 
 
 
 
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
     '''
     '''
     Dump the given MongoDB databases to a named pipe. The databases are supplied as a sequence of
     Dump the given MongoDB databases to a named pipe. The databases are supplied as a sequence of
-    dicts, one dict describing each database as per the configuration schema. Use the configuration
-    dict to construct the destination path and the given log prefix in any log entries.
+    dicts, one dict describing each database as per the configuration schema. Use the borgmatic
+    runtime directory to construct the destination path and the given log prefix in any log entries.
 
 
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -44,7 +40,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
     for database in databases:
     for database in databases:
         name = database['name']
         name = database['name']
         dump_filename = dump.make_data_source_dump_filename(
         dump_filename = dump.make_data_source_dump_filename(
-            make_dump_path(config), name, database.get('hostname')
+            make_dump_path(borgmatic_runtime_directory), name, database.get('hostname')
         )
         )
         dump_format = database.get('format', 'archive')
         dump_format = database.get('format', 'archive')
 
 
@@ -94,30 +90,36 @@ def build_dump_command(database, dump_filename, dump_format):
     )
     )
 
 
 
 
-def remove_data_source_dumps(databases, config, log_prefix, dry_run):  # pragma: no cover
+def remove_data_source_dumps(
+    databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+):  # pragma: no cover
     '''
     '''
-    Remove all database dump files for this hook regardless of the given databases. Use the log
-    prefix in any log entries. Use the given configuration dict to construct the destination path.
-    If this is a dry run, then don't actually remove anything.
+    Remove all database dump files for this hook regardless of the given databases. Use the
+    borgmatic_runtime_directory to construct the destination path and the log prefix in any log
+    entries. If this is a dry run, then don't actually remove anything.
     '''
     '''
-    dump.remove_data_source_dumps(make_dump_path(config), 'MongoDB', log_prefix, dry_run)
+    dump.remove_data_source_dumps(
+        make_dump_path(borgmatic_runtime_directory), 'MongoDB', log_prefix, dry_run
+    )
 
 
 
 
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None):  # pragma: no cover
+def make_data_source_dump_patterns(
+    databases, config, log_prefix, borgmatic_runtime_directory, name=None
+):  # pragma: no cover
     '''
     '''
-    Given a sequence of database configurations dicts, a configuration dict, a prefix to log with,
-    and a database name to match, return the corresponding glob patterns to match the database dump
-    in an archive.
+    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+    borgmatic runtime directory, and a database name to match, return the corresponding glob
+    patterns to match the database dump in an archive.
     '''
     '''
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, 'borgmatic'), name, hostname='*'
+            make_dump_path(borgmatic_runtime_directory), name, hostname='*'
         ),
         ),
-        dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+            make_dump_path(borgmatic_source_directory), name, hostname='*'
         ),
         ),
     )
     )
 
 

+ 25 - 22
borgmatic/hooks/mysql.py

@@ -14,15 +14,11 @@ from borgmatic.hooks import dump
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def make_dump_path(config, base_directory=None):  # pragma: no cover
+def make_dump_path(base_directory):  # pragma: no cover
     '''
     '''
-    Given a configuration dict and an optional base directory, make the corresponding dump path. If
-    a base directory isn't provided, use the borgmatic runtime directory.
+    Given a base directory, make the corresponding dump path.
     '''
     '''
-    return dump.make_data_source_dump_path(
-        base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
-        'mysql_databases',
-    )
+    return dump.make_data_source_dump_path(base_directory, 'mysql_databases')
 
 
 
 
 SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
 SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
@@ -125,11 +121,12 @@ def use_streaming(databases, config, log_prefix):
     return any(databases)
     return any(databases)
 
 
 
 
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
     '''
     '''
     Dump the given MySQL/MariaDB databases to a named pipe. The databases are supplied as a sequence
     Dump the given MySQL/MariaDB databases to a named pipe. The databases are supplied as a sequence
     of dicts, one dict describing each database as per the configuration schema. Use the given
     of dicts, one dict describing each database as per the configuration schema. Use the given
-    configuration dict to construct the destination path and the given log prefix in any log entries.
+    borgmatic runtime directory to construct the destination path and the given log prefix in any
+    log entries.
 
 
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -140,7 +137,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
     logger.info(f'{log_prefix}: Dumping MySQL databases{dry_run_label}')
     logger.info(f'{log_prefix}: Dumping MySQL databases{dry_run_label}')
 
 
     for database in databases:
     for database in databases:
-        dump_path = make_dump_path(config)
+        dump_path = make_dump_path(borgmatic_runtime_directory)
         extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
         extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
         dump_database_names = database_names_to_dump(
         dump_database_names = database_names_to_dump(
             database, extra_environment, log_prefix, dry_run
             database, extra_environment, log_prefix, dry_run
@@ -183,30 +180,36 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
     return [process for process in processes if process]
     return [process for process in processes if process]
 
 
 
 
-def remove_data_source_dumps(databases, config, log_prefix, dry_run):  # pragma: no cover
+def remove_data_source_dumps(
+    databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+):  # pragma: no cover
     '''
     '''
-    Remove all database dump files for this hook regardless of the given databases. Use the given
-    configuration dict to construct the destination path and the log prefix in any log entries. If
-    this is a dry run, then don't actually remove anything.
+    Remove all database dump files for this hook regardless of the given databases. Use the
+    borgmatic runtime directory to construct the destination path and the log prefix in any log
+    entries. If this is a dry run, then don't actually remove anything.
     '''
     '''
-    dump.remove_data_source_dumps(make_dump_path(config), 'MySQL', log_prefix, dry_run)
+    dump.remove_data_source_dumps(
+        make_dump_path(borgmatic_runtime_directory), 'MySQL', log_prefix, dry_run
+    )
 
 
 
 
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None):  # pragma: no cover
+def make_data_source_dump_patterns(
+    databases, config, log_prefix, borgmatic_runtime_directory, name=None
+):  # pragma: no cover
     '''
     '''
-    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, and a
-    database name to match, return the corresponding glob patterns to match the database dump in an
-    archive.
+    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+    borgmatic runtime directory, and a database name to match, return the corresponding glob
+    patterns to match the database dump in an archive.
     '''
     '''
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, 'borgmatic'), name, hostname='*'
+            make_dump_path(borgmatic_runtime_directory), name, hostname='*'
         ),
         ),
-        dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+            make_dump_path(borgmatic_source_directory), name, hostname='*'
         ),
         ),
     )
     )
 
 

+ 25 - 23
borgmatic/hooks/postgresql.py

@@ -16,15 +16,11 @@ from borgmatic.hooks import dump
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def make_dump_path(config, base_directory=None):  # pragma: no cover
+def make_dump_path(base_directory):  # pragma: no cover
     '''
     '''
-    Given a configuration dict and an optional base directory, make the corresponding dump path. If
-    a base directory isn't provided, use the borgmatic runtime directory.
+    Given a base directory, make the corresponding dump path.
     '''
     '''
-    return dump.make_data_source_dump_path(
-        base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
-        'postgresql_databases',
-    )
+    return dump.make_data_source_dump_path(base_directory, 'postgresql_databases')
 
 
 
 
 def make_extra_environment(database, restore_connection_params=None):
 def make_extra_environment(database, restore_connection_params=None):
@@ -108,12 +104,12 @@ def use_streaming(databases, config, log_prefix):
     return any(database.get('format') != 'directory' for database in databases)
     return any(database.get('format') != 'directory' for database in databases)
 
 
 
 
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
     '''
     '''
     Dump the given PostgreSQL databases to a named pipe. The databases are supplied as a sequence of
     Dump the given PostgreSQL databases to a named pipe. The databases are supplied as a sequence of
     dicts, one dict describing each database as per the configuration schema. Use the given
     dicts, one dict describing each database as per the configuration schema. Use the given
-    configuration dict to construct the destination path and the given log prefix in any log
-    entries.
+    borgmatic runtime directory to construct the destination path and the given log prefix in any
+    log entries.
 
 
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -127,7 +123,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
 
 
     for database in databases:
     for database in databases:
         extra_environment = make_extra_environment(database)
         extra_environment = make_extra_environment(database)
-        dump_path = make_dump_path(config)
+        dump_path = make_dump_path(borgmatic_runtime_directory)
         dump_database_names = database_names_to_dump(
         dump_database_names = database_names_to_dump(
             database, extra_environment, log_prefix, dry_run
             database, extra_environment, log_prefix, dry_run
         )
         )
@@ -210,30 +206,36 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
     return processes
     return processes
 
 
 
 
-def remove_data_source_dumps(databases, config, log_prefix, dry_run):  # pragma: no cover
+def remove_data_source_dumps(
+    databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+):  # pragma: no cover
     '''
     '''
-    Remove all database dump files for this hook regardless of the given databases. Use the given
-    configuration dict to construct the destination path and the log prefix in any log entries. If
-    this is a dry run, then don't actually remove anything.
+    Remove all database dump files for this hook regardless of the given databases. Use the
+    borgmatic runtime directory to construct the destination path and the log prefix in any log
+    entries. If this is a dry run, then don't actually remove anything.
     '''
     '''
-    dump.remove_data_source_dumps(make_dump_path(config), 'PostgreSQL', log_prefix, dry_run)
+    dump.remove_data_source_dumps(
+        make_dump_path(borgmatic_runtime_directory), 'PostgreSQL', log_prefix, dry_run
+    )
 
 
 
 
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None):  # pragma: no cover
+def make_data_source_dump_patterns(
+    databases, config, log_prefix, borgmatic_runtime_directory, name=None
+):  # pragma: no cover
     '''
     '''
-    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, and a
-    database name to match, return the corresponding glob patterns to match the database dump in an
-    archive.
+    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+    borgmatic runtime directory, and a database name to match, return the corresponding glob
+    patterns to match the database dump in an archive.
     '''
     '''
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, 'borgmatic'), name, hostname='*'
+            make_dump_path(borgmatic_runtime_directory), name, hostname='*'
         ),
         ),
-        dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+            make_dump_path(borgmatic_source_directory), name, hostname='*'
         ),
         ),
     )
     )
 
 

+ 25 - 23
borgmatic/hooks/sqlite.py

@@ -9,15 +9,11 @@ from borgmatic.hooks import dump
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
-def make_dump_path(config, base_directory=None):  # pragma: no cover
+def make_dump_path(base_directory):  # pragma: no cover
     '''
     '''
-    Given a configuration dict and an optional base directory, make the corresponding dump path. If
-    a base directory isn't provided, use the borgmatic runtime directory.
+    Given a base directory, make the corresponding dump path.
     '''
     '''
-    return dump.make_data_source_dump_path(
-        base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
-        'sqlite_databases',
-    )
+    return dump.make_data_source_dump_path(base_directory, 'sqlite_databases')
 
 
 
 
 def use_streaming(databases, config, log_prefix):
 def use_streaming(databases, config, log_prefix):
@@ -28,11 +24,11 @@ def use_streaming(databases, config, log_prefix):
     return any(databases)
     return any(databases)
 
 
 
 
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
     '''
     '''
     Dump the given SQLite databases to a named pipe. The databases are supplied as a sequence of
     Dump the given SQLite databases to a named pipe. The databases are supplied as a sequence of
-    configuration dicts, as per the configuration schema. Use the given configuration dict to
-    construct the destination path and the given log prefix in any log entries.
+    configuration dicts, as per the configuration schema. Use the given borgmatic runtime directory
+    to construct the destination path and the given log prefix in any log entries.
 
 
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
     pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -52,7 +48,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
                 f'{log_prefix}: No SQLite database at {database_path}; an empty database will be created and dumped'
                 f'{log_prefix}: No SQLite database at {database_path}; an empty database will be created and dumped'
             )
             )
 
 
-        dump_path = make_dump_path(config)
+        dump_path = make_dump_path(borgmatic_runtime_directory)
         dump_filename = dump.make_data_source_dump_filename(dump_path, database['name'])
         dump_filename = dump.make_data_source_dump_filename(dump_path, database['name'])
 
 
         if os.path.exists(dump_filename):
         if os.path.exists(dump_filename):
@@ -80,30 +76,36 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
     return processes
     return processes
 
 
 
 
-def remove_data_source_dumps(databases, config, log_prefix, dry_run):  # pragma: no cover
+def remove_data_source_dumps(
+    databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+):  # pragma: no cover
     '''
     '''
-    Remove the given SQLite database dumps from the filesystem. The databases are supplied as a
-    sequence of configuration dicts, as per the configuration schema. Use the given configuration
-    dict to construct the destination path and the given log prefix in any log entries. If this is a
-    dry run, then don't actually remove anything.
+    Remove all database dump files for this hook regardless of the given databases. Use the
+    borgmatic runtime directory to construct the destination path and the log prefix in any log
+    entries. If this is a dry run, then don't actually remove anything.
     '''
     '''
-    dump.remove_data_source_dumps(make_dump_path(config), 'SQLite', log_prefix, dry_run)
+    dump.remove_data_source_dumps(
+        make_dump_path(borgmatic_runtime_directory), 'SQLite', log_prefix, dry_run
+    )
 
 
 
 
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None):  # pragma: no cover
+def make_data_source_dump_patterns(
+    databases, config, log_prefix, borgmatic_runtime_directory, name=None
+):  # pragma: no cover
     '''
     '''
-    Make a pattern that matches the given SQLite databases. The databases are supplied as a sequence
-    of configuration dicts, as per the configuration schema.
+    Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+    borgmatic runtime directory, and a database name to match, return the corresponding glob
+    patterns to match the database dump in an archive.
     '''
     '''
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
     borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
 
 
     return (
     return (
+        dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, 'borgmatic'), name, hostname='*'
+            make_dump_path(borgmatic_runtime_directory), name, hostname='*'
         ),
         ),
-        dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
         dump.make_data_source_dump_filename(
         dump.make_data_source_dump_filename(
-            make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+            make_dump_path(borgmatic_source_directory), name, hostname='*'
         ),
         ),
     )
     )
 
 

+ 2 - 0
sample/systemd/borgmatic.service

@@ -9,6 +9,8 @@ Documentation=https://torsion.org/borgmatic/
 
 
 [Service]
 [Service]
 Type=oneshot
 Type=oneshot
+RuntimeDirectory=borgmatic
+StateDirectory=borgmatic
 
 
 # Load single encrypted credential.
 # Load single encrypted credential.
 LoadCredentialEncrypted=borgmatic.pw
 LoadCredentialEncrypted=borgmatic.pw

+ 70 - 15
tests/unit/actions/config/test_bootstrap.py

@@ -16,9 +16,6 @@ def test_get_config_paths_returns_list_of_config_paths():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/source')
     ).and_return('/source')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/runtime')
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     bootstrap_arguments = flexmock(
     bootstrap_arguments = flexmock(
         repository='repo',
         repository='repo',
@@ -33,6 +30,9 @@ def test_get_config_paths_returns_list_of_config_paths():
         dry_run=False,
         dry_run=False,
     )
     )
     local_borg_version = flexmock()
     local_borg_version = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     extract_process = flexmock(
     extract_process = flexmock(
         stdout=flexmock(
         stdout=flexmock(
             read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
             read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
@@ -47,13 +47,58 @@ def test_get_config_paths_returns_list_of_config_paths():
     ) == ['/borgmatic/config.yaml']
     ) == ['/borgmatic/config.yaml']
 
 
 
 
-def test_get_config_paths_translates_ssh_command_argument_to_config():
+def test_get_config_paths_probes_for_manifest():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/source')
     ).and_return('/source')
+    flexmock(module).should_receive('make_bootstrap_config').and_return({})
+    bootstrap_arguments = flexmock(
+        repository='repo',
+        archive='archive',
+        ssh_command=None,
+        local_path='borg7',
+        remote_path='borg8',
+        borgmatic_source_directory=None,
+        user_runtime_directory=None,
+    )
+    global_arguments = flexmock(
+        dry_run=False,
+    )
+    local_borg_version = flexmock()
+    borgmatic_runtime_directory = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        borgmatic_runtime_directory,
+    )
+    flexmock(module.os.path).should_receive('join').with_args(
+        'borgmatic', 'bootstrap', 'manifest.json'
+    ).and_return(flexmock()).once()
+    flexmock(module.os.path).should_receive('join').with_args(
+        borgmatic_runtime_directory, 'bootstrap', 'manifest.json'
+    ).and_return(flexmock()).once()
+    flexmock(module.os.path).should_receive('join').with_args(
+        '/source', 'bootstrap', 'manifest.json'
+    ).and_return(flexmock()).once()
+    manifest_missing_extract_process = flexmock(
+        stdout=flexmock(read=lambda: None),
+    )
+    manifest_found_extract_process = flexmock(
+        stdout=flexmock(
+            read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
+        ),
+    )
+    flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
+        manifest_missing_extract_process
+    ).and_return(manifest_missing_extract_process).and_return(manifest_found_extract_process)
+
+    assert module.get_config_paths(
+        'archive', bootstrap_arguments, global_arguments, local_borg_version
+    ) == ['/borgmatic/config.yaml']
+
+
+def test_get_config_paths_translates_ssh_command_argument_to_config():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/runtime')
+        'get_borgmatic_source_directory'
+    ).and_return('/source')
     config = flexmock()
     config = flexmock()
     flexmock(module).should_receive('make_bootstrap_config').and_return(config)
     flexmock(module).should_receive('make_bootstrap_config').and_return(config)
     bootstrap_arguments = flexmock(
     bootstrap_arguments = flexmock(
@@ -69,6 +114,9 @@ def test_get_config_paths_translates_ssh_command_argument_to_config():
         dry_run=False,
         dry_run=False,
     )
     )
     local_borg_version = flexmock()
     local_borg_version = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     extract_process = flexmock(
     extract_process = flexmock(
         stdout=flexmock(
         stdout=flexmock(
             read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
             read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
@@ -96,9 +144,6 @@ def test_get_config_paths_with_missing_manifest_raises_value_error():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/source')
     ).and_return('/source')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/runtime')
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     bootstrap_arguments = flexmock(
     bootstrap_arguments = flexmock(
         repository='repo',
         repository='repo',
@@ -113,6 +158,10 @@ def test_get_config_paths_with_missing_manifest_raises_value_error():
         dry_run=False,
         dry_run=False,
     )
     )
     local_borg_version = flexmock()
     local_borg_version = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
+    flexmock(module.os.path).should_receive('join').and_return(flexmock())
     extract_process = flexmock(stdout=flexmock(read=lambda: ''))
     extract_process = flexmock(stdout=flexmock(read=lambda: ''))
     flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
     flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
         extract_process
         extract_process
@@ -128,9 +177,6 @@ def test_get_config_paths_with_broken_json_raises_value_error():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/source')
     ).and_return('/source')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/runtime')
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     bootstrap_arguments = flexmock(
     bootstrap_arguments = flexmock(
         repository='repo',
         repository='repo',
@@ -145,6 +191,9 @@ def test_get_config_paths_with_broken_json_raises_value_error():
         dry_run=False,
         dry_run=False,
     )
     )
     local_borg_version = flexmock()
     local_borg_version = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     extract_process = flexmock(
     extract_process = flexmock(
         stdout=flexmock(read=lambda: '{"config_paths": ["/oops'),
         stdout=flexmock(read=lambda: '{"config_paths": ["/oops'),
     )
     )
@@ -162,9 +211,6 @@ def test_get_config_paths_with_json_missing_key_raises_value_error():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/source')
     ).and_return('/source')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/runtime')
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     flexmock(module).should_receive('make_bootstrap_config').and_return({})
     bootstrap_arguments = flexmock(
     bootstrap_arguments = flexmock(
         repository='repo',
         repository='repo',
@@ -179,6 +225,9 @@ def test_get_config_paths_with_json_missing_key_raises_value_error():
         dry_run=False,
         dry_run=False,
     )
     )
     local_borg_version = flexmock()
     local_borg_version = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     extract_process = flexmock(
     extract_process = flexmock(
         stdout=flexmock(read=lambda: '{}'),
         stdout=flexmock(read=lambda: '{}'),
     )
     )
@@ -210,6 +259,9 @@ def test_run_bootstrap_does_not_raise():
         dry_run=False,
         dry_run=False,
     )
     )
     local_borg_version = flexmock()
     local_borg_version = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     extract_process = flexmock(
     extract_process = flexmock(
         stdout=flexmock(
         stdout=flexmock(
             read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',
             read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',
@@ -244,6 +296,9 @@ def test_run_bootstrap_translates_ssh_command_argument_to_config():
         dry_run=False,
         dry_run=False,
     )
     )
     local_borg_version = flexmock()
     local_borg_version = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     extract_process = flexmock(
     extract_process = flexmock(
         stdout=flexmock(
         stdout=flexmock(
             read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',
             read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',

+ 13 - 12
tests/unit/actions/test_check.py

@@ -713,9 +713,6 @@ def test_collect_spot_check_archive_paths_excludes_directories():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/home/user/.borgmatic')
     ).and_return('/home/user/.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/1001/borgmatic')
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
         (
         (
             'f /etc/path',
             'f /etc/path',
@@ -732,6 +729,7 @@ def test_collect_spot_check_archive_paths_excludes_directories():
         global_arguments=flexmock(),
         global_arguments=flexmock(),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/user/1001/borgmatic',
     ) == ('/etc/path', '/etc/other')
     ) == ('/etc/path', '/etc/other')
 
 
 
 
@@ -739,9 +737,6 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/root/.borgmatic')
     ).and_return('/root/.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
         (
         (
             'f /etc/path',
             'f /etc/path',
@@ -757,6 +752,7 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
         global_arguments=flexmock(),
         global_arguments=flexmock(),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/user/0/borgmatic',
     ) == ('/etc/path',)
     ) == ('/etc/path',)
 
 
 
 
@@ -764,9 +760,6 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_source_dire
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/root/.borgmatic')
     ).and_return('/root/.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
         (
         (
             'f /etc/path',
             'f /etc/path',
@@ -782,6 +775,7 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_source_dire
         global_arguments=flexmock(),
         global_arguments=flexmock(),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/user/0/borgmatic',
     ) == ('/etc/path',)
     ) == ('/etc/path',)
 
 
 
 
@@ -789,9 +783,6 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/root.borgmatic')
     ).and_return('/root.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
     flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
         (
         (
             'f /etc/path',
             'f /etc/path',
@@ -807,6 +798,7 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
         global_arguments=flexmock(),
         global_arguments=flexmock(),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/user/0/borgmatic',
     ) == ('/etc/path',)
     ) == ('/etc/path',)
 
 
 
 
@@ -1149,6 +1141,7 @@ def test_spot_check_without_spot_configuration_errors():
             global_arguments=flexmock(),
             global_arguments=flexmock(),
             local_path=flexmock(),
             local_path=flexmock(),
             remote_path=flexmock(),
             remote_path=flexmock(),
+            borgmatic_runtime_directory='/run/borgmatic',
         )
         )
 
 
 
 
@@ -1161,6 +1154,7 @@ def test_spot_check_without_any_configuration_errors():
             global_arguments=flexmock(),
             global_arguments=flexmock(),
             local_path=flexmock(),
             local_path=flexmock(),
             remote_path=flexmock(),
             remote_path=flexmock(),
+            borgmatic_runtime_directory='/run/borgmatic',
         )
         )
 
 
 
 
@@ -1181,6 +1175,7 @@ def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentag
             global_arguments=flexmock(),
             global_arguments=flexmock(),
             local_path=flexmock(),
             local_path=flexmock(),
             remote_path=flexmock(),
             remote_path=flexmock(),
+            borgmatic_runtime_directory='/run/borgmatic',
         )
         )
 
 
 
 
@@ -1212,6 +1207,7 @@ def test_spot_check_with_count_delta_greater_than_count_tolerance_percentage_err
             global_arguments=flexmock(),
             global_arguments=flexmock(),
             local_path=flexmock(),
             local_path=flexmock(),
             remote_path=flexmock(),
             remote_path=flexmock(),
+            borgmatic_runtime_directory='/run/borgmatic',
         )
         )
 
 
 
 
@@ -1244,6 +1240,7 @@ def test_spot_check_with_failing_percentage_greater_than_data_tolerance_percenta
             global_arguments=flexmock(),
             global_arguments=flexmock(),
             local_path=flexmock(),
             local_path=flexmock(),
             remote_path=flexmock(),
             remote_path=flexmock(),
+            borgmatic_runtime_directory='/run/borgmatic',
         )
         )
 
 
 
 
@@ -1275,6 +1272,7 @@ def test_spot_check_with_high_enough_tolerances_does_not_raise():
         global_arguments=flexmock(),
         global_arguments=flexmock(),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
@@ -1362,6 +1360,9 @@ def test_run_check_runs_configured_spot_check():
     flexmock(module).should_receive('make_archives_check_id').and_return(None)
     flexmock(module).should_receive('make_archives_check_id').and_return(None)
     flexmock(module).should_receive('filter_checks_on_frequency').and_return({'spot'})
     flexmock(module).should_receive('filter_checks_on_frequency').and_return({'spot'})
     flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
     flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     flexmock(module.borgmatic.actions.check).should_receive('spot_check').once()
     flexmock(module.borgmatic.actions.check).should_receive('spot_check').once()
     flexmock(module).should_receive('make_check_time_path')
     flexmock(module).should_receive('make_check_time_path')
     flexmock(module).should_receive('write_check_time')
     flexmock(module).should_receive('write_check_time')

+ 24 - 15
tests/unit/actions/test_create.py

@@ -8,6 +8,9 @@ from borgmatic.actions import create as module
 def test_run_create_executes_and_calls_hooks_for_configured_repository():
 def test_run_create_executes_and_calls_hooks_for_configured_repository():
     flexmock(module.logger).answer = lambda message: None
     flexmock(module.logger).answer = lambda message: None
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
     flexmock(module).should_receive('create_borgmatic_manifest').once()
     flexmock(module).should_receive('create_borgmatic_manifest').once()
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
@@ -44,6 +47,9 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository():
 def test_run_create_with_store_config_files_false_does_not_create_borgmatic_manifest():
 def test_run_create_with_store_config_files_false_does_not_create_borgmatic_manifest():
     flexmock(module.logger).answer = lambda message: None
     flexmock(module.logger).answer = lambda message: None
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
     flexmock(module).should_receive('create_borgmatic_manifest').never()
     flexmock(module).should_receive('create_borgmatic_manifest').never()
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
@@ -82,6 +88,9 @@ def test_run_create_runs_with_selected_repository():
     flexmock(module.borgmatic.config.validate).should_receive(
     flexmock(module.borgmatic.config.validate).should_receive(
         'repositories_match'
         'repositories_match'
     ).once().and_return(True)
     ).once().and_return(True)
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
     flexmock(module).should_receive('create_borgmatic_manifest').once()
     flexmock(module).should_receive('create_borgmatic_manifest').once()
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
     flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
@@ -120,6 +129,7 @@ def test_run_create_bails_if_repository_does_not_match():
     flexmock(module.borgmatic.config.validate).should_receive(
     flexmock(module.borgmatic.config.validate).should_receive(
         'repositories_match'
         'repositories_match'
     ).once().and_return(False)
     ).once().and_return(False)
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
     flexmock(module).should_receive('create_borgmatic_manifest').never()
     flexmock(module).should_receive('create_borgmatic_manifest').never()
     create_arguments = flexmock(
     create_arguments = flexmock(
@@ -153,6 +163,9 @@ def test_run_create_produces_json():
     flexmock(module.borgmatic.config.validate).should_receive(
     flexmock(module.borgmatic.config.validate).should_receive(
         'repositories_match'
         'repositories_match'
     ).once().and_return(True)
     ).once().and_return(True)
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once().and_return(
     flexmock(module.borgmatic.borg.create).should_receive('create_archive').once().and_return(
         flexmock()
         flexmock()
     )
     )
@@ -191,18 +204,15 @@ def test_run_create_produces_json():
 
 
 
 
 def test_create_borgmatic_manifest_creates_manifest_file():
 def test_create_borgmatic_manifest_creates_manifest_file():
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.os.path).should_receive('join').with_args(
     flexmock(module.os.path).should_receive('join').with_args(
-        '/run/user/0/borgmatic', 'bootstrap', 'manifest.json'
-    ).and_return('/run/user/0/borgmatic/bootstrap/manifest.json')
+        '/run/borgmatic', 'bootstrap', 'manifest.json'
+    ).and_return('/run/borgmatic/bootstrap/manifest.json')
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os).should_receive('makedirs').and_return(True)
     flexmock(module.os).should_receive('makedirs').and_return(True)
 
 
     flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
     flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
     flexmock(sys.modules['builtins']).should_receive('open').with_args(
     flexmock(sys.modules['builtins']).should_receive('open').with_args(
-        '/run/user/0/borgmatic/bootstrap/manifest.json', 'w'
+        '/run/borgmatic/bootstrap/manifest.json', 'w'
     ).and_return(
     ).and_return(
         flexmock(
         flexmock(
             __enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
             __enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
@@ -211,22 +221,19 @@ def test_create_borgmatic_manifest_creates_manifest_file():
     )
     )
     flexmock(module.json).should_receive('dump').and_return(True).once()
     flexmock(module.json).should_receive('dump').and_return(True).once()
 
 
-    module.create_borgmatic_manifest({}, 'test.yaml', False)
+    module.create_borgmatic_manifest({}, 'test.yaml', '/run/borgmatic', False)
 
 
 
 
 def test_create_borgmatic_manifest_creates_manifest_file_with_custom_borgmatic_runtime_directory():
 def test_create_borgmatic_manifest_creates_manifest_file_with_custom_borgmatic_runtime_directory():
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/borgmatic')
     flexmock(module.os.path).should_receive('join').with_args(
     flexmock(module.os.path).should_receive('join').with_args(
-        '/borgmatic', 'bootstrap', 'manifest.json'
-    ).and_return('/borgmatic/bootstrap/manifest.json')
+        '/run/borgmatic', 'bootstrap', 'manifest.json'
+    ).and_return('/run/borgmatic/bootstrap/manifest.json')
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os).should_receive('makedirs').and_return(True)
     flexmock(module.os).should_receive('makedirs').and_return(True)
 
 
     flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
     flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
     flexmock(sys.modules['builtins']).should_receive('open').with_args(
     flexmock(sys.modules['builtins']).should_receive('open').with_args(
-        '/borgmatic/bootstrap/manifest.json', 'w'
+        '/run/borgmatic/bootstrap/manifest.json', 'w'
     ).and_return(
     ).and_return(
         flexmock(
         flexmock(
             __enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
             __enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
@@ -236,9 +243,11 @@ def test_create_borgmatic_manifest_creates_manifest_file_with_custom_borgmatic_r
     flexmock(module.json).should_receive('dump').and_return(True).once()
     flexmock(module.json).should_receive('dump').and_return(True).once()
 
 
     module.create_borgmatic_manifest(
     module.create_borgmatic_manifest(
-        {'borgmatic_runtime_directory': '/borgmatic'}, 'test.yaml', False
+        {'borgmatic_runtime_directory': '/borgmatic'}, 'test.yaml', '/run/borgmatic', False
     )
     )
 
 
 
 
 def test_create_borgmatic_manifest_does_not_create_manifest_file_on_dry_run():
 def test_create_borgmatic_manifest_does_not_create_manifest_file_on_dry_run():
-    module.create_borgmatic_manifest({}, 'test.yaml', True)
+    flexmock(module.json).should_receive('dump').never()
+
+    module.create_borgmatic_manifest({}, 'test.yaml', '/run/borgmatic', True)

+ 44 - 28
tests/unit/actions/test_restore.py

@@ -87,11 +87,8 @@ def test_strip_path_prefix_from_extracted_dump_destination_renames_first_matchin
 
 
 def test_restore_single_data_source_extracts_and_restores_single_file_dump():
 def test_restore_single_data_source_extracts_and_restores_single_file_dump():
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
-        'make_data_source_dump_patterns', object, object, object, object
+        'make_data_source_dump_patterns', object, object, object, object, object
     ).and_return({'postgresql': flexmock()})
     ).and_return({'postgresql': flexmock()})
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.tempfile).should_receive('mkdtemp').never()
     flexmock(module.tempfile).should_receive('mkdtemp').never()
     flexmock(module.borgmatic.hooks.dump).should_receive(
     flexmock(module.borgmatic.hooks.dump).should_receive(
         'convert_glob_patterns_to_borg_pattern'
         'convert_glob_patterns_to_borg_pattern'
@@ -123,16 +120,14 @@ def test_restore_single_data_source_extracts_and_restores_single_file_dump():
         hook_name='postgresql',
         hook_name='postgresql',
         data_source={'name': 'test', 'format': 'plain'},
         data_source={'name': 'test', 'format': 'plain'},
         connection_params=flexmock(),
         connection_params=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
 def test_restore_single_data_source_extracts_and_restores_directory_dump():
 def test_restore_single_data_source_extracts_and_restores_directory_dump():
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
-        'make_data_source_dump_patterns', object, object, object, object
+        'make_data_source_dump_patterns', object, object, object, object, object
     ).and_return({'postgresql': flexmock()})
     ).and_return({'postgresql': flexmock()})
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
     flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
         '/run/user/0/borgmatic/tmp1234'
         '/run/user/0/borgmatic/tmp1234'
     )
     )
@@ -166,16 +161,14 @@ def test_restore_single_data_source_extracts_and_restores_directory_dump():
         hook_name='postgresql',
         hook_name='postgresql',
         data_source={'name': 'test', 'format': 'directory'},
         data_source={'name': 'test', 'format': 'directory'},
         connection_params=flexmock(),
         connection_params=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
 def test_restore_single_data_source_with_directory_dump_error_cleans_up_temporary_directory():
 def test_restore_single_data_source_with_directory_dump_error_cleans_up_temporary_directory():
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
-        'make_data_source_dump_patterns', object, object, object, object
+        'make_data_source_dump_patterns', object, object, object, object, object
     ).and_return({'postgresql': flexmock()})
     ).and_return({'postgresql': flexmock()})
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
     flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
         '/run/user/0/borgmatic/tmp1234'
         '/run/user/0/borgmatic/tmp1234'
     )
     )
@@ -210,16 +203,14 @@ def test_restore_single_data_source_with_directory_dump_error_cleans_up_temporar
             hook_name='postgresql',
             hook_name='postgresql',
             data_source={'name': 'test', 'format': 'directory'},
             data_source={'name': 'test', 'format': 'directory'},
             connection_params=flexmock(),
             connection_params=flexmock(),
+            borgmatic_runtime_directory='/run/borgmatic',
         )
         )
 
 
 
 
 def test_restore_single_data_source_with_directory_dump_and_dry_run_skips_directory_move_and_cleanup():
 def test_restore_single_data_source_with_directory_dump_and_dry_run_skips_directory_move_and_cleanup():
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
-        'make_data_source_dump_patterns', object, object, object, object
+        'make_data_source_dump_patterns', object, object, object, object, object
     ).and_return({'postgresql': flexmock()})
     ).and_return({'postgresql': flexmock()})
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
     flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
         '/run/user/0/borgmatic/tmp1234'
         '/run/user/0/borgmatic/tmp1234'
     )
     )
@@ -253,6 +244,7 @@ def test_restore_single_data_source_with_directory_dump_and_dry_run_skips_direct
         hook_name='postgresql',
         hook_name='postgresql',
         data_source={'name': 'test', 'format': 'directory'},
         data_source={'name': 'test', 'format': 'directory'},
         connection_params=flexmock(),
         connection_params=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
@@ -260,9 +252,6 @@ def test_collect_archive_data_source_names_parses_archive_paths():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/root/.borgmatic')
     ).and_return('/root/.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
         ''
         ''
     )
     )
@@ -282,6 +271,7 @@ def test_collect_archive_data_source_names_parses_archive_paths():
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
     assert archive_data_source_names == {
     assert archive_data_source_names == {
@@ -294,9 +284,6 @@ def test_collect_archive_data_source_names_parses_archive_paths_with_different_b
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/root/.borgmatic')
     ).and_return('/root/.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
         ''
         ''
     )
     )
@@ -317,6 +304,7 @@ def test_collect_archive_data_source_names_parses_archive_paths_with_different_b
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
     assert archive_data_source_names == {
     assert archive_data_source_names == {
@@ -329,9 +317,6 @@ def test_collect_archive_data_source_names_parses_directory_format_archive_paths
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/root/.borgmatic')
     ).and_return('/root/.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
         ''
         ''
     )
     )
@@ -350,6 +335,7 @@ def test_collect_archive_data_source_names_parses_directory_format_archive_paths
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
     assert archive_data_source_names == {
     assert archive_data_source_names == {
@@ -361,9 +347,6 @@ def test_collect_archive_data_source_names_skips_bad_archive_paths():
     flexmock(module.borgmatic.config.paths).should_receive(
     flexmock(module.borgmatic.config.paths).should_receive(
         'get_borgmatic_source_directory'
         'get_borgmatic_source_directory'
     ).and_return('/root/.borgmatic')
     ).and_return('/root/.borgmatic')
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/run/user/0/borgmatic')
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
     flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
         ''
         ''
     )
     )
@@ -384,6 +367,7 @@ def test_collect_archive_data_source_names_skips_bad_archive_paths():
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
     assert archive_data_source_names == {
     assert archive_data_source_names == {
@@ -481,6 +465,10 @@ def test_run_restore_restores_each_data_source():
     }
     }
 
 
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+    borgmatic_runtime_directory = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        borgmatic_runtime_directory
+    )
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
         flexmock()
         flexmock()
@@ -501,6 +489,7 @@ def test_run_restore_restores_each_data_source():
         hook_name='postgresql_databases',
         hook_name='postgresql_databases',
         data_source={'name': 'foo', 'schemas': None},
         data_source={'name': 'foo', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).once()
     ).once()
     flexmock(module).should_receive('restore_single_data_source').with_args(
     flexmock(module).should_receive('restore_single_data_source').with_args(
         repository=object,
         repository=object,
@@ -513,6 +502,7 @@ def test_run_restore_restores_each_data_source():
         hook_name='postgresql_databases',
         hook_name='postgresql_databases',
         data_source={'name': 'bar', 'schemas': None},
         data_source={'name': 'bar', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).once()
     ).once()
     flexmock(module).should_receive('ensure_data_sources_found')
     flexmock(module).should_receive('ensure_data_sources_found')
 
 
@@ -534,6 +524,7 @@ def test_run_restore_restores_each_data_source():
         global_arguments=flexmock(dry_run=False),
         global_arguments=flexmock(dry_run=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
@@ -541,6 +532,9 @@ def test_run_restore_bails_for_non_matching_repository():
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
         False
         False
     )
     )
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        flexmock()
+    )
     flexmock(module.borgmatic.hooks.dispatch).should_receive(
     flexmock(module.borgmatic.hooks.dispatch).should_receive(
         'call_hooks_even_if_unconfigured'
         'call_hooks_even_if_unconfigured'
     ).never()
     ).never()
@@ -554,6 +548,7 @@ def test_run_restore_bails_for_non_matching_repository():
         global_arguments=flexmock(dry_run=False),
         global_arguments=flexmock(dry_run=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
@@ -563,6 +558,10 @@ def test_run_restore_restores_data_source_configured_with_all_name():
     }
     }
 
 
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+    borgmatic_runtime_directory = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        borgmatic_runtime_directory
+    )
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
         flexmock()
         flexmock()
@@ -599,6 +598,7 @@ def test_run_restore_restores_data_source_configured_with_all_name():
         hook_name='postgresql_databases',
         hook_name='postgresql_databases',
         data_source={'name': 'foo', 'schemas': None},
         data_source={'name': 'foo', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).once()
     ).once()
     flexmock(module).should_receive('restore_single_data_source').with_args(
     flexmock(module).should_receive('restore_single_data_source').with_args(
         repository=object,
         repository=object,
@@ -611,6 +611,7 @@ def test_run_restore_restores_data_source_configured_with_all_name():
         hook_name='postgresql_databases',
         hook_name='postgresql_databases',
         data_source={'name': 'bar', 'schemas': None},
         data_source={'name': 'bar', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).once()
     ).once()
     flexmock(module).should_receive('ensure_data_sources_found')
     flexmock(module).should_receive('ensure_data_sources_found')
 
 
@@ -632,6 +633,7 @@ def test_run_restore_restores_data_source_configured_with_all_name():
         global_arguments=flexmock(dry_run=False),
         global_arguments=flexmock(dry_run=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
@@ -641,6 +643,10 @@ def test_run_restore_skips_missing_data_source():
     }
     }
 
 
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+    borgmatic_runtime_directory = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        borgmatic_runtime_directory
+    )
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
         flexmock()
         flexmock()
@@ -677,6 +683,7 @@ def test_run_restore_skips_missing_data_source():
         hook_name='postgresql_databases',
         hook_name='postgresql_databases',
         data_source={'name': 'foo', 'schemas': None},
         data_source={'name': 'foo', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).once()
     ).once()
     flexmock(module).should_receive('restore_single_data_source').with_args(
     flexmock(module).should_receive('restore_single_data_source').with_args(
         repository=object,
         repository=object,
@@ -689,6 +696,7 @@ def test_run_restore_skips_missing_data_source():
         hook_name='postgresql_databases',
         hook_name='postgresql_databases',
         data_source={'name': 'bar', 'schemas': None},
         data_source={'name': 'bar', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).never()
     ).never()
     flexmock(module).should_receive('ensure_data_sources_found')
     flexmock(module).should_receive('ensure_data_sources_found')
 
 
@@ -710,6 +718,7 @@ def test_run_restore_skips_missing_data_source():
         global_arguments=flexmock(dry_run=False),
         global_arguments=flexmock(dry_run=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )
 
 
 
 
@@ -720,6 +729,10 @@ def test_run_restore_restores_data_sources_from_different_hooks():
     }
     }
 
 
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
     flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+    borgmatic_runtime_directory = flexmock()
+    flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+        borgmatic_runtime_directory
+    )
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
     flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
         flexmock()
         flexmock()
@@ -749,6 +762,7 @@ def test_run_restore_restores_data_sources_from_different_hooks():
         hook_name='postgresql_databases',
         hook_name='postgresql_databases',
         data_source={'name': 'foo', 'schemas': None},
         data_source={'name': 'foo', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).once()
     ).once()
     flexmock(module).should_receive('restore_single_data_source').with_args(
     flexmock(module).should_receive('restore_single_data_source').with_args(
         repository=object,
         repository=object,
@@ -761,6 +775,7 @@ def test_run_restore_restores_data_sources_from_different_hooks():
         hook_name='mysql_databases',
         hook_name='mysql_databases',
         data_source={'name': 'bar', 'schemas': None},
         data_source={'name': 'bar', 'schemas': None},
         connection_params=object,
         connection_params=object,
+        borgmatic_runtime_directory=borgmatic_runtime_directory,
     ).once()
     ).once()
     flexmock(module).should_receive('ensure_data_sources_found')
     flexmock(module).should_receive('ensure_data_sources_found')
 
 
@@ -782,4 +797,5 @@ def test_run_restore_restores_data_sources_from_different_hooks():
         global_arguments=flexmock(dry_run=False),
         global_arguments=flexmock(dry_run=False),
         local_path=flexmock(),
         local_path=flexmock(),
         remote_path=flexmock(),
         remote_path=flexmock(),
+        borgmatic_runtime_directory='/run/borgmatic',
     )
     )

+ 17 - 51
tests/unit/borg/test_create.py

@@ -1453,9 +1453,6 @@ def test_create_archive_calls_borg_with_parameters():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1483,6 +1480,7 @@ def test_create_archive_calls_borg_with_parameters():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
     )
     )
 
 
 
 
@@ -1490,9 +1488,6 @@ def test_create_archive_calls_borg_with_environment():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1521,6 +1516,7 @@ def test_create_archive_calls_borg_with_environment():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
     )
     )
 
 
 
 
@@ -1528,9 +1524,6 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1559,6 +1552,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
     )
     )
 
 
 
 
@@ -1566,9 +1560,6 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1595,6 +1586,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         json=True,
         json=True,
     )
     )
 
 
@@ -1603,9 +1595,6 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1634,6 +1623,7 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
     )
     )
 
 
 
 
@@ -1641,9 +1631,6 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1670,6 +1657,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         json=True,
         json=True,
     )
     )
 
 
@@ -1680,9 +1668,6 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create', '--dry-run'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create', '--dry-run'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1711,6 +1696,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         stats=True,
         stats=True,
     )
     )
 
 
@@ -1719,9 +1705,6 @@ def test_create_archive_with_working_directory_calls_borg_with_working_directory
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1752,6 +1735,7 @@ def test_create_archive_with_working_directory_calls_borg_with_working_directory
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
     )
     )
 
 
 
 
@@ -1759,9 +1743,6 @@ def test_create_archive_with_exit_codes_calls_borg_using_them():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1791,6 +1772,7 @@ def test_create_archive_with_exit_codes_calls_borg_using_them():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
     )
     )
 
 
 
 
@@ -1798,9 +1780,6 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_ou
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1828,6 +1807,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_ou
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         stats=True,
         stats=True,
     )
     )
 
 
@@ -1836,9 +1816,6 @@ def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (
         (
@@ -1871,6 +1848,7 @@ def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         list_files=True,
         list_files=True,
     )
     )
 
 
@@ -1879,9 +1857,6 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1910,6 +1885,7 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         progress=True,
         progress=True,
     )
     )
 
 
@@ -1918,9 +1894,6 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1948,6 +1921,7 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         progress=True,
         progress=True,
     )
     )
 
 
@@ -1957,9 +1931,6 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     processes = flexmock()
     processes = flexmock()
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (
         (
@@ -2009,6 +1980,7 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         progress=True,
         progress=True,
         stream_processes=processes,
         stream_processes=processes,
     )
     )
@@ -2018,9 +1990,6 @@ def test_create_archive_with_json_calls_borg_with_json_flag():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -2046,6 +2015,7 @@ def test_create_archive_with_json_calls_borg_with_json_flag():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         json=True,
         json=True,
     )
     )
 
 
@@ -2056,9 +2026,6 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -2084,6 +2051,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
         json=True,
         json=True,
         stats=True,
         stats=True,
     )
     )
@@ -2095,9 +2063,6 @@ def test_create_archive_calls_borg_with_working_directory():
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
     flexmock(module).should_receive('expand_directories').and_return(())
     flexmock(module).should_receive('expand_directories').and_return(())
-    flexmock(module.borgmatic.config.paths).should_receive(
-        'get_borgmatic_runtime_directory'
-    ).and_return('/var/run/0/borgmatic')
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
     flexmock(module).should_receive('make_base_create_command').and_return(
     flexmock(module).should_receive('make_base_create_command').and_return(
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
         (('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -2128,6 +2093,7 @@ def test_create_archive_calls_borg_with_working_directory():
         config_paths=['/tmp/test.yaml'],
         config_paths=['/tmp/test.yaml'],
         local_borg_version='1.2.3',
         local_borg_version='1.2.3',
         global_arguments=flexmock(log_json=False),
         global_arguments=flexmock(log_json=False),
+        borgmatic_runtime_directory='/borgmatic/run',
     )
     )
 
 
 
 

+ 100 - 25
tests/unit/config/test_paths.py

@@ -33,53 +33,120 @@ def test_get_borgmatic_source_directory_without_config_option_uses_default():
     assert module.get_borgmatic_source_directory({}) == '~/.borgmatic'
     assert module.get_borgmatic_source_directory({}) == '~/.borgmatic'
 
 
 
 
-def test_get_borgmatic_runtime_directory_uses_config_option():
+def test_runtime_directory_uses_config_option():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+    config = {'user_runtime_directory': '/run', 'borgmatic_source_directory': '/nope'}
 
 
-    assert (
-        module.get_borgmatic_runtime_directory(
-            {'user_runtime_directory': '/tmp', 'borgmatic_source_directory': '/nope'}
-        )
-        == '/tmp/./borgmatic'
+    with module.Runtime_directory(config) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/./borgmatic'
+
+
+def test_runtime_directory_uses_config_option_without_adding_duplicate_borgmatic_subdirectory():
+    flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+    config = {'user_runtime_directory': '/run/borgmatic', 'borgmatic_source_directory': '/nope'}
+
+    with module.Runtime_directory(config) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/./borgmatic'
+
+
+def test_runtime_directory_falls_back_to_xdg_runtime_dir():
+    flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+    flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(
+        '/run'
     )
     )
 
 
+    with module.Runtime_directory({}) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/./borgmatic'
+
 
 
-def test_get_borgmatic_runtime_directory_falls_back_to_linux_environment_variable():
+def test_runtime_directory_falls_back_to_xdg_runtime_dir_without_adding_duplicate_borgmatic_subdirectory():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(
     flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(
-        '/tmp'
+        '/run/borgmatic'
+    )
+
+    with module.Runtime_directory({}) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/./borgmatic'
+
+
+def test_runtime_directory_falls_back_to_runtime_directory():
+    flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+    flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+    flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+        '/run'
     )
     )
 
 
-    assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
+    with module.Runtime_directory({}) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/./borgmatic'
 
 
 
 
-def test_get_borgmatic_runtime_directory_falls_back_to_macos_environment_variable():
+def test_runtime_directory_falls_back_to_runtime_directory_without_adding_duplicate_borgmatic_subdirectory():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
     flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
-    flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('/tmp')
+    flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+        '/run/borgmatic'
+    )
 
 
-    assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
+    with module.Runtime_directory({}) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/./borgmatic'
 
 
 
 
-def test_get_borgmatic_runtime_directory_falls_back_to_other_environment_variable():
+def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_that_get_cleaned_up():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
     flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+    flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+        None
+    )
+    flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('/run')
+    temporary_directory = flexmock(name='/run/borgmatic-1234')
+    temporary_directory.should_receive('cleanup').once()
+    flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
+        prefix='borgmatic', dir='/run'
+    ).and_return(temporary_directory)
+
+    with module.Runtime_directory({}) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
+
+
+def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_that_get_cleaned_up():
+    flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+    flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+    flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+        None
+    )
     flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
     flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
-    flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('/tmp')
+    flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('/run')
+    temporary_directory = flexmock(name='/run/borgmatic-1234')
+    temporary_directory.should_receive('cleanup').once()
+    flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
+        prefix='borgmatic', dir='/run'
+    ).and_return(temporary_directory)
 
 
-    assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
+    with module.Runtime_directory({}) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
 
 
 
 
-def test_get_borgmatic_runtime_directory_defaults_to_hard_coded_path():
+def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_subdirectory_that_get_cleaned_up():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
-    flexmock(module.os.environ).should_receive('get').and_return('/run/user/0')
-    flexmock(module.os).should_receive('getuid').and_return(0)
+    flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+    flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+        None
+    )
+    flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
+    flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return(None)
+    temporary_directory = flexmock(name='/tmp/borgmatic-1234')
+    temporary_directory.should_receive('cleanup').once()
+    flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
+        prefix='borgmatic', dir='/tmp'
+    ).and_return(temporary_directory)
 
 
-    assert module.get_borgmatic_runtime_directory({}) == '/run/user/0/./borgmatic'
+    with module.Runtime_directory({}) as borgmatic_runtime_directory:
+        assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic'
 
 
 
 
 def test_get_borgmatic_state_directory_uses_config_option():
 def test_get_borgmatic_state_directory_uses_config_option():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+    flexmock(module.os.environ).should_receive('get').never()
 
 
     assert (
     assert (
         module.get_borgmatic_state_directory(
         module.get_borgmatic_state_directory(
@@ -89,16 +156,24 @@ def test_get_borgmatic_state_directory_uses_config_option():
     )
     )
 
 
 
 
-def test_get_borgmatic_state_directory_falls_back_to_environment_variable():
+def test_get_borgmatic_state_directory_falls_back_to_xdg_state_home():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
-    flexmock(module.os.environ).should_receive('get').with_args(
-        'XDG_STATE_HOME', object
-    ).and_return('/tmp')
+    flexmock(module.os.environ).should_receive('get').with_args('XDG_STATE_HOME').and_return('/tmp')
+
+    assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
+
+
+def test_get_borgmatic_state_directory_falls_back_to_state_directory():
+    flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+    flexmock(module.os.environ).should_receive('get').with_args('XDG_STATE_HOME').and_return(None)
+    flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return(
+        '/tmp'
+    )
 
 
     assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
     assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
 
 
 
 
 def test_get_borgmatic_state_directory_defaults_to_hard_coded_path():
 def test_get_borgmatic_state_directory_defaults_to_hard_coded_path():
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
     flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
-    flexmock(module.os.environ).should_receive('get').and_return('/root/.local/state')
-    assert module.get_borgmatic_state_directory({}) == '/root/.local/state/borgmatic'
+    flexmock(module.os.environ).should_receive('get').and_return(None)
+    assert module.get_borgmatic_state_directory({}) == '~/.local/state/borgmatic'

+ 27 - 6
tests/unit/hooks/test_mariadb.py

@@ -73,7 +73,12 @@ def test_dump_data_sources_dumps_each_database():
             dry_run_label=object,
             dry_run_label=object,
         ).and_return(process).once()
         ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_dumps_with_password():
 def test_dump_data_sources_dumps_with_password():
@@ -94,7 +99,9 @@ def test_dump_data_sources_dumps_with_password():
         dry_run_label=object,
         dry_run_label=object,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources([database], {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        [database], {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_dumps_all_databases_at_once():
 def test_dump_data_sources_dumps_all_databases_at_once():
@@ -112,7 +119,9 @@ def test_dump_data_sources_dumps_all_databases_at_once():
         dry_run_label=object,
         dry_run_label=object,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
 def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
@@ -132,7 +141,12 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
             dry_run_label=object,
             dry_run_label=object,
         ).and_return(process).once()
         ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_database_names_to_dump_runs_mariadb_with_list_options():
 def test_database_names_to_dump_runs_mariadb_with_list_options():
@@ -434,7 +448,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
     flexmock(module).should_receive('database_names_to_dump').and_return(())
     flexmock(module).should_receive('database_names_to_dump').and_return(())
 
 
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
-        assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False)
+        assert module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
 
 
 
 
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
@@ -445,7 +461,12 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
     )
     )
     flexmock(module).should_receive('database_names_to_dump').and_return(())
     flexmock(module).should_receive('database_names_to_dump').and_return(())
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+        )
+        == []
+    )
 
 
 
 
 def test_restore_data_source_dump_runs_mariadb_to_restore():
 def test_restore_data_source_dump_runs_mariadb_to_restore():

+ 30 - 7
tests/unit/hooks/test_mongodb.py

@@ -41,7 +41,12 @@ def test_dump_data_sources_runs_mongodump_for_each_database():
             run_to_completion=False,
             run_to_completion=False,
         ).and_return(process).once()
         ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_with_dry_run_skips_mongodump():
 def test_dump_data_sources_with_dry_run_skips_mongodump():
@@ -53,7 +58,12 @@ def test_dump_data_sources_with_dry_run_skips_mongodump():
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+        )
+        == []
+    )
 
 
 
 
 def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
 def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
@@ -82,7 +92,9 @@ def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_runs_mongodump_with_username_and_password():
 def test_dump_data_sources_runs_mongodump_with_username_and_password():
@@ -120,7 +132,9 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_runs_mongodump_with_directory_format():
 def test_dump_data_sources_runs_mongodump_with_directory_format():
@@ -137,7 +151,12 @@ def test_dump_data_sources_runs_mongodump_with_directory_format():
         shell=True,
         shell=True,
     ).and_return(flexmock()).once()
     ).and_return(flexmock()).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == []
+    )
 
 
 
 
 def test_dump_data_sources_runs_mongodump_with_options():
 def test_dump_data_sources_runs_mongodump_with_options():
@@ -163,7 +182,9 @@ def test_dump_data_sources_runs_mongodump_with_options():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_runs_mongodumpall_for_all_databases():
 def test_dump_data_sources_runs_mongodumpall_for_all_databases():
@@ -181,7 +202,9 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_build_dump_command_with_username_injection_attack_gets_escaped():
 def test_build_dump_command_with_username_injection_attack_gets_escaped():

+ 27 - 6
tests/unit/hooks/test_mysql.py

@@ -73,7 +73,12 @@ def test_dump_data_sources_dumps_each_database():
             dry_run_label=object,
             dry_run_label=object,
         ).and_return(process).once()
         ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_dumps_with_password():
 def test_dump_data_sources_dumps_with_password():
@@ -94,7 +99,9 @@ def test_dump_data_sources_dumps_with_password():
         dry_run_label=object,
         dry_run_label=object,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources([database], {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        [database], {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_dumps_all_databases_at_once():
 def test_dump_data_sources_dumps_all_databases_at_once():
@@ -112,7 +119,9 @@ def test_dump_data_sources_dumps_all_databases_at_once():
         dry_run_label=object,
         dry_run_label=object,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
 def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
@@ -132,7 +141,12 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
             dry_run_label=object,
             dry_run_label=object,
         ).and_return(process).once()
         ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_database_names_to_dump_runs_mysql_with_list_options():
 def test_database_names_to_dump_runs_mysql_with_list_options():
@@ -432,7 +446,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
     flexmock(module).should_receive('database_names_to_dump').and_return(())
     flexmock(module).should_receive('database_names_to_dump').and_return(())
 
 
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
-        assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False)
+        assert module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
 
 
 
 
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
@@ -443,7 +459,12 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
     )
     )
     flexmock(module).should_receive('database_names_to_dump').and_return(())
     flexmock(module).should_receive('database_names_to_dump').and_return(())
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+        )
+        == []
+    )
 
 
 
 
 def test_restore_data_source_dump_runs_mysql_to_restore():
 def test_restore_data_source_dump_runs_mysql_to_restore():

+ 48 - 12
tests/unit/hooks/test_postgresql.py

@@ -251,7 +251,12 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
             run_to_completion=False,
             run_to_completion=False,
         ).and_return(process).once()
         ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_raises_when_no_database_names_to_dump():
 def test_dump_data_sources_raises_when_no_database_names_to_dump():
@@ -261,7 +266,9 @@ def test_dump_data_sources_raises_when_no_database_names_to_dump():
     flexmock(module).should_receive('database_names_to_dump').and_return(())
     flexmock(module).should_receive('database_names_to_dump').and_return(())
 
 
     with pytest.raises(ValueError):
     with pytest.raises(ValueError):
-        module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False)
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
 
 
 
 
 def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
 def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
@@ -270,7 +277,9 @@ def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module).should_receive('make_dump_path').and_return('')
     flexmock(module).should_receive('database_names_to_dump').and_return(())
     flexmock(module).should_receive('database_names_to_dump').and_return(())
 
 
-    module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+    module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+    ) == []
 
 
 
 
 def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
 def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
@@ -287,7 +296,12 @@ def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == []
+    )
 
 
 
 
 def test_dump_data_sources_with_dry_run_skips_pg_dump():
 def test_dump_data_sources_with_dry_run_skips_pg_dump():
@@ -304,7 +318,12 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump():
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+        )
+        == []
+    )
 
 
 
 
 def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
 def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
@@ -340,7 +359,9 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_runs_pg_dump_with_username_and_password():
 def test_dump_data_sources_runs_pg_dump_with_username_and_password():
@@ -376,7 +397,9 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_with_username_injection_attack_gets_escaped():
 def test_dump_data_sources_with_username_injection_attack_gets_escaped():
@@ -412,7 +435,9 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_runs_pg_dump_with_directory_format():
 def test_dump_data_sources_runs_pg_dump_with_directory_format():
@@ -443,7 +468,12 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
         extra_environment={'PGSSLMODE': 'disable'},
         extra_environment={'PGSSLMODE': 'disable'},
     ).and_return(flexmock()).once()
     ).and_return(flexmock()).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == []
+    )
 
 
 
 
 def test_dump_data_sources_runs_pg_dump_with_options():
 def test_dump_data_sources_runs_pg_dump_with_options():
@@ -476,7 +506,9 @@ def test_dump_data_sources_runs_pg_dump_with_options():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
 def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
@@ -498,7 +530,9 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_dump_data_sources_runs_non_default_pg_dump():
 def test_dump_data_sources_runs_non_default_pg_dump():
@@ -532,7 +566,9 @@ def test_dump_data_sources_runs_non_default_pg_dump():
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(process).once()
     ).and_return(process).once()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+    assert module.dump_data_sources(
+        databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+    ) == [process]
 
 
 
 
 def test_restore_data_source_dump_runs_pg_restore():
 def test_restore_data_source_dump_runs_pg_restore():

+ 49 - 19
tests/unit/hooks/test_sqlite.py

@@ -18,15 +18,20 @@ def test_use_streaming_false_for_no_databases():
 def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
 def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
     databases = [{'path': '/path/to/database', 'name': 'database'}]
     databases = [{'path': '/path/to/database', 'name': 'database'}]
 
 
-    flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+    flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
-        '/path/to/dump/database'
+        '/run/borgmatic/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(True)
     flexmock(module.os.path).should_receive('exists').and_return(True)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == []
+    )
 
 
 
 
 def test_dump_data_sources_dumps_each_database():
 def test_dump_data_sources_dumps_each_database():
@@ -36,9 +41,9 @@ def test_dump_data_sources_dumps_each_database():
     ]
     ]
     processes = [flexmock(), flexmock()]
     processes = [flexmock(), flexmock()]
 
 
-    flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+    flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
-        '/path/to/dump/database'
+        '/run/borgmatic/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
@@ -46,7 +51,12 @@ def test_dump_data_sources_dumps_each_database():
         processes[1]
         processes[1]
     )
     )
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_with_path_injection_attack_gets_escaped():
 def test_dump_data_sources_with_path_injection_attack_gets_escaped():
@@ -55,9 +65,9 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped():
     ]
     ]
     processes = [flexmock()]
     processes = [flexmock()]
 
 
-    flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+    flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
-        '/path/to/dump/database'
+        '/run/borgmatic/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
@@ -67,13 +77,18 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped():
             "'/path/to/database1; naughty-command'",
             "'/path/to/database1; naughty-command'",
             '.dump',
             '.dump',
             '>',
             '>',
-            '/path/to/dump/database',
+            '/run/borgmatic/database',
         ),
         ),
         shell=True,
         shell=True,
         run_to_completion=False,
         run_to_completion=False,
     ).and_return(processes[0])
     ).and_return(processes[0])
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
 def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
@@ -82,16 +97,21 @@ def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
     ]
     ]
     processes = [flexmock()]
     processes = [flexmock()]
 
 
-    flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+    flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
-        '/path/to/dump/database'
+        '/run/borgmatic'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module).should_receive('execute_command').and_return(processes[0])
     flexmock(module).should_receive('execute_command').and_return(processes[0])
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
 def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
@@ -100,32 +120,42 @@ def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
     ]
     ]
     processes = [flexmock()]
     processes = [flexmock()]
 
 
-    flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+    flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
     flexmock(module.logger).should_receive(
     flexmock(module.logger).should_receive(
         'warning'
         'warning'
     ).twice()  # once for the name=all, once for the non-existent path
     ).twice()  # once for the name=all, once for the non-existent path
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
-        '/path/to/dump/database'
+        '/run/borgmatic/database'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
     flexmock(module).should_receive('execute_command').and_return(processes[0])
     flexmock(module).should_receive('execute_command').and_return(processes[0])
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+        )
+        == processes
+    )
 
 
 
 
 def test_dump_data_sources_does_not_dump_if_dry_run():
 def test_dump_data_sources_does_not_dump_if_dry_run():
     databases = [{'path': '/path/to/database', 'name': 'database'}]
     databases = [{'path': '/path/to/database', 'name': 'database'}]
 
 
-    flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+    flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
-        '/path/to/dump/database'
+        '/run/borgmatic'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.os.path).should_receive('exists').and_return(False)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
     flexmock(module).should_receive('execute_command').never()
 
 
-    assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+    assert (
+        module.dump_data_sources(
+            databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+        )
+        == []
+    )
 
 
 
 
 def test_restore_data_source_dump_restores_database():
 def test_restore_data_source_dump_restores_database():