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")) 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: def handle_profiles_menu(password_manager: PasswordManager) -> None:
"""Submenu for managing seed profiles.""" """Submenu for managing seed profiles."""
while True: while True:
@@ -504,9 +552,10 @@ def handle_settings(password_manager: PasswordManager) -> None:
print("6. Export database") print("6. Export database")
print("7. Import database") print("7. Import database")
print("8. Export 2FA codes") print("8. Export 2FA codes")
print("9. Set inactivity timeout") print("9. Set additional backup location")
print("10. Lock Vault") print("10. Set inactivity timeout")
print("11. Back") print("11. Lock Vault")
print("12. Back")
choice = input("Select an option: ").strip() choice = input("Select an option: ").strip()
if choice == "1": if choice == "1":
handle_profiles_menu(password_manager) handle_profiles_menu(password_manager)
@@ -527,12 +576,14 @@ def handle_settings(password_manager: PasswordManager) -> None:
elif choice == "8": elif choice == "8":
password_manager.handle_export_totp_codes() password_manager.handle_export_totp_codes()
elif choice == "9": elif choice == "9":
handle_set_inactivity_timeout(password_manager) handle_set_additional_backup_location(password_manager)
elif choice == "10": elif choice == "10":
handle_set_inactivity_timeout(password_manager)
elif choice == "11":
password_manager.lock_vault() password_manager.lock_vault()
print(colored("Vault locked. Please re-enter your password.", "yellow")) print(colored("Vault locked. Please re-enter your password.", "yellow"))
password_manager.unlock_vault() password_manager.unlock_vault()
elif choice == "11": elif choice == "12":
break break
else: else:
print(colored("Invalid choice.", "red")) print(colored("Invalid choice.", "red"))

View File

@@ -86,3 +86,15 @@ def test_relay_and_profile_actions(monkeypatch, capsys):
out = capsys.readouterr().out out = capsys.readouterr().out
assert fp1 in out assert fp1 in out
assert fp2 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)