Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3283c323f |
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Automation
|
||||
# VERSION: 01.35.04
|
||||
# VERSION: 01.35.02
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
+18
-6
@@ -1,14 +1,26 @@
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
## [01.35.04] --- 2026-06-23
|
||||
## [01.35.00] --- 2026-06-23
|
||||
|
||||
## [01.35.04] --- 2026-06-23
|
||||
## [01.35.00] --- 2026-06-23
|
||||
|
||||
## [01.35.03] --- 2026-06-23
|
||||
### Added
|
||||
- SFTP remote storage with SSH key file authentication — key stored securely in database
|
||||
- CLI restore options: --files-only, --db-only, --no-preserve-config, --password
|
||||
|
||||
## [01.35.03] --- 2026-06-23
|
||||
## [01.34.00] --- 2026-06-23
|
||||
|
||||
## [01.35.01] --- 2026-06-23
|
||||
## [01.34.00] --- 2026-06-23
|
||||
|
||||
## [01.35.01] --- 2026-06-23
|
||||
### Added
|
||||
- Dashboard: snapshot widget, backup trend chart (30 days), and storage breakdown by profile (#61)
|
||||
|
||||
## [01.33.00] --- 2026-06-23
|
||||
|
||||
## [01.33.00] --- 2026-06-23
|
||||
|
||||
### Added
|
||||
- Backup comparison: select two backups to view side-by-side size, file count, and duration differences (#64)
|
||||
- Selective article restore: browse articles inside a snapshot and restore individual items (#58)
|
||||
- Archive browser: view files inside a backup without extracting (#59)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MokoSuiteBackup
|
||||
|
||||
<!-- VERSION: 01.35.04 -->
|
||||
<!-- VERSION: 01.35.02 -->
|
||||
|
||||
Full-site backup and restore for Joomla — database, files, and configuration.
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@
|
||||
default="none"
|
||||
>
|
||||
<option value="none">COM_MOKOJOOMBACKUP_REMOTE_NONE</option>
|
||||
<option value="ftp">COM_MOKOJOOMBACKUP_REMOTE_FTP</option>
|
||||
<option value="sftp">COM_MOKOJOOMBACKUP_REMOTE_SFTP</option>
|
||||
<option value="google_drive">COM_MOKOJOOMBACKUP_REMOTE_GDRIVE</option>
|
||||
<option value="s3">COM_MOKOJOOMBACKUP_REMOTE_S3</option>
|
||||
@@ -202,34 +203,23 @@
|
||||
maxlength="255"
|
||||
showon="remote_storage:sftp"
|
||||
/>
|
||||
<field
|
||||
name="sftp_auth_type"
|
||||
type="list"
|
||||
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_AUTH_TYPE"
|
||||
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_AUTH_TYPE_DESC"
|
||||
default="key"
|
||||
showon="remote_storage:sftp"
|
||||
>
|
||||
<option value="password">COM_MOKOJOOMBACKUP_SFTP_AUTH_PASSWORD</option>
|
||||
<option value="key">COM_MOKOJOOMBACKUP_SFTP_AUTH_KEY</option>
|
||||
<option value="key_passphrase">COM_MOKOJOOMBACKUP_SFTP_AUTH_KEY_PASSPHRASE</option>
|
||||
</field>
|
||||
<field
|
||||
name="sftp_password"
|
||||
type="password"
|
||||
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD"
|
||||
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD_DESC"
|
||||
maxlength="255"
|
||||
showon="remote_storage:sftp[AND]sftp_auth_type:password"
|
||||
showon="remote_storage:sftp"
|
||||
/>
|
||||
<field
|
||||
name="sftp_key_data"
|
||||
type="SshKey"
|
||||
type="textarea"
|
||||
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY"
|
||||
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC"
|
||||
rows="6"
|
||||
cols="60"
|
||||
filter="raw"
|
||||
showon="remote_storage:sftp[AND]sftp_auth_type:key,key_passphrase"
|
||||
addfieldprefix="Joomla\Component\MokoSuiteBackup\Administrator\Field"
|
||||
showon="remote_storage:sftp"
|
||||
/>
|
||||
<field
|
||||
name="sftp_passphrase"
|
||||
@@ -237,7 +227,7 @@
|
||||
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE"
|
||||
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE_DESC"
|
||||
maxlength="255"
|
||||
showon="remote_storage:sftp[AND]sftp_auth_type:key_passphrase"
|
||||
showon="remote_storage:sftp"
|
||||
/>
|
||||
<field
|
||||
name="sftp_path"
|
||||
|
||||
@@ -256,17 +256,7 @@ 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="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_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_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"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="component" method="upgrade">
|
||||
<name>MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -22,7 +22,6 @@ 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 '',
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
-- 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`;
|
||||
@@ -141,15 +141,7 @@ class SftpUploader implements RemoteUploaderInterface
|
||||
$tmpDir = sys_get_temp_dir();
|
||||
$keyFile = $tmpDir . '/mokobackup-sftp-' . bin2hex(random_bytes(8)) . '.key';
|
||||
|
||||
/* 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) {
|
||||
if (file_put_contents($keyFile, $this->keyData) === false) {
|
||||
throw new \RuntimeException('Cannot write temporary SSH key file');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteBackup
|
||||
* @subpackage com_mokosuitebackup
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @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 = '<div id="' . htmlspecialchars($id) . '-wrapper">';
|
||||
|
||||
/* Status badge */
|
||||
if ($hasKey) {
|
||||
$html .= '<span class="badge bg-success me-2">'
|
||||
. '<span class="icon-lock" aria-hidden="true"></span> '
|
||||
. Text::_('COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED')
|
||||
. '</span>';
|
||||
}
|
||||
|
||||
/* File upload button */
|
||||
$html .= '<label class="btn btn-outline-secondary btn-sm" for="' . htmlspecialchars($id) . '-file">';
|
||||
$html .= '<span class="icon-upload" aria-hidden="true"></span> ';
|
||||
$html .= $hasKey ? Text::_('COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_REPLACE') : Text::_('COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_UPLOAD');
|
||||
$html .= '</label>';
|
||||
$html .= '<input type="file" id="' . htmlspecialchars($id) . '-file"'
|
||||
. ' accept=".pem,.key,.openssh,.ppk,*" style="display:none;"'
|
||||
. ' onchange="mokoSshKeyFileSelected(\'' . htmlspecialchars($id) . '\', this)">';
|
||||
|
||||
$html .= '<span id="' . htmlspecialchars($id) . '-status" class="ms-2 text-muted small"></span>';
|
||||
|
||||
if ($hasKey) {
|
||||
$html .= ' <button type="button" class="btn btn-sm btn-outline-danger ms-2"'
|
||||
. ' onclick="mokoSshKeyClear(\'' . htmlspecialchars($id) . '\')">'
|
||||
. '<span class="icon-times" aria-hidden="true"></span> '
|
||||
. Text::_('COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_CLEAR')
|
||||
. '</button>';
|
||||
}
|
||||
|
||||
/* 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 .= '<input type="hidden" name="' . htmlspecialchars($name) . '" id="' . htmlspecialchars($id) . '"'
|
||||
. ' value="__KEEP_EXISTING__">';
|
||||
} else {
|
||||
$html .= '<input type="hidden" name="' . htmlspecialchars($name) . '" id="' . htmlspecialchars($id) . '"'
|
||||
. ' value="">';
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
$html .= $this->getScript();
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function getScript(): string
|
||||
{
|
||||
return <<<'JS'
|
||||
<script>
|
||||
function mokoSshKeyFileSelected(fieldId, input) {
|
||||
if (!input.files || !input.files[0]) return;
|
||||
var file = input.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
/* Base64 encode the key before storing in the hidden field */
|
||||
var content = e.target.result;
|
||||
var encoded = btoa(content);
|
||||
document.getElementById(fieldId).value = encoded;
|
||||
var status = document.getElementById(fieldId + '-status');
|
||||
if (status) status.textContent = file.name + ' uploaded';
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function mokoSshKeyClear(fieldId) {
|
||||
document.getElementById(fieldId).value = '';
|
||||
var status = document.getElementById(fieldId + '-status');
|
||||
if (status) status.textContent = 'Key removed';
|
||||
var fileInput = document.getElementById(fieldId + '-file');
|
||||
if (fileInput) fileInput.value = '';
|
||||
}
|
||||
</script>
|
||||
JS;
|
||||
}
|
||||
}
|
||||
@@ -25,23 +25,6 @@ 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)) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="actionlog" method="upgrade">
|
||||
<name>Action Log - MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="console" method="upgrade">
|
||||
<name>Console - MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>Content - MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="quickicon" method="upgrade">
|
||||
<name>Quick Icon - MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>System - MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="task" method="upgrade">
|
||||
<name>Task - MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Web Services - MokoSuiteBackup</name>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<extension type="package" method="upgrade">
|
||||
<name>Package - MokoSuiteBackup</name>
|
||||
<packagename>mokosuitebackup</packagename>
|
||||
<version>01.35.04</version>
|
||||
<version>01.35.02</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
Reference in New Issue
Block a user