Compare commits

..

1 Commits

Author SHA1 Message Date
gitea-actions[bot] f3283c323f chore(version): pre-release bump to 01.35.02-dev [skip ci]
Publish to Composer / Publish Package (release) Failing after 7s
2026-06-23 13:33:05 +00:00
19 changed files with 36 additions and 192 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 01.35.03
# VERSION: 01.35.02
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+16 -8
View File
@@ -1,14 +1,6 @@
# Changelog
## [Unreleased]
## [01.35.03] --- 2026-06-23
## [01.35.03] --- 2026-06-23
## [01.35.01] --- 2026-06-23
## [01.35.01] --- 2026-06-23
## [01.35.00] --- 2026-06-23
## [01.35.00] --- 2026-06-23
@@ -16,3 +8,19 @@
### 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.34.00] --- 2026-06-23
## [01.34.00] --- 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 -1
View File
@@ -1,6 +1,6 @@
# MokoSuiteBackup
<!-- VERSION: 01.35.03 -->
<!-- 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>
@@ -182,7 +183,6 @@
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST_DESC"
maxlength="255"
required="true"
showon="remote_storage:sftp"
/>
<field
@@ -201,38 +201,25 @@
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME_DESC"
maxlength="255"
required="true"
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"
required="true"
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"
@@ -240,8 +227,7 @@
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSPHRASE_DESC"
maxlength="255"
required="true"
showon="remote_storage:sftp[AND]sftp_auth_type:key_passphrase"
showon="remote_storage:sftp"
/>
<field
name="sftp_path"
@@ -250,7 +236,6 @@
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_PATH_DESC"
default="/backups"
maxlength="512"
required="true"
showon="remote_storage:sftp"
/>
</fieldset>
@@ -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.03</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.03</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.03</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.03</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.03</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.03</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.03</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.03</version>
<version>01.35.02</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename>
<version>01.35.03</version>
<version>01.35.02</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>