Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd597a3ecd | |||
| 41b481dbfe | |||
| e72a007041 | |||
| 3edf635a4c |
@@ -5,7 +5,7 @@
|
||||
<display-name>Package - MokoJoomBackup</display-name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>Full-site backup and restore for Joomla — database, files, and configuration</description>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-dev</version>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokoplatform.Automation
|
||||
# VERSION: 01.04.02
|
||||
# VERSION: 01.05.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
+20
-36
@@ -1,8 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [01.05.00] --- 2026-06-07
|
||||
|
||||
### Added
|
||||
- Dashboard submenu entry as default landing page with `class:home` icon
|
||||
- `[DEFAULT_DIR]` placeholder for portable backup directory configuration — resolves to `administrator/components/com_mokojoombackup/backups` at runtime
|
||||
- Live AJAX directory validation on backup_dir field — checks existence, writability, and placeholder resolution as user types (debounced 400ms)
|
||||
- `checkDir` AJAX endpoint for real-time directory permission checking
|
||||
- Web-accessible warning badge on backup download buttons when archive is inside web root
|
||||
- Inline security warning in FolderPicker when default directory is selected
|
||||
- Auto `.htaccess` and `index.html` protection for web-accessible backup directories on profile save and at backup time
|
||||
- Font Awesome 6 submenu icons via CSS injection in `MokoJoomBackupComponent::boot()`
|
||||
- `syncMenuIcons()` installer postflight — syncs icon classes to `#__menu` on install and update
|
||||
- `encryptionPassword` property on `SteppedSession` for upcoming stepped backup encryption support
|
||||
|
||||
### Changed
|
||||
- Profile `backup_dir` default changed from literal path to `[DEFAULT_DIR]` placeholder
|
||||
- Backup engine fallback directory changed from hardcoded path to `[DEFAULT_DIR]`
|
||||
- `isUsingDefaultBackupDir()` now matches `[DEFAULT_DIR]` placeholder in addition to literal path and empty values
|
||||
- Dashboard submenu language key added to `.sys.ini` files (en-GB, en-US)
|
||||
|
||||
## [01.04.00] --- 2026-06-07
|
||||
|
||||
|
||||
@@ -67,38 +86,3 @@
|
||||
- SQL update migration and error handling
|
||||
- Removed orphaned scriptfile from component manifest
|
||||
- Consolidated admin files into single files block
|
||||
|
||||
## 01.00 — 2026-06-02
|
||||
|
||||
### Added
|
||||
- Initial package structure with component, system plugin, task plugin, and webservices plugin
|
||||
- Joomla Scheduled Tasks integration (plg_task_mokojoombackup) — create multiple tasks, each running a different backup profile on its own schedule
|
||||
- Individual form fields for all profile settings (no raw JSON)
|
||||
- FTP/FTPS uploader with recursive directory creation, passive mode, SSL, and size verification
|
||||
- Google Drive uploader using OAuth2 refresh tokens and resumable upload API
|
||||
- S3-compatible remote storage: AWS S3, Wasabi, Backblaze B2, MinIO (#16)
|
||||
- RemoteUploaderInterface for pluggable storage backends
|
||||
- Remote upload integrated into BackupEngine with option to delete local copy after upload
|
||||
- Restore engine with file restoration and database import
|
||||
- MokoRestore standalone restore script — self-contained site restoration without Joomla
|
||||
- "Include Restore Script" toggle per profile
|
||||
- FileRestorer with protected file handling (preserves configuration.php, .htaccess)
|
||||
- DatabaseImporter with streaming line-by-line SQL execution and error tolerance
|
||||
- Admin dashboard quickicon widget — backup status at a glance with warnings (#18)
|
||||
- Differential backups — only back up files changed since last full backup (#19)
|
||||
- DifferentialScanner with file manifests stored in backup records
|
||||
- JPA archive format import for Akeeba Backup migration (#20)
|
||||
- AES-256 archive encryption with per-profile password (#17)
|
||||
- SHA-256 checksum verification for backup integrity (#15)
|
||||
- Email notifications on backup success/failure via Joomla mailer (#14)
|
||||
- Akeeba Backup Pro importer — profiles, filters, remote storage, and backup history
|
||||
- Auto-disables Akeeba plugins and scheduled tasks after successful import
|
||||
- AJAX step-based backup engine for shared hosting (overcomes max_execution_time)
|
||||
- Progress bar modal in admin UI with real-time phase/percentage updates
|
||||
- Per-profile archive settings: format, compression level, split size, backup directory
|
||||
- Backup engine with database dumper, file scanner, and ZIP archive builder
|
||||
- Backup profiles with independent configurations
|
||||
- Backup record management (list, download, delete)
|
||||
- CLI script for cron/scheduled backups
|
||||
- REST API compatible with MokoJoomBackup MCP server
|
||||
- System plugin for automatic backup cleanup with configurable retention
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MokoJoomBackup
|
||||
|
||||
<!-- VERSION: 01.04.02 -->
|
||||
<!-- VERSION: 01.05.00 -->
|
||||
|
||||
Full-site backup and restore for Joomla — database, files, and configuration.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="component" method="upgrade">
|
||||
<name>com_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -109,6 +109,7 @@ class AjaxController extends BaseController
|
||||
// that could contain a backup folder (e.g., /home/user/backups)
|
||||
$dirs = [];
|
||||
$handle = @opendir($path);
|
||||
$warning = null;
|
||||
|
||||
if ($handle) {
|
||||
while (($entry = readdir($handle)) !== false) {
|
||||
@@ -127,18 +128,37 @@ class AjaxController extends BaseController
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
} else {
|
||||
$warning = 'Cannot read directory contents (check permissions)';
|
||||
}
|
||||
|
||||
usort($dirs, fn($a, $b) => strcasecmp($a['name'], $b['name']));
|
||||
|
||||
$parent = dirname($path);
|
||||
|
||||
$this->sendJson([
|
||||
// Ensure parent is still within allowed boundaries
|
||||
$parentAllowed = false;
|
||||
|
||||
if ($parent !== $path) {
|
||||
if ($jRoot !== false && strpos($parent, $jRoot) === 0) {
|
||||
$parentAllowed = true;
|
||||
} elseif ($homeDir !== '' && strpos($parent, $homeDir) === 0) {
|
||||
$parentAllowed = true;
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'error' => false,
|
||||
'current' => $path,
|
||||
'parent' => ($parent !== $path) ? $parent : null,
|
||||
'parent' => $parentAllowed ? $parent : null,
|
||||
'dirs' => $dirs,
|
||||
]);
|
||||
];
|
||||
|
||||
if ($warning !== null) {
|
||||
$response['warning'] = $warning;
|
||||
}
|
||||
|
||||
$this->sendJson($response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,7 +185,7 @@ class AjaxController extends BaseController
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName(['absolute_path', 'log']))
|
||||
->from($db->quoteName('#__mokojoombackup_records'))
|
||||
->where($db->quoteName('id') . ' = ' . $id);
|
||||
->where($db->quoteName('id') . ' = ' . (int) $id);
|
||||
$db->setQuery($query);
|
||||
$record = $db->loadObject();
|
||||
|
||||
@@ -205,6 +225,12 @@ class AjaxController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->app->getIdentity()->authorise('core.manage', 'com_mokojoombackup')) {
|
||||
$this->sendJson(['error' => true, 'message' => 'Access denied']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$rawPath = trim($this->input->getString('path', ''));
|
||||
|
||||
if ($rawPath === '') {
|
||||
|
||||
@@ -530,13 +530,17 @@ class BackupEngine
|
||||
$htaccess = $dir . '/.htaccess';
|
||||
|
||||
if (!is_file($htaccess)) {
|
||||
@file_put_contents($htaccess, "Order deny,allow\nDeny from all\n");
|
||||
if (@file_put_contents($htaccess, "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n") === false) {
|
||||
error_log('MokoJoomBackup: Could not create .htaccess in backup directory: ' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
$index = $dir . '/index.html';
|
||||
|
||||
if (!is_file($index)) {
|
||||
@file_put_contents($index, '<!DOCTYPE html><title></title>');
|
||||
if (@file_put_contents($index, '<!DOCTYPE html><title></title>') === false) {
|
||||
error_log('MokoJoomBackup: Could not create index.html in backup directory: ' . $dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -317,8 +317,8 @@ class SteppedBackupEngine
|
||||
$zip->close();
|
||||
|
||||
// Clean up temp SQL file
|
||||
if (is_file($sqlFile)) {
|
||||
@unlink($sqlFile);
|
||||
if (is_file($sqlFile) && !@unlink($sqlFile)) {
|
||||
error_log('MokoJoomBackup: Could not delete temp SQL file: ' . $sqlFile);
|
||||
}
|
||||
|
||||
$totalSize = file_exists($session->archivePath) ? filesize($session->archivePath) : 0;
|
||||
@@ -572,13 +572,17 @@ class SteppedBackupEngine
|
||||
$htaccess = $dir . '/.htaccess';
|
||||
|
||||
if (!is_file($htaccess)) {
|
||||
@file_put_contents($htaccess, "Order deny,allow\nDeny from all\n");
|
||||
if (@file_put_contents($htaccess, "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n") === false) {
|
||||
error_log('MokoJoomBackup: Could not create .htaccess in backup directory: ' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
$index = $dir . '/index.html';
|
||||
|
||||
if (!is_file($index)) {
|
||||
@file_put_contents($index, '<!DOCTYPE html><title></title>');
|
||||
if (@file_put_contents($index, '<!DOCTYPE html><title></title>') === false) {
|
||||
error_log('MokoJoomBackup: Could not create index.html in backup directory: ' . $dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class MokoJoomBackupComponent extends MVCComponent
|
||||
. ' #menu a[href*="com_mokojoombackup"][href*="view=profiles"] .sidebar-item-title::before { content: "\f013"; }'
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
// Non-critical
|
||||
error_log('MokoJoomBackup: boot() CSS injection failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ class FolderPickerField extends FormField
|
||||
body: form,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(r) { if (!r.ok) throw new Error('Server error (HTTP ' + r.status + ')'); return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
setStatus('text-danger', 'icon-unpublish', data.message || 'Error', null);
|
||||
@@ -241,7 +241,7 @@ class FolderPickerField extends FormField
|
||||
body: form,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(r) { if (!r.ok) throw new Error('Server error (HTTP ' + r.status + ')'); return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
tree.textContent = data.message || 'Error loading directory';
|
||||
|
||||
@@ -65,13 +65,17 @@ class ProfileTable extends Table
|
||||
$htaccess = $resolved . '/.htaccess';
|
||||
|
||||
if (!is_file($htaccess)) {
|
||||
@file_put_contents($htaccess, "Order deny,allow\nDeny from all\n");
|
||||
if (@file_put_contents($htaccess, "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n") === false) {
|
||||
error_log('MokoJoomBackup: Could not create .htaccess in: ' . $resolved);
|
||||
}
|
||||
}
|
||||
|
||||
$index = $resolved . '/index.html';
|
||||
|
||||
if (!is_file($index)) {
|
||||
@file_put_contents($index, '<!DOCTYPE html><title></title>');
|
||||
if (@file_put_contents($index, '<!DOCTYPE html><title></title>') === false) {
|
||||
error_log('MokoJoomBackup: Could not create index.html in: ' . $resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="actionlog" method="upgrade">
|
||||
<name>plg_actionlog_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="console" method="upgrade">
|
||||
<name>plg_console_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="content" method="upgrade">
|
||||
<name>plg_content_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</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>plg_quickicon_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>plg_system_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="task" method="upgrade">
|
||||
<name>plg_task_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-->
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>plg_webservices_mokojoombackup</name>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</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 - MokoJoomBackup</name>
|
||||
<packagename>mokojoombackup</packagename>
|
||||
<version>01.04.02-dev</version>
|
||||
<version>01.05.00-rc</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
+2
-2
@@ -198,7 +198,7 @@ class Pkg_MokoJoomBackupInstallerScript
|
||||
mkdir($backupDir, 0755, true);
|
||||
|
||||
// Protect backup directory with .htaccess
|
||||
file_put_contents($backupDir . '/.htaccess', "Order deny,allow\nDeny from all\n");
|
||||
file_put_contents($backupDir . '/.htaccess', "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n");
|
||||
file_put_contents($backupDir . '/index.html', '<!DOCTYPE html><title></title>');
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ class Pkg_MokoJoomBackupInstallerScript
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
} catch (\Throwable $e) {
|
||||
// Non-critical
|
||||
error_log('MokoJoomBackup: syncMenuIcons() failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user