Add configurable secondary backup location

This commit is contained in:
thePR0M3TH3AN
2025-07-03 12:03:02 -04:00
parent 7b4092b9f5
commit ffa76799e3
2 changed files with 68 additions and 5 deletions

View File

@@ -422,6 +422,54 @@ def handle_set_inactivity_timeout(password_manager: PasswordManager) -> None:
print(colored(f"Error: {e}", "red"))
def handle_set_additional_backup_location(pm: PasswordManager) -> None:
"""Configure an optional second backup directory."""
cfg_mgr = pm.config_manager
if cfg_mgr is None:
print(colored("Configuration manager unavailable.", "red"))
return
try:
current = cfg_mgr.get_additional_backup_path()
if current:
print(colored(f"Current path: {current}", "cyan"))
else:
print(colored("No additional backup location configured.", "cyan"))
except Exception as e:
logging.error(f"Error loading backup path: {e}")
print(colored(f"Error: {e}", "red"))
return
value = input(
"Enter directory for extra backups (leave blank to disable): "
).strip()
if not value:
try:
cfg_mgr.set_additional_backup_path(None)
print(colored("Additional backup location disabled.", "green"))
except Exception as e:
logging.error(f"Error clearing path: {e}")
print(colored(f"Error: {e}", "red"))
return
try:
path = Path(value).expanduser()
path.mkdir(parents=True, exist_ok=True)
test_file = path / ".seedpass_write_test"
with open(test_file, "w") as f:
f.write("test")
test_file.unlink()
except Exception as e:
print(colored(f"Path not writable: {e}", "red"))
return
try:
cfg_mgr.set_additional_backup_path(str(path))
print(colored(f"Additional backups will be copied to {path}", "green"))
except Exception as e:
logging.error(f"Error saving backup path: {e}")
print(colored(f"Error: {e}", "red"))
def handle_profiles_menu(password_manager: PasswordManager) -> None:
"""Submenu for managing seed profiles."""
while True:
@@ -504,9 +552,10 @@ def handle_settings(password_manager: PasswordManager) -> None:
print("6. Export database")
print("7. Import database")
print("8. Export 2FA codes")
print("9. Set inactivity timeout")
print("10. Lock Vault")
print("11. Back")
print("9. Set additional backup location")
print("10. Set inactivity timeout")
print("11. Lock Vault")
print("12. Back")
choice = input("Select an option: ").strip()
if choice == "1":
handle_profiles_menu(password_manager)
@@ -527,12 +576,14 @@ def handle_settings(password_manager: PasswordManager) -> None:
elif choice == "8":
password_manager.handle_export_totp_codes()
elif choice == "9":
handle_set_inactivity_timeout(password_manager)
handle_set_additional_backup_location(password_manager)
elif choice == "10":
handle_set_inactivity_timeout(password_manager)
elif choice == "11":
password_manager.lock_vault()
print(colored("Vault locked. Please re-enter your password.", "yellow"))
password_manager.unlock_vault()
elif choice == "11":
elif choice == "12":
break
else:
print(colored("Invalid choice.", "red"))

View File

@@ -86,3 +86,15 @@ def test_relay_and_profile_actions(monkeypatch, capsys):
out = capsys.readouterr().out
assert fp1 in out
assert fp2 in out
def test_settings_menu_additional_backup(monkeypatch):
with TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
pm, cfg_mgr, fp_mgr = setup_pm(tmp_path, monkeypatch)
inputs = iter(["9", "12"])
with patch("main.handle_set_additional_backup_location") as handler:
with patch("builtins.input", side_effect=lambda *_: next(inputs)):
main.handle_settings(pm)
handler.assert_called_once_with(pm)