From 156d0b761fa933094592b26ce907a35f730d93c6 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 6 Dec 2025 12:06:21 -0500 Subject: [PATCH] backups: Generate SSH client key if needed Tests: - Click on Add Remote Backup Location. Logs show that SSH client key is generated. The private key is readable only by plinth user. - Go back, and click on Add Remote Backup Location again. Logs show that SSH client key already exists. Signed-off-by: James Valleroy --- plinth/modules/backups/__init__.py | 14 ++++++++++++++ plinth/modules/backups/views.py | 11 ++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py index bd2d617bc..f921bf50a 100644 --- a/plinth/modules/backups/__init__.py +++ b/plinth/modules/backups/__init__.py @@ -137,6 +137,20 @@ def get_known_hosts_path(): return pathlib.Path(cfg.data_dir) / '.ssh' / 'known_hosts' +def generate_ssh_client_auth_key(): + """Generate SSH client authentication keypair, if needed.""" + key_path = pathlib.Path(cfg.data_dir) / '.ssh' / 'id_ed25519' + if not key_path.exists(): + logger.info('Generating SSH client key %s for FreedomBox service', + key_path) + subprocess.run( + ['ssh-keygen', '-t', 'ed25519', '-N', '', '-f', + str(key_path)], stdout=subprocess.DEVNULL, check=True) + else: + logger.info('SSH client key %s for FreedomBox service already exists', + key_file) + + def is_ssh_hostkey_verified(hostname): """Check whether SSH Hostkey has already been verified. diff --git a/plinth/modules/backups/views.py b/plinth/modules/backups/views.py index 56d8968c3..a102b049a 100644 --- a/plinth/modules/backups/views.py +++ b/plinth/modules/backups/views.py @@ -24,7 +24,8 @@ from plinth.errors import PlinthError from plinth.modules import backups, storage from plinth.views import AppView -from . import (SESSION_PATH_VARIABLE, api, errors, forms, get_known_hosts_path, +from . import (SESSION_PATH_VARIABLE, api, errors, forms, + generate_ssh_client_auth_key, get_known_hosts_path, is_ssh_hostkey_verified, privileged) from .decorators import delete_tmp_backup_file from .repository import (BorgRepository, SshBorgRepository, get_instance, @@ -358,6 +359,14 @@ class AddRemoteRepositoryView(FormView): form_class = forms.AddRemoteRepositoryForm template_name = 'backups_add_remote_repository.html' + def get(self, *args, **kwargs): + """Handle GET requests. + + Generate SSH client authentication key if necessary. + """ + generate_ssh_client_auth_key() + return super().get(*args, kwargs) + def get_context_data(self, **kwargs): """Return additional context for rendering the template.""" context = super().get_context_data(**kwargs)