diff --git a/docs/faq.rst b/docs/faq.rst index e07fcae14..ab40d322a 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -391,6 +391,26 @@ The Borg config directory has content that you should take care of: Make sure that only you have access to the Borg config directory. + +Note about creating multiple keyfile repositories at the same path +------------------------------------------------------------------ + +If you create a new keyfile-encrypted repository at the same filesystem +path multiple times (for example, when a previous repository at that path +was moved away or unmounted), Borg will not overwrite or reuse the existing +key file in your keys directory. Instead, it creates a new key file by +appending a numeric suffix to the base name (e.g., .2, .3, ...). + +This means you may see multiple key files like: + +- ~/.config/borg/keys/home_user_backup +- ~/.config/borg/keys/home_user_backup.2 +- ~/.config/borg/keys/home_user_backup.3 + +Each of these corresponds to a distinct repository created at the same +path at different times. This behavior avoids accidental key reuse or +overwrite. + .. _home_data_borg: How important is the $HOME/.local/share/borg directory? diff --git a/src/borg/testsuite/archiver/repo_create_cmd_test.py b/src/borg/testsuite/archiver/repo_create_cmd_test.py index a0d67b358..9e04e3582 100644 --- a/src/borg/testsuite/archiver/repo_create_cmd_test.py +++ b/src/borg/testsuite/archiver/repo_create_cmd_test.py @@ -55,3 +55,51 @@ def test_repo_create_refuse_to_overwrite_keyfile(archivers, request, monkeypatch with open(keyfile) as file: after = file.read() assert before == after + + +def test_repo_create_keyfile_same_path_creates_new_keys(archivers, request): + """Regression test for GH issue #6230. + + When creating a new keyfile-encrypted repository at the same filesystem path + multiple times (e.g., after moving/unmounting the previous one), Borg must not + overwrite or reuse the existing key file. Instead, it should create a new key + file in the keys directory, appending a numeric suffix like .2, .3, ... + """ + archiver = request.getfixturevalue(archivers) + + # First creation at path A + cmd(archiver, "repo-create", KF_ENCRYPTION) + keys = sorted(os.listdir(archiver.keys_path)) + assert len(keys) == 1 + base_key = keys[0] + base_path = os.path.join(archiver.keys_path, base_key) + with open(base_path, "rb") as f: + base_contents = f.read() + + # Simulate moving/unmounting the repo by removing the path to allow re-create at the same path + import shutil + + shutil.rmtree(archiver.repository_path) + cmd(archiver, "repo-create", KF_ENCRYPTION) + keys = sorted(os.listdir(archiver.keys_path)) + assert len(keys) == 2 + assert base_key in keys + # The new file should be base_key suffixed with .2 + assert any(k == base_key + ".2" for k in keys) + second_path = os.path.join(archiver.keys_path, base_key + ".2") + with open(second_path, "rb") as f: + second_contents = f.read() + assert second_contents != base_contents + + # Remove repo again and create a third time at same path + shutil.rmtree(archiver.repository_path) + cmd(archiver, "repo-create", KF_ENCRYPTION) + keys = sorted(os.listdir(archiver.keys_path)) + assert len(keys) == 3 + assert any(k == base_key + ".3" for k in keys) + third_path = os.path.join(archiver.keys_path, base_key + ".3") + with open(third_path, "rb") as f: + third_contents = f.read() + # Ensure all keys are distinct + assert third_contents != base_contents + assert third_contents != second_contents