Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d7359cefd | |||
| c828c46a4b | |||
| 38a98e5ea3 | |||
| a5a2f48e7c | |||
| 70f25f6e79 | |||
| 26dd5cf5c2 | |||
| b754542254 | |||
| 3942fa4661 | |||
| 0764697086 | |||
| f7767b76dd | |||
| 37c003a97a | |||
| 3c5460559b | |||
| e433314e42 | |||
| db3beae7d3 | |||
| 734a5326e5 | |||
| 6c48a6a777 | |||
| 4e4e608f89 | |||
| 2c17b07835 | |||
| ea17d81e5e | |||
| 58080a5ad7 | |||
| e8f054ddae | |||
| 21bcf479cc | |||
| 695b9e0be5 | |||
| 8fc7616d4d | |||
| afe766302a | |||
| 1a30dd7b98 | |||
| 446296e0c8 | |||
| 8f7c255c22 | |||
| db328cd7e1 | |||
| 6fe5a003ea | |||
| 728e55c721 | |||
| 2554ae98d0 | |||
| 77a3b5f91e | |||
| 3d640d1fa9 | |||
| 7f89abf809 | |||
| c0b40d5a7e | |||
| 8b367c3983 | |||
| 7a92c14de6 | |||
| b70d7f6b80 | |||
| 09f964b427 | |||
| 0b1db735ce | |||
| 77270cceab | |||
| 291f09223d | |||
| 674fd0f6b9 | |||
| 0d2e4b0c01 |
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<mokoplatform xmlns="https://standards.mokoconsulting.tech/mokoplatform/1.0" schema-version="1.0">
|
|
||||||
<identity>
|
|
||||||
<name>MokoSuiteBackup</name>
|
|
||||||
<display-name>Package - MokoSuiteBackup</display-name>
|
|
||||||
<org>MokoConsulting</org>
|
|
||||||
<description>Full-site backup and restore for Joomla — database, files, and configuration</description>
|
|
||||||
<version>01.23.01-dev</version>
|
|
||||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
|
||||||
</identity>
|
|
||||||
<governance>
|
|
||||||
<platform>joomla</platform>
|
|
||||||
<standards-version>05.00.00</standards-version>
|
|
||||||
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/mokoplatform</standards-source>
|
|
||||||
</governance>
|
|
||||||
<build>
|
|
||||||
<language>PHP</language>
|
|
||||||
<package-type>joomla-extension</package-type>
|
|
||||||
<entry-point>source/</entry-point>
|
|
||||||
</build>
|
|
||||||
</mokoplatform>
|
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Release
|
# INGROUP: mokocli.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||||
# VERSION: 09.02.00
|
# VERSION: 09.02.00
|
||||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||||
@@ -43,19 +43,19 @@ jobs:
|
|||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup mokocli tools
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
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
|
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
|
||||||
fi
|
fi
|
||||||
if [ -d "/opt/moko-platform/cli" ]; then
|
if [ -d "/opt/mokocli/cli" ]; then
|
||||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
else
|
else
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||||
/tmp/moko-platform-api
|
/tmp/mokocli
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Release
|
# INGROUP: mokocli.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
||||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||||
# VERSION: 05.00.00
|
# VERSION: 05.00.00
|
||||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||||
@@ -66,25 +66,25 @@ jobs:
|
|||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup mokocli tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
echo Using pre-installed /opt/moko-platform
|
echo Using pre-installed /opt/mokocli
|
||||||
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo Falling back to fresh clone
|
echo Falling back to fresh clone
|
||||||
if ! command -v composer > /dev/null 2>&1; then
|
if ! command -v composer > /dev/null 2>&1; 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
|
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
|
||||||
fi
|
fi
|
||||||
rm -rf /tmp/moko-platform-api
|
rm -rf /tmp/mokocli
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
cd /tmp/moko-platform-api
|
cd /tmp/mokocli
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Rename branch to rc
|
- name: Rename branch to rc
|
||||||
@@ -109,6 +109,40 @@ jobs:
|
|||||||
--path . --stability rc --bump minor --branch rc \
|
--path . --stability rc --bump minor --branch rc \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Update RC release notes from CHANGELOG.md
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
# Extract [Unreleased] section from changelog
|
||||||
|
NOTES=""
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||||
|
fi
|
||||||
|
[ -z "$NOTES" ] && NOTES="Release candidate"
|
||||||
|
|
||||||
|
# Find the RC release and update its body
|
||||||
|
RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/releases/tags/release-candidate" \
|
||||||
|
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
|
python3 -c "
|
||||||
|
import json, urllib.request
|
||||||
|
body = open('/dev/stdin').read()
|
||||||
|
payload = json.dumps({'body': body}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'${API_BASE}/releases/${RELEASE_ID}',
|
||||||
|
data=payload, method='PATCH',
|
||||||
|
headers={
|
||||||
|
'Authorization': 'token ${TOKEN}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
urllib.request.urlopen(req)
|
||||||
|
" <<< "$NOTES"
|
||||||
|
echo "RC release notes updated from CHANGELOG.md"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
@@ -149,26 +183,26 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "No conflict markers found"
|
echo "No conflict markers found"
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup mokocli tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||||
run: |
|
run: |
|
||||||
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
echo Using pre-installed /opt/moko-platform
|
echo Using pre-installed /opt/mokocli
|
||||||
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo Falling back to fresh clone
|
echo Falling back to fresh clone
|
||||||
if ! command -v composer > /dev/null 2>&1; then
|
if ! command -v composer > /dev/null 2>&1; 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
|
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
|
||||||
fi
|
fi
|
||||||
rm -rf /tmp/moko-platform-api
|
rm -rf /tmp/mokocli
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
cd /tmp/moko-platform-api
|
cd /tmp/mokocli
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: "Determine version bump level"
|
- name: "Determine version bump level"
|
||||||
@@ -194,22 +228,32 @@ jobs:
|
|||||||
--path . --stability stable ${BUMP_FLAG} --branch main \
|
--path . --stability stable ${BUMP_FLAG} --branch main \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
- name: Update release notes from CHANGELOG.md
|
- name: Update release notes and promote changelog
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
# Get the stable release info (version and ID)
|
||||||
|
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
|
||||||
|
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||||
|
# Extract version from release name (e.g. "06.17.00" or "v06.17.00")
|
||||||
|
VERSION=$(python3 -c "
|
||||||
|
import json, sys, re
|
||||||
|
r = json.load(sys.stdin)
|
||||||
|
name = r.get('name', '')
|
||||||
|
m = re.search(r'(\d+\.\d+\.\d+)', name)
|
||||||
|
print(m.group(1) if m else '')
|
||||||
|
" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog
|
# Extract [Unreleased] section from changelog
|
||||||
|
NOTES=""
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||||
[ -z "$NOTES" ] && NOTES="Stable release"
|
|
||||||
else
|
|
||||||
NOTES="Stable release"
|
|
||||||
fi
|
fi
|
||||||
|
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||||
|
|
||||||
# Update release body via API
|
# Update release body via API
|
||||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -n "$RELEASE_ID" ]; then
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import json, urllib.request
|
import json, urllib.request
|
||||||
@@ -219,7 +263,7 @@ jobs:
|
|||||||
'${API_BASE}/releases/${RELEASE_ID}',
|
'${API_BASE}/releases/${RELEASE_ID}',
|
||||||
data=payload, method='PATCH',
|
data=payload, method='PATCH',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
|
'Authorization': 'token ${TOKEN}',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})
|
})
|
||||||
urllib.request.urlopen(req)
|
urllib.request.urlopen(req)
|
||||||
@@ -227,6 +271,24 @@ jobs:
|
|||||||
echo "Release notes updated from CHANGELOG.md"
|
echo "Release notes updated from CHANGELOG.md"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
|
||||||
|
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
python3 -c "
|
||||||
|
import sys
|
||||||
|
version, date = sys.argv[1], sys.argv[2]
|
||||||
|
content = open('CHANGELOG.md').read()
|
||||||
|
old = '## [Unreleased]'
|
||||||
|
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
|
||||||
|
content = content.replace(old, new, 1)
|
||||||
|
open('CHANGELOG.md', 'w').write(content)
|
||||||
|
" "$VERSION" "$DATE"
|
||||||
|
git add CHANGELOG.md
|
||||||
|
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
|
||||||
|
git push origin main || true
|
||||||
|
echo "Changelog promoted: [Unreleased] → [${VERSION}]"
|
||||||
|
fi
|
||||||
|
|
||||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||||
- name: "Step 9: Mirror release to GitHub"
|
- name: "Step 9: Mirror release to GitHub"
|
||||||
if: >-
|
if: >-
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Universal
|
# INGROUP: MokoStandards.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Delete feature branches after PR merge
|
# BRIEF: Delete feature branches after PR merge
|
||||||
|
|||||||
@@ -13,13 +13,6 @@
|
|||||||
name: "Generic: Project CI"
|
name: "Generic: Project CI"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev
|
|
||||||
- dev/**
|
|
||||||
- rc/**
|
|
||||||
- version/**
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokoplatform.Automation
|
# INGROUP: moko-platform.Automation
|
||||||
# VERSION: 01.23.01
|
# VERSION: 01.25.00
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Create branch and comment
|
- name: Create branch and comment
|
||||||
run: |
|
run: |
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.CI
|
# INGROUP: mokocli.CI
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
||||||
# PATH: /templates/workflows/universal/pr-check.yml.template
|
# PATH: /templates/workflows/universal/pr-check.yml.template
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: PR gate — branch policy + code validation before merge
|
# BRIEF: PR gate — branch policy + code validation before merge
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# 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
|
||||||
|
# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs
|
||||||
|
|
||||||
|
name: "Joomla: Metadata Validation"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, converted_to_draft, ready_for_review]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-metadata:
|
||||||
|
name: "Validate Joomla Metadata"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup mokocli tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
run: |
|
||||||
|
if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
|
echo Using pre-installed /opt/mokocli
|
||||||
|
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo Falling back to fresh clone
|
||||||
|
if ! command -v composer > /dev/null 2>&1; 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
|
||||||
|
fi
|
||||||
|
rm -rf /tmp/mokocli
|
||||||
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||||
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Validate metadata against Joomla manifest
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
php ${MOKO_CLI}/joomla_metadata_validate.php \
|
||||||
|
--path . \
|
||||||
|
--token "${GITEA_TOKEN}" \
|
||||||
|
--org "${GITEA_ORG}" \
|
||||||
|
--repo "${GITEA_REPO}" \
|
||||||
|
--api-base "${GITEA_URL}/api/v1" \
|
||||||
|
--ci
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "::error::Joomla metadata mismatch — update delivery will fail. Run 'php cli/joomla_metadata_validate.php' locally to see details."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Release
|
# INGROUP: mokocli.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||||
# VERSION: 05.01.00
|
# VERSION: 05.01.00
|
||||||
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
||||||
@@ -60,25 +60,25 @@ jobs:
|
|||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
ref: ${{ github.ref_name }}
|
ref: ${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup mokocli tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
|
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
|
||||||
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
echo Using pre-installed /opt/moko-platform
|
echo Using pre-installed /opt/mokocli
|
||||||
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo Falling back to fresh clone
|
echo Falling back to fresh clone
|
||||||
if ! command -v composer > /dev/null 2>&1; then
|
if ! command -v composer > /dev/null 2>&1; 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
|
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
|
||||||
fi
|
fi
|
||||||
rm -rf /tmp/moko-platform-api
|
rm -rf /tmp/mokocli
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Detect platform
|
- name: Detect platform
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Validation
|
# INGROUP: mokocli.Validation
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
||||||
# PATH: /templates/workflows/joomla/repo_health.yml.template
|
# PATH: /templates/workflows/joomla/repo_health.yml.template
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
|
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
|
||||||
@@ -33,7 +33,8 @@ on:
|
|||||||
- scripts
|
- scripts
|
||||||
- repo
|
- repo
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoPlatform.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||||
|
# 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:
|
||||||
|
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.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 mokoplatform
|
||||||
|
env:
|
||||||
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||||
|
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokoplatform.git" /tmp/mokoplatform
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd /tmp/mokoplatform
|
||||||
|
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/mokoplatform/cli/workflow_sync.php ${ARGS}
|
||||||
+6
-6
@@ -1,6 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [01.25.00] --- 2026-06-20
|
||||||
|
|
||||||
|
## [01.24.00] --- 2026-06-20
|
||||||
|
|
||||||
|
## [01.24.00] --- 2026-06-19
|
||||||
|
|
||||||
## [01.23.00] --- 2026-06-18
|
## [01.23.00] --- 2026-06-18
|
||||||
|
|
||||||
## [01.21.00] --- 2026-06-16
|
## [01.21.00] --- 2026-06-16
|
||||||
@@ -11,9 +17,3 @@
|
|||||||
- Submenu icons not rendering in Joomla 6 — set `menu_icon` param for level 2+ items (Atum only renders `img` column icons for level 1)
|
- Submenu icons not rendering in Joomla 6 — set `menu_icon` param for level 2+ items (Atum only renders `img` column icons for level 1)
|
||||||
- CSS selector `#menu` → `.main-nav` for icon injection (Joomla 6 uses dynamic `id="menu{moduleId}"`)
|
- CSS selector `#menu` → `.main-nav` for icon injection (Joomla 6 uses dynamic `id="menu{moduleId}"`)
|
||||||
- Use `margin-inline-end` instead of `margin-right` for RTL layout support
|
- Use `margin-inline-end` instead of `margin-right` for RTL layout support
|
||||||
|
|
||||||
## [01.08.00] --- 2026-06-07
|
|
||||||
|
|
||||||
## [01.07.00] --- 2026-06-07
|
|
||||||
|
|
||||||
## [01.06.00] --- 2026-06-07
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# MokoSuiteBackup
|
# MokoSuiteBackup
|
||||||
|
|
||||||
<!-- VERSION: 01.23.01 -->
|
<!-- VERSION: 01.25.00 -->
|
||||||
|
|
||||||
Full-site backup and restore for Joomla — database, files, and configuration.
|
Full-site backup and restore for Joomla — database, files, and configuration.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="webservices" method="upgrade">
|
<extension type="plugin" group="webservices" method="upgrade">
|
||||||
<name>Web Services - MokoSuiteBackup</name>
|
<name>Web Services - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="component" method="upgrade">
|
<extension type="component" method="upgrade">
|
||||||
<name>MokoSuiteBackup</name>
|
<name>MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoSuiteBackup
|
||||||
|
* @subpackage com_mokosuitebackup
|
||||||
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Joomla\Component\MokoSuiteBackup\Administrator\Helper;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight helper for external consumers (bridge plugins, MCP servers, CLI tools)
|
||||||
|
* to query MokoSuiteBackup status without bootstrapping the full component.
|
||||||
|
*
|
||||||
|
* Usage from any Joomla plugin:
|
||||||
|
* \Joomla\Component\MokoSuiteBackup\Administrator\Helper\BackupStatusHelper::getStatusSummary()
|
||||||
|
*/
|
||||||
|
class BackupStatusHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Check whether MokoSuiteBackup is installed and enabled.
|
||||||
|
*/
|
||||||
|
public static function isInstalled(): bool
|
||||||
|
{
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('COUNT(*)')
|
||||||
|
->from($db->quoteName('#__extensions'))
|
||||||
|
->where($db->quoteName('element') . ' = ' . $db->quote('pkg_mokosuitebackup'))
|
||||||
|
->where($db->quoteName('enabled') . ' = 1');
|
||||||
|
|
||||||
|
return (int) $db->setQuery($query)->loadResult() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest backup record for a given profile (or any profile).
|
||||||
|
*
|
||||||
|
* @param int|null $profileId Limit to a specific profile, or null for any.
|
||||||
|
* @return object|null Record object or null if no backups exist.
|
||||||
|
*/
|
||||||
|
public static function getLatestRecord(?int $profileId = null): ?object
|
||||||
|
{
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select([
|
||||||
|
$db->quoteName('r.id'),
|
||||||
|
$db->quoteName('r.profile_id'),
|
||||||
|
$db->quoteName('r.description'),
|
||||||
|
$db->quoteName('r.status'),
|
||||||
|
$db->quoteName('r.origin'),
|
||||||
|
$db->quoteName('r.backup_type'),
|
||||||
|
$db->quoteName('r.archivename'),
|
||||||
|
$db->quoteName('r.total_size'),
|
||||||
|
$db->quoteName('r.db_size'),
|
||||||
|
$db->quoteName('r.files_count'),
|
||||||
|
$db->quoteName('r.tables_count'),
|
||||||
|
$db->quoteName('r.backupstart'),
|
||||||
|
$db->quoteName('r.backupend'),
|
||||||
|
$db->quoteName('r.filesexist'),
|
||||||
|
$db->quoteName('r.remote_filename'),
|
||||||
|
$db->quoteName('r.checksum'),
|
||||||
|
$db->quoteName('p.title', 'profile_title'),
|
||||||
|
])
|
||||||
|
->from($db->quoteName('#__mokosuitebackup_records', 'r'))
|
||||||
|
->join('LEFT', $db->quoteName('#__mokosuitebackup_profiles', 'p') . ' ON p.id = r.profile_id')
|
||||||
|
->where($db->quoteName('r.status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'fail'])) . ')')
|
||||||
|
->order($db->quoteName('r.backupstart') . ' DESC');
|
||||||
|
|
||||||
|
if ($profileId !== null) {
|
||||||
|
$query->where($db->quoteName('r.profile_id') . ' = ' . (int) $profileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->setLimit(1);
|
||||||
|
$record = $db->setQuery($query)->loadObject();
|
||||||
|
|
||||||
|
return $record ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a full status summary for heartbeat payloads.
|
||||||
|
*
|
||||||
|
* Returns an array suitable for JSON encoding in bridge heartbeats:
|
||||||
|
* - latest backup status, time, size, destination
|
||||||
|
* - total and recent (7-day) backup counts
|
||||||
|
* - streak of consecutive successes
|
||||||
|
*
|
||||||
|
* @return array{installed: bool, latest: ?array, totals: array}
|
||||||
|
*/
|
||||||
|
public static function getStatusSummary(): array
|
||||||
|
{
|
||||||
|
if (!self::isInstalled()) {
|
||||||
|
return ['installed' => false, 'latest' => null, 'totals' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = Factory::getContainer()->get('DatabaseDriver');
|
||||||
|
|
||||||
|
// Latest completed/failed backup
|
||||||
|
$latest = self::getLatestRecord();
|
||||||
|
$latestArray = null;
|
||||||
|
|
||||||
|
if ($latest) {
|
||||||
|
$latestArray = [
|
||||||
|
'status' => $latest->status,
|
||||||
|
'backup_type' => $latest->backup_type,
|
||||||
|
'description' => $latest->description,
|
||||||
|
'backup_start' => $latest->backupstart,
|
||||||
|
'backup_end' => $latest->backupend,
|
||||||
|
'total_size' => (int) $latest->total_size,
|
||||||
|
'destination' => $latest->remote_filename ? 'remote' : 'local',
|
||||||
|
'profile' => $latest->profile_title,
|
||||||
|
'origin' => $latest->origin,
|
||||||
|
'files_count' => (int) $latest->files_count,
|
||||||
|
'tables_count' => (int) $latest->tables_count,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Totals
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select([
|
||||||
|
'COUNT(*) AS total',
|
||||||
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('complete') . ' THEN 1 ELSE 0 END) AS success',
|
||||||
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('fail') . ' THEN 1 ELSE 0 END) AS failed',
|
||||||
|
])
|
||||||
|
->from($db->quoteName('#__mokosuitebackup_records'));
|
||||||
|
|
||||||
|
$allTime = $db->setQuery($query)->loadObject();
|
||||||
|
|
||||||
|
// Recent (last 7 days)
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select([
|
||||||
|
'COUNT(*) AS total',
|
||||||
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('complete') . ' THEN 1 ELSE 0 END) AS success',
|
||||||
|
'SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('fail') . ' THEN 1 ELSE 0 END) AS failed',
|
||||||
|
])
|
||||||
|
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||||
|
->where($db->quoteName('backupstart') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)');
|
||||||
|
|
||||||
|
$recent = $db->setQuery($query)->loadObject();
|
||||||
|
|
||||||
|
// Success streak — count consecutive successes from latest backward
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('status'))
|
||||||
|
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||||
|
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'fail'])) . ')')
|
||||||
|
->order($db->quoteName('backupstart') . ' DESC')
|
||||||
|
->setLimit(50);
|
||||||
|
|
||||||
|
$statuses = $db->setQuery($query)->loadColumn();
|
||||||
|
$streak = 0;
|
||||||
|
|
||||||
|
foreach ($statuses as $s) {
|
||||||
|
if ($s === 'complete') {
|
||||||
|
$streak++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'installed' => true,
|
||||||
|
'latest' => $latestArray,
|
||||||
|
'totals' => [
|
||||||
|
'all_time' => (int) ($allTime->total ?? 0),
|
||||||
|
'all_success' => (int) ($allTime->success ?? 0),
|
||||||
|
'all_failed' => (int) ($allTime->failed ?? 0),
|
||||||
|
'recent_total' => (int) ($recent->total ?? 0),
|
||||||
|
'recent_success' => (int) ($recent->success ?? 0),
|
||||||
|
'recent_failed' => (int) ($recent->failed ?? 0),
|
||||||
|
'success_streak' => $streak,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @package MokoSuiteBackup
|
|
||||||
* @subpackage com_mokosuitebackup
|
|
||||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
||||||
* @license GNU General Public License version 3 or later; see LICENSE
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Joomla\Component\MokoSuiteBackup\Administrator\Utility;
|
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
use Joomla\Database\DatabaseInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a public API for external plugins (e.g. MokoSuiteClient bridge)
|
|
||||||
* to query backup status without depending on internal model internals.
|
|
||||||
*
|
|
||||||
* @since 01.22.07
|
|
||||||
*/
|
|
||||||
class BackupStatusHelper
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get a summary of the latest backup status.
|
|
||||||
*
|
|
||||||
* Returns an array suitable for inclusion in heartbeat payloads.
|
|
||||||
*
|
|
||||||
* @param int $staleDays Days without backup before status is degraded.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public static function getStatus(int $staleDays = 7): array
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
||||||
}
|
|
||||||
catch (\Throwable $e)
|
|
||||||
{
|
|
||||||
return ['installed' => true, 'status' => 'error', 'message' => 'Database unavailable'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most recently inserted backup record (by ID, any status)
|
|
||||||
$query = $db->getQuery(true)
|
|
||||||
->select([
|
|
||||||
$db->quoteName('id'),
|
|
||||||
$db->quoteName('description'),
|
|
||||||
$db->quoteName('status'),
|
|
||||||
$db->quoteName('backup_type'),
|
|
||||||
$db->quoteName('total_size'),
|
|
||||||
$db->quoteName('backupstart'),
|
|
||||||
$db->quoteName('backupend'),
|
|
||||||
$db->quoteName('origin'),
|
|
||||||
$db->quoteName('filesexist'),
|
|
||||||
])
|
|
||||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
|
||||||
->order($db->quoteName('id') . ' DESC');
|
|
||||||
|
|
||||||
$db->setQuery($query, 0, 1);
|
|
||||||
$latest = $db->loadObject();
|
|
||||||
|
|
||||||
if (!$latest)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'installed' => true,
|
|
||||||
'status' => 'degraded',
|
|
||||||
'message' => 'No backups found',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$db->setQuery(
|
|
||||||
$db->getQuery(true)
|
|
||||||
->select('COUNT(*)')
|
|
||||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
|
||||||
->where($db->quoteName('status') . ' = ' . $db->quote('complete'))
|
|
||||||
);
|
|
||||||
$totalBackups = (int) $db->loadResult();
|
|
||||||
|
|
||||||
$cutoff = date('Y-m-d H:i:s', strtotime("-{$staleDays} days"));
|
|
||||||
$db->setQuery(
|
|
||||||
$db->getQuery(true)
|
|
||||||
->select('COUNT(*)')
|
|
||||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
|
||||||
->where($db->quoteName('status') . ' = ' . $db->quote('complete'))
|
|
||||||
->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff))
|
|
||||||
);
|
|
||||||
$recentBackups = (int) $db->loadResult();
|
|
||||||
|
|
||||||
// Failures in last 7 days
|
|
||||||
$db->setQuery(
|
|
||||||
$db->getQuery(true)
|
|
||||||
->select('COUNT(*)')
|
|
||||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
|
||||||
->where($db->quoteName('status') . ' = ' . $db->quote('fail'))
|
|
||||||
->where($db->quoteName('backupstart') . ' >= ' . $db->quote($cutoff))
|
|
||||||
);
|
|
||||||
$failCount7d = (int) $db->loadResult();
|
|
||||||
|
|
||||||
// Determine overall status
|
|
||||||
$daysSince = 999;
|
|
||||||
|
|
||||||
if (!empty($latest->backupstart) && $latest->backupstart !== '0000-00-00 00:00:00')
|
|
||||||
{
|
|
||||||
$daysSince = (int) ((time() - strtotime($latest->backupstart)) / 86400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$status = 'ok';
|
|
||||||
|
|
||||||
if ($latest->status === 'fail')
|
|
||||||
{
|
|
||||||
$status = 'degraded';
|
|
||||||
}
|
|
||||||
elseif ($latest->status !== 'complete')
|
|
||||||
{
|
|
||||||
$status = ($latest->status === 'running') ? 'ok' : 'degraded';
|
|
||||||
}
|
|
||||||
elseif ($daysSince > $staleDays)
|
|
||||||
{
|
|
||||||
$status = 'degraded';
|
|
||||||
}
|
|
||||||
|
|
||||||
$sizeMb = $latest->total_size
|
|
||||||
? round($latest->total_size / 1048576)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return [
|
|
||||||
'installed' => true,
|
|
||||||
'status' => $status,
|
|
||||||
'last_backup' => $latest->backupstart,
|
|
||||||
'last_status' => $latest->status,
|
|
||||||
'last_size_mb' => $sizeMb,
|
|
||||||
'days_since' => $daysSince,
|
|
||||||
'backup_type' => $latest->backup_type,
|
|
||||||
'origin' => $latest->origin,
|
|
||||||
'total_backups' => $totalBackups,
|
|
||||||
'recent_7d' => $recentBackups,
|
|
||||||
'fail_count_7d' => $failCount7d,
|
|
||||||
'files_exist' => (bool) $latest->filesexist,
|
|
||||||
'description' => $latest->description,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if MokoSuiteBackup component is installed.
|
|
||||||
*
|
|
||||||
* Useful for external plugins that want to check before calling getStatus().
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public static function isInstalled(): bool
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
||||||
|
|
||||||
$query = $db->getQuery(true)
|
|
||||||
->select('COUNT(*)')
|
|
||||||
->from($db->quoteName('#__extensions'))
|
|
||||||
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuitebackup'))
|
|
||||||
->where($db->quoteName('type') . ' = ' . $db->quote('component'));
|
|
||||||
|
|
||||||
$db->setQuery($query);
|
|
||||||
|
|
||||||
return (int) $db->loadResult() > 0;
|
|
||||||
}
|
|
||||||
catch (\Throwable $e)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="actionlog" method="upgrade">
|
<extension type="plugin" group="actionlog" method="upgrade">
|
||||||
<name>Action Log - MokoSuiteBackup</name>
|
<name>Action Log - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="console" method="upgrade">
|
<extension type="plugin" group="console" method="upgrade">
|
||||||
<name>Console - MokoSuiteBackup</name>
|
<name>Console - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="content" method="upgrade">
|
<extension type="plugin" group="content" method="upgrade">
|
||||||
<name>Content - MokoSuiteBackup</name>
|
<name>Content - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-06-04</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<extension type="plugin" group="quickicon" method="upgrade">
|
<extension type="plugin" group="quickicon" method="upgrade">
|
||||||
<name>Quick Icon - MokoSuiteBackup</name>
|
<name>Quick Icon - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="system" method="upgrade">
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
<name>System - MokoSuiteBackup</name>
|
<name>System - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="task" method="upgrade">
|
<extension type="plugin" group="task" method="upgrade">
|
||||||
<name>Task - MokoSuiteBackup</name>
|
<name>Task - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
<extension type="plugin" group="webservices" method="upgrade">
|
<extension type="plugin" group="webservices" method="upgrade">
|
||||||
<name>Web Services - MokoSuiteBackup</name>
|
<name>Web Services - MokoSuiteBackup</name>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<extension type="package" method="upgrade">
|
<extension type="package" method="upgrade">
|
||||||
<name>Package - MokoSuiteBackup</name>
|
<name>Package - MokoSuiteBackup</name>
|
||||||
<packagename>mokosuitebackup</packagename>
|
<packagename>mokosuitebackup</packagename>
|
||||||
<version>01.23.01-dev</version>
|
<version>01.25.00</version>
|
||||||
<creationDate>2026-06-02</creationDate>
|
<creationDate>2026-06-02</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
<?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
|
|
||||||
*
|
|
||||||
* REST API endpoints — wire-compatible with the mcp_mokosuitebackup MCP server.
|
|
||||||
*
|
|
||||||
* Akeeba-compatible routes:
|
|
||||||
* POST /api/index.php/v1/mokosuitebackup/backup — Start backup
|
|
||||||
* GET /api/index.php/v1/mokosuitebackup/backups — List records
|
|
||||||
* DELETE /api/index.php/v1/mokosuitebackup/backup/:id — Delete record
|
|
||||||
* GET /api/index.php/v1/mokosuitebackup/backup/:id/download — Download archive
|
|
||||||
* GET /api/index.php/v1/mokosuitebackup/profiles — List profiles
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Joomla\Plugin\WebServices\MokoSuiteBackup\Extension;
|
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
|
||||||
|
|
||||||
use Joomla\CMS\Plugin\CMSPlugin;
|
|
||||||
use Joomla\CMS\Router\ApiRouter;
|
|
||||||
use Joomla\Event\Event;
|
|
||||||
use Joomla\Event\SubscriberInterface;
|
|
||||||
use Joomla\Router\Route;
|
|
||||||
|
|
||||||
final class MokoSuiteBackupWebServices extends CMSPlugin implements SubscriberInterface
|
|
||||||
{
|
|
||||||
protected $autoloadLanguage = true;
|
|
||||||
|
|
||||||
public static function getSubscribedEvents(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'onBeforeApiRoute' => 'onBeforeApiRoute',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function onBeforeApiRoute(Event $event): void
|
|
||||||
{
|
|
||||||
/** @var ApiRouter $router */
|
|
||||||
[$router] = array_values($event->getArguments());
|
|
||||||
|
|
||||||
$defaults = [
|
|
||||||
'component' => 'com_mokosuitebackup',
|
|
||||||
'public' => false,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Standard CRUD for backup records
|
|
||||||
$router->createCRUDRoutes('v1/mokosuitebackup/backups', 'backups', $defaults);
|
|
||||||
|
|
||||||
// Start a backup (POST)
|
|
||||||
$router->addRoute(
|
|
||||||
new Route(
|
|
||||||
['POST'],
|
|
||||||
'v1/mokosuitebackup/backup',
|
|
||||||
'backups.backup',
|
|
||||||
[],
|
|
||||||
$defaults
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete a backup (DELETE)
|
|
||||||
$router->addRoute(
|
|
||||||
new Route(
|
|
||||||
['DELETE'],
|
|
||||||
'v1/mokosuitebackup/backup/:id',
|
|
||||||
'backups.delete',
|
|
||||||
['id' => '(\d+)'],
|
|
||||||
$defaults
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Download a backup archive (GET)
|
|
||||||
$router->addRoute(
|
|
||||||
new Route(
|
|
||||||
['GET'],
|
|
||||||
'v1/mokosuitebackup/backup/:id/download',
|
|
||||||
'backups.download',
|
|
||||||
['id' => '(\d+)'],
|
|
||||||
$defaults
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// List backup profiles (GET)
|
|
||||||
$router->addRoute(
|
|
||||||
new Route(
|
|
||||||
['GET'],
|
|
||||||
'v1/mokosuitebackup/profiles',
|
|
||||||
'backups.profiles',
|
|
||||||
[],
|
|
||||||
$defaults
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<!DOCTYPE html><title></title>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<!DOCTYPE html><title></title>
|
|
||||||
Reference in New Issue
Block a user