From e420adcc958123c264408f5633c54c1647c042c6 Mon Sep 17 00:00:00 2001
From: Paulo Truta
Date: Sun, 5 Jul 2026 19:04:45 +0000
Subject: [PATCH] Add SSH key pair management to Settings dashboard
Add a new SSH Key Access section to the Settings page that lets users
generate, download, regenerate, and revoke an ED25519 key pair for
direct SSH access to the Edgebox instance.
- TaskFactory: GENERATE_SSH_KEY, GET_SSH_KEY, REVOKE_SSH_KEY constants
and factory methods.
- SettingsController: new actions wired into ALLOWED_ACTIONS, SSH key
options read in index(), and a downloadPrivateKey route that serves
the private key as an attachment.
- HomeController: action descriptions and icons for the three SSH task
types so they render correctly in the Actions overview.
- settings/index.html.twig: SSH section with download button, macOS/
Linux and Windows connection instructions, and an Advanced panel with
the public key, fingerprint, connection command, and regenerate/
revoke controls.
- settings/action.html.twig: status messages for the SSH actions.
---
src/src/Controller/HomeController.php | 21 ++++
src/src/Controller/SettingsController.php | 30 +++++
src/src/Factory/TaskFactory.php | 27 +++++
src/templates/pages/settings/action.html.twig | 9 ++
src/templates/pages/settings/index.html.twig | 114 ++++++++++++++++++
5 files changed, 201 insertions(+)
diff --git a/src/src/Controller/HomeController.php b/src/src/Controller/HomeController.php
index a13f719..dcc01a5 100644
--- a/src/src/Controller/HomeController.php
+++ b/src/src/Controller/HomeController.php
@@ -259,6 +259,24 @@ private function getActionsOverviewContainerVars(): array
Task::STATUS_FINISHED => 'Set Browser Dev Environment password',
Task::STATUS_ERROR => 'Failed to set Browser Dev Environment password',
],
+ 'generate_ssh_key' => [
+ Task::STATUS_CREATED => 'Waiting to generate SSH key pair',
+ Task::STATUS_EXECUTING => 'Generating SSH key pair',
+ Task::STATUS_FINISHED => 'Generated SSH key pair',
+ Task::STATUS_ERROR => 'Failed to generate SSH key pair',
+ ],
+ 'revoke_ssh_key' => [
+ Task::STATUS_CREATED => 'Waiting to revoke SSH key pair',
+ Task::STATUS_EXECUTING => 'Revoking SSH key pair',
+ Task::STATUS_FINISHED => 'Revoked SSH key pair',
+ Task::STATUS_ERROR => 'Failed to revoke SSH key pair',
+ ],
+ 'get_ssh_key' => [
+ Task::STATUS_CREATED => 'Waiting to read SSH key pair',
+ Task::STATUS_EXECUTING => 'Reading SSH key pair',
+ Task::STATUS_FINISHED => 'Downloaded SSH private key',
+ Task::STATUS_ERROR => 'Failed to read SSH key pair',
+ ],
];
$unknown_action_descriptions = [
@@ -281,6 +299,9 @@ private function getActionsOverviewContainerVars(): array
'disable_public_dashboard' => 'ui-04',
'activate_browserdev' => 'button-play',
'deactivate_browserdev' => 'button-pause',
+ 'generate_ssh_key' => 'key-25',
+ 'revoke_ssh_key' => 'key-25',
+ 'get_ssh_key' => 'key-25',
'unknown_action' => 'ui-04',
];
diff --git a/src/src/Controller/SettingsController.php b/src/src/Controller/SettingsController.php
index 081bde0..e93f18c 100644
--- a/src/src/Controller/SettingsController.php
+++ b/src/src/Controller/SettingsController.php
@@ -46,6 +46,9 @@ class SettingsController extends BaseController
'enable_public_dashboard' => 'Enabling Online Access',
'disable_public_dashboard' => 'Disabling Online Access',
'update_system' => 'Updating System',
+ 'generate_ssh_key' => 'Generating SSH Key',
+ 'get_ssh_key' => 'Getting SSH Key',
+ 'revoke_ssh_key' => 'Revoking SSH Key',
];
/**
@@ -55,6 +58,9 @@ class SettingsController extends BaseController
'enable_public_dashboard' => 'createEnablePublicDashboardTask',
'disable_public_dashboard' => 'createDisablePublicDashboardTask',
'update_system' => 'createApplyUpdatesTask',
+ 'generate_ssh_key' => 'createGenerateSshKeyTask',
+ 'get_ssh_key' => 'createGetSshKeyTask',
+ 'revoke_ssh_key' => 'createRevokeSshKeyTask',
];
public function __construct(
@@ -317,6 +323,9 @@ public function index(Request $request): Response
$browserdev_status = $this->browserDevHelper->getBrowserDevStatus(true);
+ $ssh_public_key = $this->optionRepository->findOneBy(['name' => 'SSH_PUBLIC_KEY']) ?? new Option();
+ $ssh_fingerprint = $this->optionRepository->findOneBy(['name' => 'SSH_KEY_FINGERPRINT']) ?? new Option();
+
return $this->render('pages/settings/index.html.twig', [
'controller_title' => 'Settings',
'controller_subtitle' => 'Features & Security',
@@ -340,6 +349,8 @@ public function index(Request $request): Response
'dashboard_settings' => $this->dashboardHelper->getSettings(),
'updates_status' => $updates_status,
'browserdev_status' => $browserdev_status,
+ 'ssh_public_key' => $ssh_public_key->getValue(),
+ 'ssh_fingerprint' => $ssh_fingerprint->getValue(),
]);
}
@@ -356,6 +367,25 @@ public function logout(): Response
return $this->redirectToRoute('settings');
}
+ #[Route('/settings/download/private_key', name: 'settings_download_private_key')]
+ public function downloadPrivateKey(): Response
+ {
+ $privateKey = $this->optionRepository->findOneBy(['name' => 'SSH_PRIVATE_KEY']);
+
+ if (null === $privateKey || empty($privateKey->getValue())) {
+ // No private key available. Redirect to settings with an alert.
+ return $this->redirectToRoute('settings');
+ }
+
+ $keyContent = $privateKey->getValue();
+ $response = new Response($keyContent);
+ $response->headers->set('Content-Type', 'application/octet-stream');
+ $response->headers->set('Content-Disposition', 'attachment; filename="id_ed25519"');
+ $response->headers->set('Content-Length', (string) strlen($keyContent));
+
+ return $response;
+ }
+
#[Route('/settings/{action}', name: 'settings_action')]
public function action(string $action): Response
{
diff --git a/src/src/Factory/TaskFactory.php b/src/src/Factory/TaskFactory.php
index 0819624..dd683ab 100644
--- a/src/src/Factory/TaskFactory.php
+++ b/src/src/Factory/TaskFactory.php
@@ -35,6 +35,9 @@ class TaskFactory
public const APPLY_UPDATES = 'apply_updates';
public const ENABLE_BROWSERDEV = 'activate_browserdev';
public const DISABLE_BROWSERDEV = 'deactivate_browserdev';
+ public const GENERATE_SSH_KEY = 'generate_ssh_key';
+ public const GET_SSH_KEY = 'get_ssh_key';
+ public const REVOKE_SSH_KEY = 'revoke_ssh_key';
public const SET_BROWSERDEV_PASSWORD = 'set_browserdev_password';
private OptionRepository $optionRepository;
@@ -299,4 +302,28 @@ public function createSetBrowserDevPasswordTask(string $password): Task
return $task;
}
+
+ public function createGenerateSshKeyTask(): Task
+ {
+ $task = new Task();
+ $task->setTask(self::GENERATE_SSH_KEY);
+
+ return $task;
+ }
+
+ public function createGetSshKeyTask(): Task
+ {
+ $task = new Task();
+ $task->setTask(self::GET_SSH_KEY);
+
+ return $task;
+ }
+
+ public function createRevokeSshKeyTask(): Task
+ {
+ $task = new Task();
+ $task->setTask(self::REVOKE_SSH_KEY);
+
+ return $task;
+ }
}
diff --git a/src/templates/pages/settings/action.html.twig b/src/templates/pages/settings/action.html.twig
index 7f7c469..7bf292d 100644
--- a/src/templates/pages/settings/action.html.twig
+++ b/src/templates/pages/settings/action.html.twig
@@ -35,6 +35,15 @@
{% if action == "update_system" %}
Updating Edgebox
This can take a minute...
{% endif %}
+ {% if action == "generate_ssh_key" %}
+ Generating SSH Key Pair
Generating a new ED25519 key pair...
+ {% endif %}
+ {% if action == "revoke_ssh_key" %}
+ Revoking SSH Key Pair
Removing the SSH key pair from this Edgebox...
+ {% endif %}
+ {% if action == "get_ssh_key" %}
+ Fetching SSH Key
Reading the SSH key pair...
+ {% endif %}
+
{% embed 'blocks/_collapse_card.html.twig' with {
'title': 'Browser Dev Environment',
'collapse_id': 'collapseBrowserDevOptions',
@@ -602,3 +715,4 @@
{% endblock content %}
+