Compare commits

..

13 Commits

Author SHA1 Message Date
gitea-actions[bot] 9c1ce2bd44 chore(version): pre-release bump to 01.45.07-dev [skip ci] 2026-06-29 15:28:04 +00:00
gitea-actions[bot] 2b36fa47e3 chore(version): auto-bump patch 01.45.06-dev [skip ci] 2026-06-29 15:27:47 +00:00
jmiller c21e237c38 chore: migrate update server URLs to MokoGitea
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 21s
2026-06-29 15:22:53 +00:00
gitea-actions[bot] 0b21c61578 chore(version): pre-release bump to 01.45.05-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
2026-06-29 15:11:27 +00:00
jmiller 433eb6a967 chore: migrate update server URLs to MokoGitea
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 15s
Universal: PR Check / Secret Scan (pull_request) Successful in 13s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 30s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 1m2s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-06-29 15:11:04 +00:00
gitea-actions[bot] 611d9588f4 chore(version): pre-release bump to 01.45.03-dev [skip ci] 2026-06-29 11:36:41 +00:00
gitea-actions[bot] 7303d363f2 chore(version): pre-release bump to 01.45.02-dev [skip ci] 2026-06-29 11:36:17 +00:00
jmiller c6c6000d53 Merge pull request 'fix: Bootstrap modals' (#163) from fix/bootstrap-modals into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 22s
2026-06-29 11:35:38 +00:00
jmiller bed685e203 Merge pull request 'fix: MokoRestore improvements' (#162) from fix/mokorestore-improvements into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 25s
2026-06-29 11:35:30 +00:00
jmiller 35fd11cde9 chore: retrigger CI
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 12s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 54s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-06-29 06:33:42 -05:00
jmiller 6e731f2bca fix: remove run/backup buttons, move actions to detail view, custom restore script name, version bump 01.43.11-dev
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 12s
Universal: PR Check / Secret Scan (pull_request) Successful in 10s
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 27s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 1m1s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Remove Run Backup / Backup Now buttons from profiles list, profile edit toolbar, and backup records view
- Move download, browse archive, and view log from backup list rows into individual backup record detail view
- Add download button to backup detail toolbar
- Link profile column in backup records list to profile edit
- Complete restore script filename customization across BackupEngine, SteppedBackupEngine, and MokoRestore
- Remove ordering field from profiles, default sort by ID ascending
- Fix untranslated JFIELD language keys
- Bump all manifests to 01.43.11-dev
2026-06-29 06:33:00 -05:00
gitea-actions[bot] 438a2fdaec chore(version): pre-release bump to 01.44.03-dev [skip ci] 2026-06-28 19:12:58 +00:00
gitea-actions[bot] 27a2008675 chore(version): auto-bump patch 01.44.02-dev [skip ci] 2026-06-28 19:12:46 +00:00
219 changed files with 2057 additions and 8280 deletions
+2 -12
View File
@@ -29,7 +29,6 @@ secrets/
*.pid
*.seed
# ============================================================
# OS / Editor / IDE cruft
# ============================================================
@@ -46,7 +45,6 @@ Icon?
.idea/
.settings/
.claude/
.gemini/
.vscode/*
!.vscode/tasks.json
!.vscode/settings.json.example
@@ -118,8 +116,6 @@ out/
*.map
*.css.map
*.js.map
*.min.css
*.min.js
*.tsbuildinfo
# ============================================================
@@ -155,7 +151,7 @@ package-lock.json
# PHP / Composer tooling
# ============================================================
vendor/
!src/media/vendor/
!source/media/vendor/
composer.lock
*.phar
codeception.phar
@@ -181,7 +177,7 @@ __pycache__/
*.egg
*.egg-info/
.installed.cfg
/MANIFEST
MANIFEST
develop-eggs/
downloads/
eggs/
@@ -205,9 +201,3 @@ hypothesis/
profile.ps1
.mcp.json
# ============================================================
# Local wiki clone (not version controlled)
# ============================================================
wiki/
docs/
+6 -6
View File
@@ -7,7 +7,7 @@ Full-site backup and restore for Joomla — database, files, and configuration.
| Field | Value |
|---|---|
| **Package** | `pkg_mokosuitebackup` |
| **Language** | PHP 8.3+ |
| **Language** | PHP 8.1+ |
| **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoSuiteBackup Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteBackup/wiki) |
@@ -29,7 +29,7 @@ Joomla **package** with four sub-extensions:
### com_mokosuitebackup (Component)
- Admin backend for managing backup profiles and records
- Backup engine: `Engine/BackupEngine`, `Engine/DatabaseDumper`, `Engine/FileScanner`, `Engine/Archiver`
- Joomla 6 MVC: Controllers, Models, Views, Tables
- Joomla 4/5 MVC: Controllers, Models, Views, Tables
- Namespace: `Joomla\Component\MokoSuiteBackup\Administrator`
- DB tables: `#__mokosuitebackup_profiles`, `#__mokosuitebackup_records`
- CLI: `cli/mokosuitebackup.php` for cron-based backups
@@ -59,13 +59,13 @@ Joomla **package** with four sub-extensions:
- **Attribution**: `Authored-by: Moko Consulting`
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
- **Minification**: handled at build time (CI)
- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/wiki)
- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)
## Coding Standards
- PHP 8.3+ minimum (Joomla 6)
- Joomla 6 DI container pattern: `services/provider.php` → Extension class
- PHP 8.1+ minimum
- Joomla 4/5 DI container pattern: `services/provider.php` → Extension class
- Legacy stub `.php` file required for plugin loader but empty
- `SubscriberInterface` for event subscription (not `on*` method naming)
- `bind() → check() → store()` for Table operations (not `save()`)
+3 -3
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Release
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00
@@ -43,7 +43,7 @@ jobs:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup MokoCLI tools
- name: Setup mokocli tools
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
+29 -31
View File
@@ -3,18 +3,18 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from MokoGitea repo metadata (API)
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
# +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +=======================================================================+
# | |
# | Reads MokoGitea repo metadata (joomla|dolibarr|generic) to branch logic. |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages |
@@ -64,14 +64,10 @@ jobs:
promote-rc:
name: Promote to RC
runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
)
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository
@@ -79,9 +75,8 @@ jobs:
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
submodules: recursive
- name: Setup MokoCLI tools
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -114,8 +109,8 @@ jobs:
run: |
git fetch origin rc
git checkout rc
git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-actions[bot]"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
@@ -168,13 +163,9 @@ jobs:
release:
name: Build & Release Pipeline
runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
)
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository
@@ -182,12 +173,11 @@ jobs:
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
submodules: recursive
- name: Configure git for bot pushes
run: |
git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-actions[bot]"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
@@ -203,7 +193,7 @@ jobs:
fi
echo "No conflict markers found"
- name: Setup MokoCLI tools
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -398,7 +388,7 @@ jobs:
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)"
- name: "Step 11: Delete rc branch and recreate dev from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
@@ -410,9 +400,17 @@ jobs:
"${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# dev is reset from main by the dedicated "Cascade Main -> Dev" workflow
# (cascade-dev.yml), which runs after this release completes.
echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
+3 -4
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Universal
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 01.00.00
@@ -33,8 +33,7 @@ jobs:
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
# URL-encode the branch name's slashes (no PHP dependency on the runner)
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
+7 -62
View File
@@ -1,65 +1,10 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Release
# BRIEF: Reset dev to main after each release (cascade). Moved out of auto-release Step 11.
#
# +========================================================================+
# | CASCADE MAIN -> DEV |
# +========================================================================+
# | dev mirrors main and is reset on every release. |
# | delete+recreate cannot run against a protected branch, so this |
# | force-pushes main -> dev. The automation identity is force-push |
# | allowlisted on dev via branch-protection.yml. |
# | Runs AFTER the release workflow completes, so dev picks up the |
# | fully version-bumped + changelog-promoted main (not the pre-bump |
# | state). Force-push (not merge) => no version-file conflicts. |
# +========================================================================+
name: "Cascade Main -> Dev"
on:
workflow_run:
workflows: ["Universal: Build & Release"]
types: [completed]
workflow_dispatch:
concurrency:
group: cascade-dev-${{ github.repository }}
cancel-in-progress: true
permissions:
contents: write
# DISABLED — auto-release Step 11 recreates dev from main after every release.
# Cascade-dev is redundant and causes version conflicts when both main and dev
# have different version numbers in templateDetails.xml / manifest.xml.
name: "Cascade Main → Dev (DISABLED)"
on: workflow_dispatch
jobs:
cascade:
name: Reset dev to main
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success')
noop:
runs-on: ubuntu-latest
steps:
- name: Force dev to main
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
git init -q sync && cd sync
git config user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config user.name "mokogitea-actions[bot]"
git remote add origin "https://x-access-token:${TOKEN}@${SERVER#https://}/${REPO}.git"
git fetch -q --depth=1 origin main
if git ls-remote --exit-code --heads origin dev >/dev/null 2>&1; then
# dev exists -> force it to match main (dev is a mirror of main)
git push --force origin FETCH_HEAD:refs/heads/dev
echo "dev reset to main" >> "$GITHUB_STEP_SUMMARY"
else
# dev missing (e.g. renamed to rc mid-cycle) -> recreate from main
git push origin FETCH_HEAD:refs/heads/dev
echo "dev created from main" >> "$GITHUB_STEP_SUMMARY"
fi
- run: echo "Cascade disabled — auto-release handles dev recreation"
+3 -8
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.CI
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/ci-generic.yml
# PATH: /.gitea/workflows/ci-generic.yml
# VERSION: 01.00.00
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
@@ -32,8 +32,6 @@ jobs:
lint:
name: Lint & Validate
runs-on: ubuntu-latest
# Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout
@@ -132,9 +130,6 @@ jobs:
name: Tests
runs-on: ubuntu-latest
needs: lint
# Run only when lint succeeded; always() forces evaluation so a skipped
# lint (e.g. template repos) skips this job cleanly instead of hanging.
if: ${{ always() && needs.lint.result == 'success' }}
steps:
- name: Checkout
+4 -4
View File
@@ -3,12 +3,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Universal
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a MokoGitea issue when a CI gate fails.
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
@@ -52,7 +52,7 @@ jobs:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/cleanup.yml
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/cleanup.yml
# VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
@@ -50,7 +50,7 @@ jobs:
for BRANCH in $BRANCHES; do
# Skip protected branches
case "$BRANCH" in
main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
main|master|develop|release/*|hotfix/*) continue ;;
esac
# Check if branch is merged into main
+126
View File
@@ -0,0 +1,126 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Deploy
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
name: "Universal: Deploy to Dev (Manual)"
on:
workflow_dispatch:
inputs:
clear_remote:
description: 'Delete all remote files before uploading'
required: false
default: 'false'
type: boolean
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
deploy:
name: SFTP Deploy to Dev
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Setup MokoStandards tools
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
id: check
env:
HOST: ${{ vars.DEV_FTP_HOST }}
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
PORT: ${{ vars.DEV_FTP_PORT }}
run: |
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "host=$HOST" >> "$GITHUB_OUTPUT"
REMOTE="${PATH_VAR%/}"
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
[ -z "$PORT" ] && PORT="22"
echo "port=$PORT" >> "$GITHUB_OUTPUT"
- name: Deploy via SFTP
if: steps.check.outputs.skip != 'true'
env:
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
> /tmp/sftp-config.json
if [ -n "$SFTP_KEY" ]; then
echo "$SFTP_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
fi
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
else
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Summary
if: always()
run: |
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
else
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
fi
+3 -3
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
+2 -2
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 02.64.00
# VERSION: 01.45.07
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+7 -7
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/notify.yml
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/notify.yml
# VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure
@@ -15,9 +15,9 @@ name: "Universal: Notifications"
on:
workflow_run:
workflows:
- "Universal: Build & Release"
- "Joomla: Extension CI"
- "Generic: Project CI"
- "Joomla Build & Release"
- "Joomla Extension CI"
- "Deploy"
types:
- completed
+7 -13
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge
@@ -126,8 +126,6 @@ jobs:
validate:
name: Validate PR
runs-on: ubuntu-latest
# Skip on template repos (Template-*) — no real manifest/source/changelog to validate.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout
@@ -149,12 +147,11 @@ jobs:
- name: Detect platform
id: platform
run: |
# Platform comes from the MokoGitea metadata API (public GET); manifest.xml is no longer used.
API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata"
PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)"
# Read platform from XML manifest (<platform> tag) or plain text fallback
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
[ -z "$PLATFORM" ] && PLATFORM="generic"
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Detected platform: $PLATFORM"
- name: Setup PHP
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
@@ -288,7 +285,7 @@ jobs:
# Block legacy raw/branch update server URLs on MokoGitea
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the MokoGitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS"
exit 1
fi
@@ -495,9 +492,6 @@ jobs:
name: Build RC Package
runs-on: ubuntu-latest
needs: [branch-policy, validate]
# Run only when both gates succeeded; always() forces evaluation so a skipped
# validate (e.g. template repos) skips this job cleanly instead of hanging.
if: ${{ always() && needs.branch-policy.result == 'success' && needs.validate.result == 'success' }}
steps:
- name: Trigger RC pre-release
+7 -9
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Validation
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
# VERSION: 01.00.00
@@ -20,7 +20,7 @@ permissions:
contents: read
env:
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
@@ -28,14 +28,12 @@ jobs:
validate-metadata:
name: "Validate Joomla Metadata"
runs-on: ubuntu-latest
# Skip on template repos (Template-*) — they have no real extension manifest to validate.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup MokoCLI tools
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -57,14 +55,14 @@ jobs:
- name: Validate metadata against Joomla manifest
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
php ${MOKO_CLI}/joomla_metadata_validate.php \
--path . \
--token "${MOKOGITEA_TOKEN}" \
--token "${GITEA_TOKEN}" \
--org "${GITEA_ORG}" \
--repo "${GITEA_REPO}" \
--api-base "${MOKOGITEA_URL}/api/v1" \
--api-base "${GITEA_URL}/api/v1" \
--ci
if [ $? -ne 0 ]; then
+7 -11
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Release
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.02.00
@@ -48,13 +48,9 @@ jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
if: >-
!startsWith(github.event.repository.name, 'Template-') &&
(
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
)
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
steps:
- name: Checkout
@@ -69,7 +65,7 @@ jobs:
run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup MokoCLI tools
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -156,8 +152,8 @@ jobs:
fi
# Commit version bump
git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-actions[bot]"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
+2 -2
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Universal
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/rc-revert.yml
# VERSION: 09.23.00
+4 -4
View File
@@ -6,9 +6,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
@@ -88,7 +88,7 @@ jobs:
# Hardcoded authorized users — always allowed
case "$ACTOR" in
jmiller|mokogitea-actions[bot])
jmiller|gitea-actions[bot])
ALLOWED=true
PERMISSION=admin
METHOD="hardcoded allowlist"
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow.Template
# INGROUP: MokoCLI.CI
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
# VERSION: 01.00.00
@@ -0,0 +1,81 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
name: "Universal: Workflow Sync Trigger"
on:
workflow_dispatch:
pull_request:
types: [closed]
branches:
- main
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
sync:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]'))
steps:
- name: Determine platform from repo name
id: platform
run: |
REPO="${{ github.event.repository.name }}"
case "$REPO" in
Template-Joomla) PLATFORM="joomla" ;;
Template-Dolibarr) PLATFORM="dolibarr" ;;
Template-Go) PLATFORM="go" ;;
Template-MCP) PLATFORM="mcp" ;;
Template-Generic) PLATFORM="" ;;
*) PLATFORM="" ;;
esac
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokocli
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
- name: Install PHP
run: |
if ! command -v php &> /dev/null; then
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
fi
- name: Install dependencies
run: |
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
- name: Run workflow sync
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
ARGS="--token ${MOKOGITEA_TOKEN}"
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
ARGS="${ARGS} --phase repos"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$PLATFORM" ]; then
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
+150 -6
View File
@@ -1,14 +1,158 @@
# Changelog
## [Unreleased]
## [02.64.00] --- 2026-07-14
### Added
- Customizable restore script filename per backup profile (reduces discoverability on remote servers)
- MokoRestore standalone mode: multi-ZIP selector when multiple backup archives are present
- MokoRestore preflight: Joomla installation detection warning before overwriting an existing site
- MokoRestore error handling: try/catch on fetch calls, HTTP status checks, JSON parse recovery
- Download button on individual backup record detail toolbar
- Profile column in backup records list links to the profile edit view
## [02.64.00] --- 2026-07-14
### Changed
- Moved download, browse archive, and view log actions from backup list rows into the individual backup record view
- Removed "Run Backup" / "Backup Now" buttons from profiles list, profile edit toolbar, and backup records view (backups are triggered from the dashboard only)
- Removed ordering field from profiles; default sort is now by ID ascending
- MokoRestore cleanup and security messages now reference the actual script filename instead of hardcoded "restore.php"
## [02.63.00] --- 2026-07-13
### Fixed
- Bootstrap 5 modal conversion for snapshots view (data-bs-dismiss, modal-footer, getOrCreateInstance)
- ntfy default URL changed from ntfy.sh to ntfy.mokoconsulting.tech
- Untranslated JFIELD_ORDERING_ASC / JFIELD_ORDERING_LABEL language keys replaced with component-specific keys
- Options page title now shows "MokoSuiteBackup Options" instead of raw language key
- Profile dropdown IDs in backup records and dashboard show "#ID — Title (type)" format
- MokoRestore stalling: unhandled promise rejections from network errors or non-JSON responses left UI in loading state
## [02.62.00] --- 2026-07-13
## [01.43.00] --- 2026-06-24
## [02.61.00] --- 2026-07-13
## [02.60.00] --- 2026-07-12
## [01.43.00] --- 2026-06-24
## [01.42.00] --- 2026-06-23
## [01.42.00] --- 2026-06-23
## [01.41.00] — 2026-06-23
### Added — Multi-Remote Storage
- New `#__mokosuitebackup_remotes` table for multiple destinations per profile
- Remote destinations UI: AJAX-driven add/edit/delete/toggle modal on profile edit
- Engine uploads to ALL enabled destinations (BackupEngine + SteppedBackupEngine)
- Migration auto-converts existing SFTP/S3/GDrive/FTP profile columns to new table
- Backward compatibility: falls back to legacy single-remote columns if table empty
- Secrets masked in API responses, merged from DB on save
### Added — Content Snapshots
- Lightweight JSON snapshots of articles, categories, and modules
- Includes tags, custom fields, workflow associations, field values
- Restore modes: Replace (clean slate), Merge (upsert), Selective (per-article)
- Snapshot retention: max count + max age with automatic cleanup
- Scheduled snapshot task via com_scheduler
- CLI: `mokosuitebackup:snapshot create|restore|list|delete`
- REST API: create, list, restore, delete, download snapshots
- Tabbed browse modal: Articles / Categories / Modules with item counts
### Added — SFTP Remote Storage
- SFTP support with SSH key file authentication (key stored base64 in database)
- Auth type dropdown: Password / Key File / Key File + Passphrase
- SshKeyField: file upload via FileReader, key never exposed in HTML
- SFTP remote directory browser for path selection
- `__KEEP_EXISTING__` sentinel preserves key on profile re-save
### Added — MokoRestore Wizard (9 steps)
- Per-table conflict resolution: Replace / Skip / Merge / Data Only
- Preset buttons: "All Replace", "All Skip", "Everything except users"
- Post-restore actions: reset passwords, hits, versions, sessions, cache
- Auto-detect sanitized passwords and prompt for reset (random temp password)
- Standalone mode: restore.php scans directory for ZIP files
- Wrapped mode: restore.php bundled inside backup ZIP
- Security gate with filesystem verification + path traversal protection
### Added — Data Sanitization
- Sanitize user passwords: replace hashes with invalid sentinel
- Sanitize user emails: replace with dummy values
- Clear session data: exclude `#__session` table
- Preserve super admin credentials (optional)
- GDPR-friendly backup sharing for demos and staging sites
### Added — Backup Engine
- Pre-flight validation: directory, disk space, extensions, credentials, running backups
- Auto-verify archive integrity after creation (ZIP, tar.gz, 7z)
- 7z archive format via system 7za/7z CLI binary with native encryption
- Streaming database dump to temp file (prevents OOM on large sites)
- S3 streaming upload via CURLOPT_PUT (prevents OOM)
- Graceful remote degradation: local backup preserved if upload fails
- DatabaseDumper::dumpToFile() for memory-efficient operation
### Added — Admin UI
- Dashboard: snapshot widget, 30-day backup trend chart, per-profile storage breakdown
- CPanel admin dashboard module (mod_mokosuitebackup_cpanel) with quick actions
- Backup type filter dropdown in backups list
- Backup comparison: select two backups for side-by-side diff
- Archive browser: view files inside backup without extracting
- Manual purge: delete backups older than a date with count preview
- Backup count badges on profile list
- "Do not navigate away" warning in backup/restore progress modals
- Clickable placeholder pills for backup directory and archive name fields
- Comprehensive help modal with absolute/relative/placeholder path documentation
- Placeholder resolution display with EXAMPLE prefix
- All placeholders UPPERCASE: [HOST], [SITE_NAME], [DATE], [DATETIME], etc.
### Added — CLI & API
- `mokosuitebackup:restore` with --files-only, --db-only, --password options
- `mokosuitebackup:snapshot` with create, restore, list, delete actions
- REST API for snapshots: create, list, restore, delete, download
- Profile credentials masked in API responses
### Added — Notifications & Logging
- Email/ntfy notifications for site restore, snapshot create/restore
- Joomla Action Logs for restore, snapshot, and snapshot restore events
- Global ntfy server/topic/token settings (fallback for profiles)
### Added — Security & Configuration
- Webcron secret field with CSPRNG generator + strength meter
- IP whitelist field with current IP detection + one-click "Add my IP"
- 10 ACL permissions with full enforcement audit across all controllers
- Config defaults: archive format, MokoRestore mode, sanitization settings
- Path traversal protection on all archive extraction (ZIP, tar.gz, JPA)
### Fixed
- CLI RestoreCommand passed wrong arguments (filepath instead of record ID)
- JPA path traversal: reject `../` in archive entry paths
- S3Uploader OOM: streaming upload instead of file_get_contents
- DatabaseDumper OOM: streaming to file instead of in-memory string
- AkeebaImporter: removed unserialize() (PHP object injection risk)
- BackupTable: delete DB row before file (prevents data loss)
- RestoreEngine: staging path sanitized with preg_replace
- API profiles: sensitive fields masked with `***`
- Webcron: missing return after sendJsonResponse on auth failure
- loadFormData(): cast array to object (PHP 8.x TypeError fix)
- MokoRestore data-only mode: uses REPLACE INTO for existing rows
- Plaintext archive deleted on encryption failure
- TarGzArchiver: intermediate .tar cleaned in finally block
- Install script: single-line comments converted to block comments
- Orphaned root-level webservices plugin files removed
- include_mokorestore column: TINYINT changed to VARCHAR(20)
- Snapshot fields_values: scoped dump and restore to com_content.article (previously destroyed values for contacts, users, etc.)
- Run Backup button: accept CSRF token from GET (fixes "token did not match" on profile edit)
- SFTP fields: moved into remote fieldset for showon visibility; removed required attr that blocked non-SFTP saves
- Script.php merge conflict markers resolved
## [01.24.00] — 2026-06-02
### Added
- Initial release: full-site backup and restore for Joomla 6
- Database, files, and configuration backup
- ZIP and tar.gz archive formats with AES-256 encryption
- Differential backups based on file manifests
- FTP/FTPS, S3, Google Drive remote storage
- MokoRestore standalone restore wizard
- CLI backup and restore commands
- REST API for remote management
- Scheduled tasks via com_scheduler
- Email and ntfy push notifications
- Per-profile retention, exclusions, and notifications
- Akeeba Backup migration tool
- Admin dashboard with system health checks
+1 -1
View File
@@ -4,7 +4,7 @@ Thank you for your interest in contributing to MokoSuiteBackup.
## Getting Started
1. Fork the repository on MokoGitea
1. Fork the repository on Gitea
2. Create a feature branch from `dev` (`feature/your-feature`)
3. Make your changes following the coding standards below
4. Submit a pull request targeting `dev`
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md
VERSION: 02.64.00
VERSION: 01.45.07
BRIEF: Security vulnerability reporting and handling policy
-->
+1 -1
View File
@@ -12,7 +12,7 @@
}
],
"require": {
"php": ">=8.3",
"php": ">=8.1",
"ext-zip": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
+37
View File
@@ -0,0 +1,37 @@
<?php
/**
* @package MokoSuiteBackup
* @subpackage plg_webservices_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
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Plugin\WebServices\MokoSuiteBackup\Extension\MokoSuiteBackupWebServices;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new MokoSuiteBackupWebServices(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('webservices', 'mokosuitebackup')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
@@ -15,6 +15,5 @@
<action name="mokosuitebackup.backup.purge" title="COM_MOKOSUITEBACKUP_ACTION_BACKUP_PURGE" />
<action name="mokosuitebackup.backup.compare" title="COM_MOKOSUITEBACKUP_ACTION_BACKUP_COMPARE" />
<action name="mokosuitebackup.backup.browse" title="COM_MOKOSUITEBACKUP_ACTION_BACKUP_BROWSE" />
<action name="mokosuitebackup.backup.cancel" title="COM_MOKOSUITEBACKUP_ACTION_BACKUP_CANCEL" />
</section>
</access>
@@ -13,7 +13,7 @@ namespace Joomla\Component\MokoSuiteBackup\Api\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\ApiController;
use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine;
class BackupsController extends ApiController
{
@@ -38,7 +38,8 @@ class BackupsController extends ApiController
$profileId = (int) ($data['profile'] ?? 1);
$description = $data['description'] ?? 'API backup ' . date('Y-m-d H:i:s');
$result = (new BackupRunner())->run($profileId, $description, 'api');
$engine = new BackupEngine();
$result = $engine->run($profileId, $description, 'api');
if ($result['success']) {
$this->app->setHeader('status', 200);
@@ -21,11 +21,9 @@ namespace Joomla\Component\MokoSuiteBackup\Api\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Controller\ApiController;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotEngine;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotPortable;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\SnapshotRestoreEngine;
class SnapshotsController extends ApiController
@@ -161,11 +159,11 @@ class SnapshotsController extends ApiController
$data = json_decode($this->input->json->getRaw(), true) ?: [];
$mode = (string) ($data['mode'] ?? 'replace');
$mode = $data['mode'] ?? 'replace';
$contentTypes = $data['content_types'] ?? [];
// Accept the external mode names too; the engine normalises + validates.
if (!in_array($mode, ['replace', 'merge', 'overwrite', 'create', 'duplicate'], true)) {
// Enforce valid restore mode
if (!in_array($mode, ['replace', 'merge'], true)) {
$mode = 'replace';
}
@@ -185,86 +183,6 @@ class SnapshotsController extends ApiController
return $this;
}
/**
* Inject a snapshot payload directly into this site (master → slave).
* POST /api/index.php/v1/mokosuitebackup/snapshot/inject
*
* Body: {
* "snapshot": { version, content_types, tables }, // the payload (required)
* "mode": "overwrite" | "create" | "duplicate", // optional; defaults to the site's snapshot_inject_mode
* "content_types": [ ... ], // optional filter
* "description": "..." // optional
* }
*/
public function inject(): static
{
if (!$this->app->getIdentity()->authorise('mokosuitebackup.snapshot.manage', 'com_mokosuitebackup')) {
$this->app->setHeader('status', 403);
echo json_encode(['errors' => [['title' => 'Access denied']]]);
$this->app->close();
return $this;
}
$params = ComponentHelper::getParams('com_mokosuitebackup');
// The receiving (slave) site must explicitly allow injection.
if (!(int) $params->get('snapshot_allow_inject', 0)) {
$this->app->setHeader('status', 403);
echo json_encode(['errors' => [['title' => 'Snapshot injection is disabled on this site']]]);
$this->app->close();
return $this;
}
$body = json_decode($this->input->json->getRaw(), true) ?: [];
$snapshot = $body['snapshot'] ?? null;
if (!is_array($snapshot)) {
$this->app->setHeader('status', 400);
echo json_encode(['errors' => [['title' => 'Missing "snapshot" payload']]]);
$this->app->close();
return $this;
}
// Explicit mode wins; otherwise the site default from Options.
$mode = (string) ($body['mode'] ?? $params->get('snapshot_inject_mode', 'overwrite'));
$contentTypes = $body['content_types'] ?? [];
$description = (string) ($body['description'] ?? 'Injected snapshot');
// Materialise a local record from the payload, then restore it.
$ingest = SnapshotPortable::ingestData($snapshot, $description);
if (empty($ingest['success'])) {
$this->app->setHeader('status', 422);
echo json_encode(['errors' => [['title' => $ingest['message']]]]);
$this->app->close();
return $this;
}
$engine = new SnapshotRestoreEngine();
$result = $engine->restore((int) $ingest['id'], $mode, $contentTypes);
$result['snapshot_id'] = (int) $ingest['id'];
if (!empty($ingest['warnings'])) {
$result['warnings'] = array_merge($result['warnings'] ?? [], $ingest['warnings']);
}
if ($result['success']) {
$this->app->setHeader('status', 200);
echo json_encode(['data' => $result]);
} else {
$this->app->setHeader('status', 500);
echo json_encode(['errors' => [['title' => $result['message']]], 'data' => $result]);
}
$this->app->close();
return $this;
}
/**
* Delete a snapshot record and its data file (DELETE /api/index.php/v1/mokosuitebackup/snapshot/:id)
*/
@@ -24,6 +24,7 @@ class JsonapiView extends BaseApiView
'origin',
'backup_type',
'archivename',
'absolute_path',
'total_size',
'db_size',
'files_count',
@@ -30,7 +30,7 @@ if (!defined('JPATH_BASE')) {
require_once JPATH_BASE . '/includes/framework.php';
use Joomla\CMS\Factory;
use Joomla\Component\MokoSuiteBackup\Administrator\Service\BackupRunner;
use Joomla\Component\MokoSuiteBackup\Administrator\Engine\BackupEngine;
// Parse CLI arguments
$profileId = 1;
@@ -56,7 +56,8 @@ echo "Profile: {$profileId}\n";
echo "Description: {$description}\n";
echo "Starting backup...\n\n";
$result = (new BackupRunner())->run($profileId, $description, 'cli');
$engine = new BackupEngine();
$result = $engine->run($profileId, $description, 'cli');
if ($result['success']) {
echo "SUCCESS: " . $result['message'] . "\n";
@@ -206,31 +206,6 @@
/>
</fieldset>
<fieldset name="snapshot_transfer" label="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_TRANSFER">
<field
name="snapshot_inject_mode"
type="list"
label="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE"
description="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE_DESC"
default="overwrite"
>
<option value="overwrite">COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_OVERWRITE</option>
<option value="create">COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_CREATE</option>
<option value="duplicate">COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_DUPLICATE</option>
</field>
<field
name="snapshot_allow_inject"
type="radio"
label="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT"
description="COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT_DESC"
default="0"
class="btn-group"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="notifications" label="COM_MOKOJOOMBACKUP_CONFIG_NOTIFICATIONS">
<field
name="notify_email"
@@ -15,7 +15,6 @@
>
<option value="">COM_MOKOJOOMBACKUP_FILTER_STATUS_ALL</option>
<option value="complete">COM_MOKOJOOMBACKUP_STATUS_COMPLETE</option>
<option value="warning">COM_MOKOJOOMBACKUP_STATUS_WARNING</option>
<option value="running">COM_MOKOJOOMBACKUP_STATUS_RUNNING</option>
<option value="fail">COM_MOKOJOOMBACKUP_STATUS_FAIL</option>
<option value="pending">COM_MOKOJOOMBACKUP_STATUS_PENDING</option>
@@ -205,9 +205,113 @@
/>
</fieldset>
<!-- The "Keep local copy" setting is now configured per remote destination
(in the Add/Edit Destination modal), so the profile-level field and its
"Remote" tab have been removed. -->
<fieldset name="remote" label="COM_MOKOJOOMBACKUP_FIELDSET_REMOTE">
<field
name="remote_legacy_note"
type="note"
label=""
description="COM_MOKOJOOMBACKUP_REMOTE_LEGACY_NOTE"
class="alert alert-info small"
/>
<field
name="remote_storage"
type="list"
label="COM_MOKOJOOMBACKUP_FIELD_REMOTE_STORAGE"
description="COM_MOKOJOOMBACKUP_FIELD_REMOTE_STORAGE_DESC"
default="none"
>
<option value="none">COM_MOKOJOOMBACKUP_REMOTE_NONE</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>
</field>
<field
name="remote_keep_local"
type="radio"
label="COM_MOKOJOOMBACKUP_FIELD_KEEP_LOCAL"
description="COM_MOKOJOOMBACKUP_FIELD_KEEP_LOCAL_DESC"
default="1"
class="btn-group"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<!-- SFTP fields (shown when remote_storage = sftp) -->
<field
name="sftp_host"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_HOST_DESC"
maxlength="255"
showon="remote_storage:sftp"
/>
<field
name="sftp_port"
type="number"
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_PORT"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_PORT_DESC"
default="22"
min="1"
max="65535"
showon="remote_storage:sftp"
/>
<field
name="sftp_username"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME_DESC"
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"
/>
<field
name="sftp_key_data"
type="SshKey"
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC"
filter="raw"
showon="remote_storage:sftp[AND]sftp_auth_type:key,key_passphrase"
addfieldprefix="Joomla\Component\MokoSuiteBackup\Administrator\Field"
/>
<field
name="sftp_passphrase"
type="password"
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"
/>
<field
name="sftp_path"
type="SftpPath"
label="COM_MOKOJOOMBACKUP_FIELD_SFTP_PATH"
description="COM_MOKOJOOMBACKUP_FIELD_SFTP_PATH_DESC"
default="/backups"
maxlength="512"
showon="remote_storage:sftp"
addfieldprefix="Joomla\Component\MokoSuiteBackup\Administrator\Field"
/>
</fieldset>
<fieldset name="retention" label="COM_MOKOJOOMBACKUP_FIELDSET_RETENTION">
<field
@@ -304,4 +408,157 @@
/>
</fieldset>
<fieldset name="ftp" label="COM_MOKOJOOMBACKUP_FIELDSET_FTP">
<field
name="ftp_host"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_FTP_HOST"
description="COM_MOKOJOOMBACKUP_FIELD_FTP_HOST_DESC"
maxlength="255"
showon="remote_storage:ftp"
/>
<field
name="ftp_port"
type="number"
label="COM_MOKOJOOMBACKUP_FIELD_FTP_PORT"
description="COM_MOKOJOOMBACKUP_FIELD_FTP_PORT_DESC"
default="21"
min="1"
max="65535"
showon="remote_storage:ftp"
/>
<field
name="ftp_username"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_FTP_USERNAME"
maxlength="255"
showon="remote_storage:ftp"
/>
<field
name="ftp_password"
type="password"
label="COM_MOKOJOOMBACKUP_FIELD_FTP_PASSWORD"
maxlength="255"
showon="remote_storage:ftp"
/>
<field
name="ftp_path"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_FTP_PATH"
description="COM_MOKOJOOMBACKUP_FIELD_FTP_PATH_DESC"
default="/backups"
maxlength="512"
showon="remote_storage:ftp"
/>
<field
name="ftp_passive"
type="radio"
label="COM_MOKOJOOMBACKUP_FIELD_FTP_PASSIVE"
description="COM_MOKOJOOMBACKUP_FIELD_FTP_PASSIVE_DESC"
default="1"
class="btn-group"
showon="remote_storage:ftp"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="ftp_ssl"
type="radio"
label="COM_MOKOJOOMBACKUP_FIELD_FTP_SSL"
description="COM_MOKOJOOMBACKUP_FIELD_FTP_SSL_DESC"
default="0"
class="btn-group"
showon="remote_storage:ftp"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="google_drive" label="COM_MOKOJOOMBACKUP_FIELDSET_GDRIVE">
<field
name="gdrive_client_id"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_GDRIVE_CLIENT_ID"
description="COM_MOKOJOOMBACKUP_FIELD_GDRIVE_CLIENT_ID_DESC"
maxlength="255"
showon="remote_storage:google_drive"
/>
<field
name="gdrive_client_secret"
type="password"
label="COM_MOKOJOOMBACKUP_FIELD_GDRIVE_CLIENT_SECRET"
maxlength="255"
showon="remote_storage:google_drive"
/>
<field
name="gdrive_refresh_token"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_GDRIVE_REFRESH_TOKEN"
description="COM_MOKOJOOMBACKUP_FIELD_GDRIVE_REFRESH_TOKEN_DESC"
maxlength="512"
showon="remote_storage:google_drive"
/>
<field
name="gdrive_folder_id"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_GDRIVE_FOLDER_ID"
description="COM_MOKOJOOMBACKUP_FIELD_GDRIVE_FOLDER_ID_DESC"
maxlength="255"
showon="remote_storage:google_drive"
/>
</fieldset>
<fieldset name="s3" label="COM_MOKOJOOMBACKUP_FIELDSET_S3">
<field
name="s3_endpoint"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_S3_ENDPOINT"
description="COM_MOKOJOOMBACKUP_FIELD_S3_ENDPOINT_DESC"
maxlength="512"
hint="https://s3.amazonaws.com"
showon="remote_storage:s3"
/>
<field
name="s3_region"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_S3_REGION"
description="COM_MOKOJOOMBACKUP_FIELD_S3_REGION_DESC"
default="us-east-1"
maxlength="50"
showon="remote_storage:s3"
/>
<field
name="s3_access_key"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_S3_ACCESS_KEY"
maxlength="255"
showon="remote_storage:s3"
/>
<field
name="s3_secret_key"
type="password"
label="COM_MOKOJOOMBACKUP_FIELD_S3_SECRET_KEY"
maxlength="255"
showon="remote_storage:s3"
/>
<field
name="s3_bucket"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_S3_BUCKET"
description="COM_MOKOJOOMBACKUP_FIELD_S3_BUCKET_DESC"
maxlength="255"
showon="remote_storage:s3"
/>
<field
name="s3_path"
type="text"
label="COM_MOKOJOOMBACKUP_FIELD_S3_PATH"
description="COM_MOKOJOOMBACKUP_FIELD_S3_PATH_DESC"
default="/backups"
maxlength="512"
showon="remote_storage:s3"
/>
</fieldset>
</form>
@@ -5,7 +5,6 @@
; @license GPL-3.0-or-later
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
COM_MOKOJOOMBACKUP_CONFIGURATION="MokoSuiteBackup Options"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla"
@@ -23,7 +22,7 @@ COM_MOKOSUITEBACKUP_ACTION_BACKUP_RESTORE="Restore Backup"
COM_MOKOSUITEBACKUP_ACTION_BACKUP_RESTORE_DESC="Allows users in this group to restore the site from a backup archive. This is a destructive operation that overwrites the current site."
; Dashboard view
COM_MOKOJOOMBACKUP_DASHBOARD_TITLE="Dashboard"
COM_MOKOJOOMBACKUP_DASHBOARD_TITLE="MokoSuiteBackup Dashboard"
COM_MOKOJOOMBACKUP_DASHBOARD_LAST_BACKUP="Last Backup"
COM_MOKOJOOMBACKUP_DASHBOARD_NO_BACKUPS="No backups yet"
COM_MOKOJOOMBACKUP_DASHBOARD_NEXT_SCHEDULED="Next Scheduled"
@@ -45,14 +44,14 @@ COM_MOKOJOOMBACKUP_DASHBOARD_BACKUP_TREND="Backup Trend (30 days)"
; Backups view
COM_MOKOJOOMBACKUP_BACKUPS_N_ITEMS_DELETED="%d backup records deleted."
COM_MOKOJOOMBACKUP_BACKUPS_N_ITEMS_DELETED_1="%d backup record deleted."
COM_MOKOJOOMBACKUP_BACKUPS_TITLE="Records"
COM_MOKOJOOMBACKUP_BACKUPS_TITLE="Backup Records"
COM_MOKOJOOMBACKUP_BACKUPS_TABLE_CAPTION="Table of backup records"
COM_MOKOJOOMBACKUP_NO_BACKUPS="No backups found. Click 'Backup Now' to create your first backup."
COM_MOKOJOOMBACKUP_TOOLBAR_BACKUP_NOW="Backup Now"
COM_MOKOJOOMBACKUP_DOWNLOAD="Download"
; Backup detail view
COM_MOKOJOOMBACKUP_BACKUP_DETAIL="Detail"
COM_MOKOJOOMBACKUP_BACKUP_DETAIL="Backup Detail"
COM_MOKOJOOMBACKUP_VIEW_LOG="Backup Log"
COM_MOKOJOOMBACKUP_BROWSE_ARCHIVE="Browse Archive Contents"
COM_MOKOJOOMBACKUP_BROWSE_COL_NAME="Name"
@@ -76,7 +75,7 @@ COM_MOKOJOOMBACKUP_FIELD_DB_SIZE="DB Size"
COM_MOKOJOOMBACKUP_FIELD_REMOTE="Remote Path"
; Profiles view
COM_MOKOJOOMBACKUP_PROFILES_TITLE="Profiles"
COM_MOKOJOOMBACKUP_PROFILES_TITLE="Backup Profiles"
COM_MOKOJOOMBACKUP_PROFILES_TABLE_CAPTION="Table of backup profiles"
COM_MOKOJOOMBACKUP_NO_PROFILES="No backup profiles found."
COM_MOKOJOOMBACKUP_PROFILE_NEW="New Profile"
@@ -208,7 +207,6 @@ COM_MOKOJOOMBACKUP_TYPE_DIFFERENTIAL="Differential (changed files + full DB)"
; Status labels
COM_MOKOJOOMBACKUP_STATUS_COMPLETE="Complete"
COM_MOKOJOOMBACKUP_STATUS_WARNING="Warning"
COM_MOKOJOOMBACKUP_STATUS_RUNNING="Running"
COM_MOKOJOOMBACKUP_STATUS_FAIL="Failed"
COM_MOKOJOOMBACKUP_STATUS_PENDING="Pending"
@@ -251,9 +249,9 @@ COM_MOKOJOOMBACKUP_FIELD_NOTIFY_FAILURE_DESC="Send an email when a backup fails.
; Retention
COM_MOKOJOOMBACKUP_FIELDSET_RETENTION="Retention"
COM_MOKOJOOMBACKUP_FIELD_RETENTION_DAYS="Keep Backups (days)"
COM_MOKOJOOMBACKUP_FIELD_RETENTION_DAYS_DESC="Delete completed backups from this profile older than this many days. Set to 0 for unlimited (keep by age disabled)."
COM_MOKOJOOMBACKUP_FIELD_RETENTION_DAYS_DESC="Delete completed backups from this profile older than this many days. Set to 0 to use the global default from component options."
COM_MOKOJOOMBACKUP_FIELD_RETENTION_COUNT="Keep Backups (count)"
COM_MOKOJOOMBACKUP_FIELD_RETENTION_COUNT_DESC="Maximum number of completed backups to keep for this profile. Oldest are removed first. Set to 0 for unlimited (keep by count disabled)."
COM_MOKOJOOMBACKUP_FIELD_RETENTION_COUNT_DESC="Maximum number of completed backups to keep for this profile. Oldest are removed first. Set to 0 to use the global default from component options."
COM_MOKOJOOMBACKUP_FIELD_NTFY_SPACER_DESC="<strong>Push Notifications (ntfy)</strong> — Send instant push notifications to your phone or desktop via <a href='https://ntfy.sh' target='_blank'>ntfy.sh</a> or a self-hosted ntfy server."
COM_MOKOJOOMBACKUP_FIELD_NTFY_TOPIC="ntfy Topic"
@@ -331,34 +329,9 @@ COM_MOKOJOOMBACKUP_CONFIG_SHOW_UPDATE_NOTICE_DESC="Display the update site confi
COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates."
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
; Full-screen backup progress view (runbackup)
COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE="Backing Up"
COM_MOKOJOOMBACKUP_RUNBACKUP_PROFILE="Profile: %s"
COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING="Starting backup…"
COM_MOKOJOOMBACKUP_RUNBACKUP_RUNNING="Backing up…"
COM_MOKOJOOMBACKUP_RUNBACKUP_COMPLETE="Backup complete"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUING="Backup complete — continuing…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed"
COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes."
COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue."
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE="Backup failed — the update was not started. Retry the backup, or go back without updating."
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE_DESC="Default maximum age for backup records. Used by the system plugin and CLI cleanup command."
@@ -407,8 +380,6 @@ COM_MOKOJOOMBACKUP_FIELD_NOTIFY_USER_GROUPS_DESC="Select Joomla user groups whos
; Dashboard warnings
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING_TITLE="Backup directory is inside the web root"
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING="One or more profiles store backups in the default directory inside the web root. This may expose backup archives if .htaccess is not supported. Move backups to a directory outside the web root for better security."
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING_TITLE="Site URL (live_site) is not set"
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING="Your Global Configuration has no Site URL (live_site). CLI and scheduled backups can't reliably determine the site's domain (archive names fall back to the server hostname), and some URLs may be generated incorrectly. Set Site URL in System → Global Configuration to your full site address including https://."
COM_MOKOJOOMBACKUP_WEB_ACCESSIBLE_WARNING="This backup is stored inside the web root and may be directly downloadable if .htaccess is not supported."
@@ -479,8 +450,6 @@ COM_MOKOSUITEBACKUP_ACTION_BACKUP_COMPARE="Compare Backups"
COM_MOKOSUITEBACKUP_ACTION_BACKUP_COMPARE_DESC="Allows users to compare two backup records side-by-side."
COM_MOKOSUITEBACKUP_ACTION_BACKUP_BROWSE="Browse Archives"
COM_MOKOSUITEBACKUP_ACTION_BACKUP_BROWSE_DESC="Allows users to view file listings inside backup archives without extracting."
COM_MOKOSUITEBACKUP_ACTION_BACKUP_CANCEL="Cancel Stalled Backup"
COM_MOKOSUITEBACKUP_ACTION_BACKUP_CANCEL_DESC="Allows users to cancel backup records stuck in running status and clean up partial archive files."
; Snapshot ACL
COM_MOKOSUITEBACKUP_ACTION_SNAPSHOT_MANAGE="Manage Snapshots"
@@ -504,22 +473,6 @@ COM_MOKOJOOMBACKUP_WEBCRON_IP_NONE="No IP restrictions — any IP can trigger we
COM_MOKOJOOMBACKUP_WEBCRON_IP_PLACEHOLDER="Enter IP address"
COM_MOKOJOOMBACKUP_WEBCRON_IP_ADD="Add"
; Snapshot transfer / injection (#237)
COM_MOKOJOOMBACKUP_SNAPSHOT_DOWNLOAD="Download portable snapshot (.msbsnap)"
COM_MOKOJOOMBACKUP_SNAPSHOT_NOT_FOUND="Snapshot not found."
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT="Import Snapshot"
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_DESC="Upload a .msbsnap file exported from another site. It is added as a snapshot here, which you can then restore."
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_FILE="Snapshot file (.msbsnap)"
COM_MOKOJOOMBACKUP_SNAPSHOT_IMPORT_NO_FILE="No snapshot file was uploaded, or the upload failed."
COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_OVERWRITE="Overwrite — replace matching items (master wins)"
COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_CREATE="Create — skip existing, add only new items"
COM_MOKOJOOMBACKUP_SNAPSHOT_MODE_DUPLICATE="Duplicate — add as new copies (remapped IDs)"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_TRANSFER="Snapshot Transfer / Injection"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE="Default Conflict Mode"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_INJECT_MODE_DESC="Default mode used when a snapshot is injected via the API without an explicit mode."
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT="Allow Snapshot Injection (API)"
COM_MOKOJOOMBACKUP_CONFIG_SNAPSHOT_ALLOW_INJECT_DESC="Allow a remote master site to inject snapshots directly into this site via the Web Services API (POST /snapshot/inject). Off by default; requires an API token with snapshot-manage permission."
; Snapshot browse / detail view
COM_MOKOJOOMBACKUP_SNAPSHOT_BROWSE="Browse Snapshot"
COM_MOKOJOOMBACKUP_SNAPSHOT_TAB_ARTICLES="Articles"
@@ -547,12 +500,6 @@ COM_MOKOJOOMBACKUP_PURGE_INVALID_DATE="Invalid date. Please select a valid date.
COM_MOKOJOOMBACKUP_PURGE_SUCCESS="%d backup(s) purged successfully."
COM_MOKOJOOMBACKUP_PURGE_PARTIAL="%d backup(s) purged, but %d could not be deleted."
; Cancel Stalled Backup
COM_MOKOJOOMBACKUP_TOOLBAR_CANCEL_STALLED="Cancel Stalled"
COM_MOKOJOOMBACKUP_CANCEL_NONE_SELECTED="No backup records selected."
COM_MOKOJOOMBACKUP_CANCEL_NONE_RUNNING="None of the selected backups are in running status."
COM_MOKOJOOMBACKUP_CANCEL_SUCCESS="%d stalled backup(s) cancelled."
; Remote Destinations (multi-remote)
COM_MOKOJOOMBACKUP_REMOTE_DESTINATIONS="Remote Destinations"
COM_MOKOJOOMBACKUP_REMOTE_ADD="Add Destination"
@@ -5,7 +5,6 @@
; @license GPL-3.0-or-later
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla — database, files, and configuration."
COM_MOKOJOOMBACKUP_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS="Backup Records"
@@ -5,7 +5,6 @@
; @license GPL-3.0-or-later
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla"
COM_MOKOJOOMBACKUP_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS="Backup Records"
@@ -19,7 +18,7 @@ COM_MOKOSUITEBACKUP_ACTION_BACKUP_DOWNLOAD_DESC="Allows users in this group to d
COM_MOKOSUITEBACKUP_ACTION_BACKUP_RESTORE="Restore Backup"
COM_MOKOSUITEBACKUP_ACTION_BACKUP_RESTORE_DESC="Allows users in this group to restore the site from a backup archive. This is a destructive operation that overwrites the current site."
COM_MOKOJOOMBACKUP_DASHBOARD_TITLE="Dashboard"
COM_MOKOJOOMBACKUP_DASHBOARD_TITLE="MokoSuiteBackup Dashboard"
COM_MOKOJOOMBACKUP_DASHBOARD_LAST_BACKUP="Last Backup"
COM_MOKOJOOMBACKUP_DASHBOARD_NO_BACKUPS="No backups yet"
COM_MOKOJOOMBACKUP_DASHBOARD_NEXT_SCHEDULED="Next Scheduled"
@@ -31,8 +30,8 @@ COM_MOKOJOOMBACKUP_DASHBOARD_QUICK_ACTIONS="Quick Actions"
COM_MOKOJOOMBACKUP_DASHBOARD_SCHEDULED_TASKS="Scheduled Tasks"
COM_MOKOJOOMBACKUP_DASHBOARD_UPDATE_SITE="Update Site"
COM_MOKOJOOMBACKUP_DASHBOARD_SYSTEM_HEALTH="System Health"
COM_MOKOJOOMBACKUP_BACKUPS_TITLE="Records"
COM_MOKOJOOMBACKUP_PROFILES_TITLE="Profiles"
COM_MOKOJOOMBACKUP_BACKUPS_TITLE="Backup Records"
COM_MOKOJOOMBACKUP_PROFILES_TITLE="Backup Profiles"
COM_MOKOJOOMBACKUP_TOOLBAR_BACKUP_NOW="Backup Now"
COM_MOKOJOOMBACKUP_NO_BACKUPS="No backups found. Click 'Backup Now' to create your first backup."
COM_MOKOJOOMBACKUP_NO_PROFILES="No backup profiles found."
@@ -53,34 +52,9 @@ COM_MOKOJOOMBACKUP_CONFIG_SHOW_UPDATE_NOTICE_DESC="Display the update site confi
COM_MOKOJOOMBACKUP_CONFIG_PREACTION="Pre-action Backups"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE="Backup Before Extension Update"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UPDATE_DESC="Automatically run a full backup before any extension is updated. Uses the default profile. Throttled to once per 10 minutes to prevent duplicate backups during batch updates."
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT="Auto-run on Update click"
COM_MOKOJOOMBACKUP_CONFIG_PREUPDATE_AUTO_INTERCEPT_DESC="When enabled, clicking the Update button on the Joomla Update or Extensions update page first pops the backup progress modal, runs the pre-update backup, then automatically continues the update. When disabled, a notice with a manual 'Back up now' button is shown instead."
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL="Backup Before Extension Uninstall"
COM_MOKOJOOMBACKUP_CONFIG_BACKUP_BEFORE_UNINSTALL_DESC="Automatically run a full backup before any extension is uninstalled. Uses the default profile. Throttled to once per 10 minutes."
; Full-screen backup progress view (runbackup)
COM_MOKOJOOMBACKUP_RUNBACKUP_TITLE="Backing Up"
COM_MOKOJOOMBACKUP_RUNBACKUP_PROFILE="Profile: %s"
COM_MOKOJOOMBACKUP_RUNBACKUP_STARTING="Starting backup…"
COM_MOKOJOOMBACKUP_RUNBACKUP_RUNNING="Backing up…"
COM_MOKOJOOMBACKUP_RUNBACKUP_COMPLETE="Backup complete"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUING="Backup complete — continuing…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED="Backup failed"
COM_MOKOJOOMBACKUP_RUNBACKUP_RETRY="Retry backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_ANYWAY="Continue without backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_BACK_TO_DASHBOARD="Back to dashboard"
COM_MOKOJOOMBACKUP_RUNBACKUP_VIEW_RECORD="View backup record"
COM_MOKOJOOMBACKUP_RUNBACKUP_LEAVE_WARNING="A backup is in progress. Leaving now will cancel it."
COM_MOKOJOOMBACKUP_RUNBACKUP_AUTOCONTINUE="Automatically continue the update when the backup finishes"
COM_MOKOJOOMBACKUP_RUNBACKUP_DONT_CLOSE="Please do not close this window or switch to another window until the backup finishes."
COM_MOKOJOOMBACKUP_RUNBACKUP_UPDATE_RUNNING="Backup complete. The update is now running — please wait and do not close this window…"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL="Cancel backup"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM1="Cancel the backup that is in progress?"
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCEL_CONFIRM2="Are you sure? The backup will be stopped and the update will NOT continue."
COM_MOKOJOOMBACKUP_RUNBACKUP_CANCELLING="Cancelling…"
COM_MOKOJOOMBACKUP_RUNBACKUP_FAILED_NO_UPDATE="Backup failed — the update was not started. Retry the backup, or go back without updating."
COM_MOKOJOOMBACKUP_RUNBACKUP_CONTINUE_UPDATE="Continue the update"
COM_MOKOJOOMBACKUP_CONFIG_CLEANUP="Cleanup Defaults"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE="Max Backup Age (days)"
COM_MOKOJOOMBACKUP_CONFIG_MAX_AGE_DESC="Default maximum age for backup records. Used by the system plugin and CLI cleanup command."
@@ -98,8 +72,6 @@ COM_MOKOJOOMBACKUP_FOLDER_NOT_FOUND="Directory not found"
COM_MOKOJOOMBACKUP_BACKUP_DIR_DEFAULT="Default (inside web root)"
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING_TITLE="Backup directory is inside the web root"
COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING="One or more profiles store backups in the default directory inside the web root. This may expose backup archives if .htaccess is not supported. Move backups to a directory outside the web root for better security."
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING_TITLE="Site URL (live_site) is not set"
COM_MOKOJOOMBACKUP_DASHBOARD_LIVESITE_WARNING="Your Global Configuration has no Site URL (live_site). CLI and scheduled backups can't reliably determine the site's domain (archive names fall back to the server hostname), and some URLs may be generated incorrectly. Set Site URL in System → Global Configuration to your full site address including https://."
COM_MOKOJOOMBACKUP_WEB_ACCESSIBLE_WARNING="This backup is stored inside the web root and may be directly downloadable if .htaccess is not supported."
COM_MOKOJOOMBACKUP_FOLDER_EXISTS="Directory exists"
COM_MOKOJOOMBACKUP_FOLDER_NOT_FOUND="Directory not found"
@@ -144,27 +116,3 @@ COM_MOKOJOOMBACKUP_PURGE_NONE_FOUND="No completed backups found before the selec
COM_MOKOJOOMBACKUP_PURGE_INVALID_DATE="Invalid date. Please select a valid date."
COM_MOKOJOOMBACKUP_PURGE_SUCCESS="%d backup(s) purged successfully."
COM_MOKOJOOMBACKUP_PURGE_PARTIAL="%d backup(s) purged, but %d could not be deleted."
; Cancel Stalled Backup
COM_MOKOJOOMBACKUP_TOOLBAR_CANCEL_STALLED="Cancel Stalled"
COM_MOKOJOOMBACKUP_CANCEL_NONE_SELECTED="No backup records selected."
COM_MOKOJOOMBACKUP_CANCEL_NONE_RUNNING="None of the selected backups are in running status."
COM_MOKOJOOMBACKUP_CANCEL_SUCCESS="%d stalled backup(s) cancelled."
; Backup status
COM_MOKOJOOMBACKUP_STATUS_WARNING="Warning"
; Delete feedback
COM_MOKOJOOMBACKUP_BACKUPS_N_ITEMS_DELETED="%d backup records deleted."
COM_MOKOJOOMBACKUP_BACKUPS_N_ITEMS_DELETED_1="%d backup record deleted."
; ACL - Cancel
COM_MOKOSUITEBACKUP_ACTION_BACKUP_CANCEL="Cancel Stalled Backup"
COM_MOKOSUITEBACKUP_ACTION_BACKUP_CANCEL_DESC="Allows users to cancel backup records stuck in running status and clean up partial archive files."
; Retention (per-profile)
COM_MOKOJOOMBACKUP_FIELDSET_RETENTION="Retention"
COM_MOKOJOOMBACKUP_FIELD_RETENTION_DAYS="Keep Backups (days)"
COM_MOKOJOOMBACKUP_FIELD_RETENTION_DAYS_DESC="Delete completed backups from this profile older than this many days. Set to 0 for unlimited (keep by age disabled)."
COM_MOKOJOOMBACKUP_FIELD_RETENTION_COUNT="Keep Backups (count)"
COM_MOKOJOOMBACKUP_FIELD_RETENTION_COUNT_DESC="Maximum number of completed backups to keep for this profile. Oldest are removed first. Set to 0 for unlimited (keep by count disabled)."
@@ -5,7 +5,6 @@
; @license GPL-3.0-or-later
COM_MOKOJOOMBACKUP="MokoSuiteBackup"
COM_MOKOJOOMBACKUP_SHORT="Backup"
COM_MOKOJOOMBACKUP_DESCRIPTION="Full-site backup and restore for Joomla — database, files, and configuration."
COM_MOKOJOOMBACKUP_SUBMENU_DASHBOARD="Dashboard"
COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS="Backup Records"
@@ -1,33 +0,0 @@
/**
* MokoSuiteBackup — admin styles (loaded via the Joomla Web Asset Manager).
*/
/* Add/Edit Destination modal.
* The modal content is wrapped in a Bootstrap .container-fluid because
* Bootstrap's .modal-content has no padding of its own. Give that container a
* single, comfortable 1rem frame and drop the inner sections' horizontal padding
* so it isn't doubled. */
#remoteModal .modal-content > .container-fluid {
padding: 1rem;
}
#remoteModal .modal-header,
#remoteModal .modal-body,
#remoteModal .modal-footer {
padding-left: 0;
padding-right: 0;
}
#remoteModal .modal-body .row + .row,
#remoteModal .remote-type-fields {
margin-top: 0.25rem;
}
#remoteModal hr {
margin: 1rem 0;
opacity: 0.12;
}
#remoteModal .form-label {
margin-bottom: 0.35rem;
}
@@ -1,86 +0,0 @@
/*
* @package MokoSuiteBackup
* @subpackage com_mokosuitebackup
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*
* Full-screen backup progress screen (view=runbackup). Loaded via the Joomla
* Web Asset Manager (asset com_mokosuitebackup.runbackup).
*/
/* Full-screen backup runs on a black backdrop. */
html,
body {
background-color: #000 !important;
}
/* Colours are pulled from the Atum admin template's own CSS custom properties
so the screen matches whatever theme the site runs, with Bootstrap and then
a literal as progressive fallbacks. */
.msb-runbackup {
--msb-accent: var(--template-link-color, var(--bs-link-color, #2a69b8));
--msb-accent2: var(--template-special-color, var(--msb-accent));
--msb-success: var(--template-success-color, var(--bs-success, #2f7d32));
--msb-danger: var(--template-danger-color, var(--bs-danger, #c52128));
--msb-track: var(--template-bg-light, var(--bs-gray-200, #edf0f5));
padding: 0 1rem;
}
/* overflow:hidden clips the full-bleed bar to the card's rounded corners.
The card's gray background comes from a Bootstrap utility class on the
element itself (see the template), not a rule here. */
.msb-runbackup .card {
overflow: hidden;
}
.msb-rb-progress {
height: 2.5rem;
background: var(--msb-track);
overflow: hidden;
}
.msb-rb-bar {
position: relative;
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
font-size: .95rem;
white-space: nowrap;
background-color: var(--msb-accent);
background-image: linear-gradient(90deg, var(--msb-accent), var(--msb-accent2));
transition: width .35s ease;
}
.msb-rb-bar.is-animated::after {
content: "";
position: absolute;
inset: 0;
background-image: linear-gradient(45deg, rgba(255, 255, 255, .18) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .18) 50%, rgba(255, 255, 255, .18) 75%, transparent 75%, transparent);
background-size: 1rem 1rem;
animation: msb-rb-stripes 1s linear infinite;
}
.msb-rb-bar.is-success {
background-image: none;
background-color: var(--msb-success);
}
.msb-rb-bar.is-danger {
background-image: none;
background-color: var(--msb-danger);
}
@keyframes msb-rb-stripes {
from {
background-position: 1rem 0;
}
to {
background-position: 0 0;
}
}
@@ -1,19 +0,0 @@
{
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "com_mokosuitebackup",
"version": "1.0.0",
"description": "MokoSuiteBackup web assets",
"license": "GPL-3.0-or-later",
"assets": [
{
"name": "com_mokosuitebackup.admin",
"type": "style",
"uri": "com_mokosuitebackup/css/admin.css"
},
{
"name": "com_mokosuitebackup.runbackup",
"type": "style",
"uri": "com_mokosuitebackup/css/runbackup.css"
}
]
}
@@ -6,25 +6,15 @@
* @license GNU General Public License version 3 or later; see LICENSE
-->
<extension type="component" method="upgrade">
<!--
IMPORTANT: a component's <name> drives the Joomla-derived extension
element (com_ + sanitized name), so it MUST stay element-safe:
"MokoSuiteBackup" -> com_mokosuitebackup. Do NOT apply the "Type - Name"
display convention here — "Component - MokoSuiteBackup" derives
com_component-mokosuitebackup, which registers a duplicate component and
orphans the real one. Packages are exempt because they set the element
via <packagename>; plugins/modules are exempt because <name> is only a
display label there.
-->
<name>MokoSuiteBackup</name>
<version>02.64.00</version>
<version>01.45.07</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<description>Full-site backup and restore for Joomla — database, files, and configuration.</description>
<description>COM_MOKOJOOMBACKUP_DESCRIPTION</description>
<namespace path="src">Joomla\Component\MokoSuiteBackup</namespace>
@@ -47,20 +37,20 @@
</update>
<administration>
<menu img="class:archive">COM_MOKOJOOMBACKUP_SHORT</menu>
<menu img="class:archive">COM_MOKOJOOMBACKUP</menu>
<submenu>
<menu link="option=com_mokosuitebackup&amp;view=dashboard"
img="class:home"
alt="Dashboard">Dashboard</menu>
alt="Dashboard">COM_MOKOJOOMBACKUP_SUBMENU_DASHBOARD</menu>
<menu link="option=com_mokosuitebackup&amp;view=backups"
img="class:database"
alt="Backups">Backup Records</menu>
alt="Backups">COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS</menu>
<menu link="option=com_mokosuitebackup&amp;view=snapshots"
img="class:camera"
alt="Snapshots">Content Snapshots</menu>
alt="Snapshots">COM_MOKOJOOMBACKUP_SUBMENU_SNAPSHOTS</menu>
<menu link="option=com_mokosuitebackup&amp;view=profiles"
img="class:cog"
alt="Profiles">Backup Profiles</menu>
alt="Profiles">COM_MOKOJOOMBACKUP_SUBMENU_PROFILES</menu>
</submenu>
<files folder=".">
<filename>access.xml</filename>
@@ -78,11 +68,6 @@
</languages>
</administration>
<media destination="com_mokosuitebackup" folder="media">
<folder>css</folder>
<filename>joomla.asset.json</filename>
</media>
<api>
<files folder="api">
<folder>src</folder>
@@ -11,6 +11,32 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitebackup_profiles` (
`exclude_dirs` TEXT NOT NULL COMMENT 'Newline-separated directory paths to exclude',
`exclude_files` TEXT NOT NULL COMMENT 'Newline-separated filename patterns to exclude',
`exclude_tables` TEXT NOT NULL COMMENT 'Newline-separated table names to exclude',
`remote_storage` VARCHAR(20) NOT NULL DEFAULT 'none' COMMENT 'none, ftp, google_drive, s3',
`ftp_host` VARCHAR(255) NOT NULL DEFAULT '',
`ftp_port` INT(5) UNSIGNED NOT NULL DEFAULT 21,
`ftp_username` VARCHAR(255) NOT NULL DEFAULT '',
`ftp_password` VARCHAR(255) NOT NULL DEFAULT '',
`ftp_path` VARCHAR(512) NOT NULL DEFAULT '/backups',
`ftp_passive` TINYINT(1) NOT NULL DEFAULT 1,
`ftp_ssl` TINYINT(1) NOT NULL DEFAULT 0,
`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 '',
`sftp_path` VARCHAR(512) NOT NULL DEFAULT '/backups',
`gdrive_client_id` VARCHAR(255) NOT NULL DEFAULT '',
`gdrive_client_secret` VARCHAR(255) NOT NULL DEFAULT '',
`gdrive_refresh_token` VARCHAR(512) NOT NULL DEFAULT '',
`gdrive_folder_id` VARCHAR(255) NOT NULL DEFAULT '',
`s3_endpoint` VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'S3 endpoint URL (blank = AWS default)',
`s3_region` VARCHAR(50) NOT NULL DEFAULT 'us-east-1',
`s3_access_key` VARCHAR(255) NOT NULL DEFAULT '',
`s3_secret_key` VARCHAR(255) NOT NULL DEFAULT '',
`s3_bucket` VARCHAR(255) NOT NULL DEFAULT '',
`s3_path` VARCHAR(512) NOT NULL DEFAULT '/backups',
`remote_keep_local` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'Keep local copy after upload',
`encryption_password` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'AES-256 archive encryption password (blank = no encryption)',
`include_mokorestore` VARCHAR(20) NOT NULL DEFAULT '0' COMMENT 'MokoRestore mode: 0=none, 1=wrapped, standalone',
@@ -23,8 +49,8 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitebackup_profiles` (
`notify_user_groups` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'Comma-separated Joomla user group IDs',
`notify_on_success` TINYINT(1) NOT NULL DEFAULT 0,
`notify_on_failure` TINYINT(1) NOT NULL DEFAULT 1,
`retention_days` INT(11) NOT NULL DEFAULT 0 COMMENT 'Delete backups older than N days; 0 = unlimited',
`retention_count` INT(11) NOT NULL DEFAULT 0 COMMENT 'Keep newest N backups; 0 = unlimited',
`retention_days` INT(11) NOT NULL DEFAULT 0 COMMENT '0 = use global default',
`retention_count` INT(11) NOT NULL DEFAULT 0 COMMENT '0 = use global default',
`ntfy_topic` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'ntfy topic name',
`ntfy_server` VARCHAR(512) NOT NULL DEFAULT 'https://ntfy.sh' COMMENT 'ntfy server URL',
`ntfy_token` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'ntfy access token (optional)',
@@ -39,7 +65,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitebackup_records` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`profile_id` INT(11) UNSIGNED NOT NULL DEFAULT 1,
`description` VARCHAR(255) NOT NULL DEFAULT '',
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending, running, complete, warning, fail',
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending, running, complete, fail',
`origin` VARCHAR(20) NOT NULL DEFAULT 'backend' COMMENT 'backend, cli, api, scheduled',
`backup_type` VARCHAR(20) NOT NULL DEFAULT 'full' COMMENT 'full, database, files',
`archivename` VARCHAR(512) NOT NULL DEFAULT '',
@@ -1,4 +1,3 @@
DROP TABLE IF EXISTS `#__mokosuitebackup_remotes`;
DROP TABLE IF EXISTS `#__mokosuitebackup_records`;
DROP TABLE IF EXISTS `#__mokosuitebackup_snapshots`;
DROP TABLE IF EXISTS `#__mokosuitebackup_profiles`;
@@ -0,0 +1 @@
/* 01.44.02 — no schema changes */
@@ -0,0 +1 @@
/* 01.44.03 — no schema changes */
@@ -1 +0,0 @@
/* 01.45.00 — no schema changes */
@@ -0,0 +1 @@
/* 01.45.02 — no schema changes */
@@ -0,0 +1 @@
/* 01.45.03 — no schema changes */
@@ -0,0 +1 @@
/* 01.45.05 — no schema changes */
@@ -0,0 +1 @@
/* 01.45.06 — no schema changes */
@@ -0,0 +1 @@
/* 01.45.07 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.16 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.17 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.18 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.20 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.21 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.22 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.23 — no schema changes */
@@ -1 +0,0 @@
/* 02.52.24 — no schema changes */
@@ -1,31 +0,0 @@
-- Remove legacy single-remote storage columns (superseded by #__mokosuitebackup_remotes).
-- Plain DROP COLUMN (no IF EXISTS): all columns are created by install.mysql.sql and
-- earlier updates, so they always exist here. `DROP COLUMN IF EXISTS` is a MariaDB-only
-- extension and errors on Oracle MySQL 8.x, which Joomla also supports.
ALTER TABLE `#__mokosuitebackup_profiles`
DROP COLUMN `remote_storage`,
DROP COLUMN `ftp_host`,
DROP COLUMN `ftp_port`,
DROP COLUMN `ftp_username`,
DROP COLUMN `ftp_password`,
DROP COLUMN `ftp_path`,
DROP COLUMN `ftp_passive`,
DROP COLUMN `ftp_ssl`,
DROP COLUMN `sftp_host`,
DROP COLUMN `sftp_port`,
DROP COLUMN `sftp_username`,
DROP COLUMN `sftp_auth_type`,
DROP COLUMN `sftp_password`,
DROP COLUMN `sftp_key_data`,
DROP COLUMN `sftp_passphrase`,
DROP COLUMN `sftp_path`,
DROP COLUMN `gdrive_client_id`,
DROP COLUMN `gdrive_client_secret`,
DROP COLUMN `gdrive_refresh_token`,
DROP COLUMN `gdrive_folder_id`,
DROP COLUMN `s3_endpoint`,
DROP COLUMN `s3_region`,
DROP COLUMN `s3_access_key`,
DROP COLUMN `s3_secret_key`,
DROP COLUMN `s3_bucket`,
DROP COLUMN `s3_path`;
@@ -1 +0,0 @@
/* 02.52.27 — no schema changes */
@@ -1 +0,0 @@
/* 02.53.00 — no schema changes */
@@ -1 +0,0 @@
/* 02.54.00 — no schema changes */
@@ -1 +0,0 @@
/* 02.55.00 — no schema changes */
@@ -1 +0,0 @@
/* 02.55.03 — no schema changes */
@@ -1 +0,0 @@
/* 02.56.00 — no schema changes */
@@ -1,13 +0,0 @@
-- Legacy single-remote storage columns are now purged by the package installer
-- script (Pkg_MokoSuiteBackupInstallerScript::dropLegacyRemoteColumns()), NOT here.
--
-- The 26 legacy remote_storage/ftp_*/sftp_*/gdrive_*/s3_* columns must be removed
-- portably across MariaDB and MySQL 8. `DROP COLUMN IF EXISTS` is MariaDB-only
-- (errors on MySQL 8); plain `DROP COLUMN` errors when a column is absent. The
-- previous version gated the ALTER on INFORMATION_SCHEMA and ran it via
-- PREPARE/EXECUTE/DEALLOCATE — but those statements are rejected by Joomla's
-- installer SQL path (MySQL error 1295: "This command is not supported in the
-- prepared statement protocol yet"), which ABORTED the whole component install.
--
-- The drop is now done in PHP (INFORMATION_SCHEMA gate + a plain ALTER for only the
-- columns that still exist), so this migration is intentionally a no-op.
@@ -1 +0,0 @@
/* 02.56.05 — no schema changes */
@@ -1 +0,0 @@
/* 02.56.07 — no schema changes */
@@ -1 +0,0 @@
/* 02.56.08 — no schema changes */
@@ -1 +0,0 @@
/* 02.56.11 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.00 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.01 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.02 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.04 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.05 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.06 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.07 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.08 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.09 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.10 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.11 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.12 — no schema changes */
@@ -1 +0,0 @@
/* 02.57.13 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.00 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.01 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.02 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.03 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.04 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.05 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.06 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.07 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.08 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.09 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.10 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.11 — no schema changes */
@@ -1 +0,0 @@
/* 02.58.12 — no schema changes */

Some files were not shown because too many files have changed in this diff Show More