diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 0e91f9e..52c77a6 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 01.35.00 +# VERSION: 01.35.03 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/README.md b/README.md index 52776a2..60f886b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MokoSuiteBackup - + Full-site backup and restore for Joomla — database, files, and configuration. diff --git a/source/packages/com_mokosuitebackup/forms/profile.xml b/source/packages/com_mokosuitebackup/forms/profile.xml index fbbb2f7..274154b 100644 --- a/source/packages/com_mokosuitebackup/forms/profile.xml +++ b/source/packages/com_mokosuitebackup/forms/profile.xml @@ -159,7 +159,6 @@ default="none" > - @@ -183,6 +182,7 @@ label="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST" description="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST_DESC" maxlength="255" + required="true" showon="remote_storage:sftp" /> + + + + + diff --git a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini index 9398fa4..1f2f08d 100644 --- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini @@ -256,7 +256,17 @@ COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME_DESC="Username for SSH authentication" COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD="SSH Password" COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD_DESC="Password for SSH authentication. Leave blank if using a key file." COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY="SSH Private Key" -COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Paste the contents of your SSH private key (e.g. id_rsa or id_ed25519). The key is stored securely in the database and written to a temp file with 0600 permissions only during upload, then deleted. Leave blank to use password authentication." +COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Upload or paste your SSH private key (e.g. id_rsa or id_ed25519). The key is stored securely in the database and written to a temp file with 0600 permissions only during upload, then deleted. Leave blank to use password authentication." +COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_UPLOAD="Upload Key File" +COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_REPLACE="Replace Key" +COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED="Key loaded" +COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_NONE="No key file" +COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_CLEAR="Remove Key" +COM_MOKOJOOMBACKUP_FIELD_SFTP_AUTH_TYPE="Authentication Type" +COM_MOKOJOOMBACKUP_FIELD_SFTP_AUTH_TYPE_DESC="Choose how to authenticate with the SFTP server." +COM_MOKOJOOMBACKUP_SFTP_AUTH_PASSWORD="Password" +COM_MOKOJOOMBACKUP_SFTP_AUTH_KEY="Key File" +COM_MOKOJOOMBACKUP_SFTP_AUTH_KEY_PASSPHRASE="Key File + Passphrase" COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE="Key Passphrase" COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE_DESC="Passphrase for the private key, if encrypted. Leave blank for unencrypted keys." COM_MOKOJOOMBACKUP_FIELD_SFTP_PATH="Remote Path" diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 5a869f4..6344b92 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/install.mysql.sql b/source/packages/com_mokosuitebackup/sql/install.mysql.sql index 67020ec..a87895c 100644 --- a/source/packages/com_mokosuitebackup/sql/install.mysql.sql +++ b/source/packages/com_mokosuitebackup/sql/install.mysql.sql @@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitebackup_profiles` ( `sftp_host` VARCHAR(255) NOT NULL DEFAULT '', `sftp_port` INT(5) UNSIGNED NOT NULL DEFAULT 22, `sftp_username` VARCHAR(255) NOT NULL DEFAULT '', + `sftp_auth_type` VARCHAR(20) NOT NULL DEFAULT 'key', `sftp_password` VARCHAR(255) NOT NULL DEFAULT '', `sftp_key_data` MEDIUMTEXT, `sftp_passphrase` VARCHAR(255) NOT NULL DEFAULT '', diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/01.36.00.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/01.36.00.sql new file mode 100644 index 0000000..b8b6565 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/01.36.00.sql @@ -0,0 +1,4 @@ +-- MokoSuiteBackup 01.36.00 — SFTP auth type column + +ALTER TABLE `#__mokosuitebackup_profiles` + ADD COLUMN `sftp_auth_type` VARCHAR(20) NOT NULL DEFAULT 'key' AFTER `sftp_username`; diff --git a/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php b/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php index 5911c06..980ea4a 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SftpUploader.php @@ -141,7 +141,15 @@ class SftpUploader implements RemoteUploaderInterface $tmpDir = sys_get_temp_dir(); $keyFile = $tmpDir . '/mokobackup-sftp-' . bin2hex(random_bytes(8)) . '.key'; - if (file_put_contents($keyFile, $this->keyData) === false) { + /* Key is stored base64-encoded in the database — decode before writing */ + $keyContent = base64_decode($this->keyData, true); + + if ($keyContent === false) { + /* Fallback: might be raw PEM (legacy or paste) */ + $keyContent = $this->keyData; + } + + if (file_put_contents($keyFile, $keyContent) === false) { throw new \RuntimeException('Cannot write temporary SSH key file'); } diff --git a/source/packages/com_mokosuitebackup/src/Field/SshKeyField.php b/source/packages/com_mokosuitebackup/src/Field/SshKeyField.php new file mode 100644 index 0000000..176ce22 --- /dev/null +++ b/source/packages/com_mokosuitebackup/src/Field/SshKeyField.php @@ -0,0 +1,109 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * + * Custom field for SSH private key input. + * Supports both file upload (via FileReader JS) and paste-in textarea. + * The key content is stored in the database, not as a file on disk. + */ + +namespace Joomla\Component\MokoSuiteBackup\Administrator\Field; + +defined('_JEXEC') or die; + +use Joomla\CMS\Form\FormField; +use Joomla\CMS\Language\Text; + +class SshKeyField extends FormField +{ + protected $type = 'SshKey'; + + protected function getInput(): string + { + $value = $this->value ?? ''; + $id = $this->id; + $name = $this->name; + + $hasKey = !empty($value) && str_contains($value, 'PRIVATE KEY'); + + $html = '
'; + + /* Status badge */ + if ($hasKey) { + $html .= '' + . ' ' + . Text::_('COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED') + . ''; + } + + /* File upload button */ + $html .= ''; + $html .= ''; + + $html .= ''; + + if ($hasKey) { + $html .= ' '; + } + + /* Hidden field — key data is NEVER rendered as visible text. + On existing keys, we submit a sentinel value to preserve the DB value + unless a new file is uploaded or clear is clicked. */ + if ($hasKey) { + $html .= ''; + } else { + $html .= ''; + } + + $html .= '
'; + $html .= $this->getScript(); + + return $html; + } + + private function getScript(): string + { + return <<<'JS' + +JS; + } +} diff --git a/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php b/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php index fef06ca..5102e91 100644 --- a/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php +++ b/source/packages/com_mokosuitebackup/src/Table/ProfileTable.php @@ -25,6 +25,23 @@ class ProfileTable extends Table public function store($updateNulls = true): bool { + /* Handle SSH key sentinel — when __KEEP_EXISTING__ is submitted, + preserve the current DB value instead of overwriting with the sentinel. + This prevents the key from being exposed in the form HTML. */ + if (isset($this->sftp_key_data) && $this->sftp_key_data === '__KEEP_EXISTING__') { + if ($this->id) { + $db = $this->getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName('sftp_key_data')) + ->from($db->quoteName($this->_tbl)) + ->where($db->quoteName('id') . ' = ' . (int) $this->id); + $db->setQuery($query); + $this->sftp_key_data = $db->loadResult() ?: ''; + } else { + $this->sftp_key_data = ''; + } + } + $result = parent::store($updateNulls); if ($result && !empty($this->backup_dir)) { diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index c772cd4..f6fc4f3 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 21baa6b..652e0c9 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 6223170..0fad0bd 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 4a16d7c..1612ee8 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index 12314b7..e21776c 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 4623395..9b2faa7 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index a831160..f2ac768 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 01.35.01 + 01.35.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index f1ade58..9b1bb07 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 01.35.01 + 01.35.03 2026-06-02 Moko Consulting hello@mokoconsulting.tech