Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec751b6ddb |
@@ -8,7 +8,7 @@
|
||||
<name>MokoWaaS</name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>White-label identity, security hardening, and tenant restriction layer for WaaS-managed Joomla environments</description>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
|
||||
@@ -1,85 +1,84 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup moko-platform 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
|
||||
fi
|
||||
if [ -d "/opt/moko-platform/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true
|
||||
echo "$BUMP"
|
||||
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true
|
||||
[ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; }
|
||||
|
||||
# Propagate to platform manifests with -dev suffix
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch dev --stability dev 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
VERSION="${VERSION}-dev"
|
||||
|
||||
# Commit if anything changed
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
echo "No version changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
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 commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push origin dev
|
||||
echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.GA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup moko-platform 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
|
||||
fi
|
||||
if [ -d "/opt/moko-platform/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true
|
||||
echo "$BUMP"
|
||||
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true
|
||||
[ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; }
|
||||
|
||||
# Propagate to platform manifests
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch dev 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Commit if anything changed
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
echo "No version changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
git add -A
|
||||
git commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push origin dev
|
||||
echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,48 +1,48 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Delete feature branches after PR merge
|
||||
|
||||
name: "Branch Cleanup"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Delete merged branch
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.head.ref != 'dev' &&
|
||||
github.event.pull_request.head.ref != 'main'
|
||||
|
||||
steps:
|
||||
- name: Delete source branch
|
||||
run: |
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BRANCH}', safe=''))")
|
||||
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||
|
||||
if [ "$STATUS" = "204" ]; then
|
||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "$STATUS" = "404" ]; then
|
||||
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
||||
fi
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Delete feature branches after PR merge
|
||||
|
||||
name: "Branch Cleanup"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
name: Delete merged branch
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.head.ref != 'dev' &&
|
||||
github.event.pull_request.head.ref != 'main'
|
||||
|
||||
steps:
|
||||
- name: Delete source branch
|
||||
run: |
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BRANCH}', safe=''))")
|
||||
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||
|
||||
if [ "$STATUS" = "204" ]; then
|
||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "$STATUS" = "404" ]; then
|
||||
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
||||
fi
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# INGROUP: MokoStandards.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||
# PATH: /templates/workflows/cascade-dev.yml.template
|
||||
# VERSION: 02.00.00
|
||||
# BRIEF: Forward-merge main → all open branches after every push to main
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Discover target branches
|
||||
id: branches
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
ALL_BRANCHES=""
|
||||
while true; do
|
||||
BATCH=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/branches?page=${PAGE}&limit=50" \
|
||||
| jq -r '.[].name // empty')
|
||||
[ -z "$BATCH" ] && break
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
- name: Cascade to all target branches
|
||||
if: steps.branches.outputs.targets != ''
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
# Check if branch is already up to date
|
||||
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
|
||||
RESPONSE=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/compare/${ENCODED_BRANCH}...main")
|
||||
|
||||
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
|
||||
# Check for existing cascade PR
|
||||
EXISTING=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
|
||||
|
||||
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
# Create cascade PR
|
||||
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
|
||||
# Try auto-merge
|
||||
PR_DATA=$(curl -sS \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/pulls/${PR_NUMBER}")
|
||||
|
||||
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
|
||||
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"Do\": \"merge\",
|
||||
|
||||
@@ -43,9 +43,9 @@ jobs:
|
||||
|
||||
- name: Clone MokoStandards
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
||||
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||
run: |
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install \
|
||||
@@ -346,7 +346,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install \
|
||||
@@ -391,7 +391,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
@@ -458,10 +458,10 @@ jobs:
|
||||
steps:
|
||||
- name: Trigger pre-release build
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.head_ref }}
|
||||
run: |
|
||||
curl -s -X POST "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
curl -s -X POST "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# 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
|
||||
@@ -33,17 +33,17 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
token: ${{ secrets.GA_TOKEN }}
|
||||
|
||||
- name: Delete merged branches
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Merged Branch Cleanup ==="
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
|
||||
# List branches via API
|
||||
BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||
|
||||
DELETED=0
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Check if branch is merged into main
|
||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||
echo " Deleting merged branch: ${BRANCH}"
|
||||
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
fi
|
||||
@@ -66,20 +66,20 @@ jobs:
|
||||
|
||||
- name: Clean old workflow runs
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Workflow Run Cleanup ==="
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Get old completed runs
|
||||
RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/actions/runs?status=completed&limit=50" | \
|
||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||
|
||||
DELETED=0
|
||||
for RUN_ID in $RUNS; do
|
||||
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
done
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Security
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# 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
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
steps:
|
||||
- name: Create branch and comment
|
||||
run: |
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Notifications
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# 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
|
||||
@@ -18,6 +18,7 @@ on:
|
||||
- "Joomla Build & Release"
|
||||
- "Joomla Extension CI"
|
||||
- "Deploy"
|
||||
- "Cascade Main → Dev"
|
||||
types:
|
||||
- completed
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.CI
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# INGROUP: MokoStandards.CI
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||
# PATH: /templates/workflows/universal/pr-check.yml.template
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: PR gate — branch policy + code validation before merge
|
||||
@@ -108,9 +108,8 @@ jobs:
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
# 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:]')
|
||||
# Parse manifest for platform detection
|
||||
PLATFORM=$(php /tmp/mokostandards-api/cli/manifest_read.php --path . --field platform 2>/dev/null)
|
||||
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -195,6 +194,35 @@ jobs:
|
||||
echo "Source: ${FILE_COUNT} files"
|
||||
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
|
||||
|
||||
# ── Changelog Gate ────────────────────────────────────────────────────
|
||||
changelog:
|
||||
name: Changelog Updated
|
||||
runs-on: ubuntu-latest
|
||||
if: github.base_ref == 'main'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check CHANGELOG.md was updated
|
||||
run: |
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
HEAD="${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
if git diff --name-only "$BASE" "$HEAD" | grep -q "^CHANGELOG.md$"; then
|
||||
echo "CHANGELOG.md updated"
|
||||
else
|
||||
# Allow [skip changelog] in PR title or body
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
PR_BODY="${{ github.event.pull_request.body }}"
|
||||
if echo "$PR_TITLE $PR_BODY" | grep -qi "\[skip changelog\]"; then
|
||||
echo "::warning::Changelog skip requested via [skip changelog]"
|
||||
exit 0
|
||||
fi
|
||||
echo "::error::CHANGELOG.md must be updated before merging to main. Add [skip changelog] to the PR title to bypass."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Pre-Release RC Build ─────────────────────────────────────────────────
|
||||
pre-release:
|
||||
name: Build RC Package
|
||||
@@ -204,11 +232,11 @@ jobs:
|
||||
steps:
|
||||
- name: Trigger RC pre-release
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.head_ref }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -1,233 +1,223 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Pre-release channel'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- release-candidate
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
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:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
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
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
STABILITY="${{ inputs.stability || 'development' }}"
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Read current version (bump already handled by push workflow)
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||
|
||||
# Strip any existing suffix from version before applying stability
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||
|
||||
# Verify version consistency across all files
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Update VERSION variable with suffix
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
# Commit version bump
|
||||
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 || {
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Auto-detect element via manifest_element.php
|
||||
php ${MOKO_CLI}/manifest_element.php \
|
||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||
--repo "${GITEA_REPO}" --github-output
|
||||
|
||||
# Read back element outputs
|
||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||
fi
|
||||
|
||||
- name: "Sync updates.xml to all branches"
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
|
||||
for BRANCH in main dev; do
|
||||
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||
echo "Syncing updates.xml -> ${BRANCH}"
|
||||
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||
git checkout "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue
|
||||
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git add updates.xml
|
||||
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
|
||||
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
|
||||
fi
|
||||
git checkout "${CURRENT_BRANCH}" 2>/dev/null
|
||||
done
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Pre-release channel'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- release-candidate
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
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:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GA_TOKEN }}
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
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
|
||||
fi
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
STABILITY="${{ inputs.stability || 'development' }}"
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Read current version (bump already handled by push workflow)
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
|
||||
|
||||
# Verify version consistency across all files
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Commit version bump
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Auto-detect element via manifest_element.php
|
||||
php ${MOKO_CLI}/manifest_element.php \
|
||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||
--repo "${GITEA_REPO}" --github-output
|
||||
|
||||
# Read back element outputs
|
||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||
fi
|
||||
|
||||
- name: "Sync updates.xml to all branches"
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
|
||||
for BRANCH in main dev; do
|
||||
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||
echo "Syncing updates.xml -> ${BRANCH}"
|
||||
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||
git checkout "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue
|
||||
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git add updates.xml
|
||||
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
|
||||
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
|
||||
fi
|
||||
git checkout "${CURRENT_BRANCH}" 2>/dev/null
|
||||
done
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -7,14 +7,18 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Validation
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# INGROUP: MokoStandards.Validation
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||
# PATH: /templates/workflows/joomla/repo_health.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
|
||||
# ============================================================================
|
||||
|
||||
name: "Generic: Repo Health"
|
||||
name: "Joomla: Repo Health"
|
||||
|
||||
concurrency:
|
||||
group: repo-health-${{ github.repository }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -49,7 +53,7 @@ env:
|
||||
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
|
||||
|
||||
# Repo health policy
|
||||
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/
|
||||
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.gitea/workflows/
|
||||
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
|
||||
REPO_DISALLOWED_DIRS:
|
||||
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
||||
@@ -60,7 +64,7 @@ env:
|
||||
# File / directory variables
|
||||
DOCS_INDEX: docs/docs-index.md
|
||||
SCRIPT_DIR: scripts
|
||||
WORKFLOWS_DIR: .mokogitea/workflows
|
||||
WORKFLOWS_DIR: .gitea/workflows
|
||||
SHELLCHECK_PATTERN: '*.sh'
|
||||
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
@@ -81,7 +85,7 @@ jobs:
|
||||
- name: Check actor permission (admin only)
|
||||
id: perm
|
||||
env:
|
||||
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
||||
TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
run: |
|
||||
@@ -284,7 +288,7 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "${SCRIPTS_REQUIRED_DIRS:-}" ]; then IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"; else required_dirs=(); fi
|
||||
IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"
|
||||
IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}"
|
||||
|
||||
missing_dirs=()
|
||||
@@ -388,27 +392,23 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
|
||||
IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
|
||||
if [ -n "${REPO_DISALLOWED_DIRS:-}" ]; then IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"; else disallowed_dirs=(); fi
|
||||
IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES:-}"
|
||||
|
||||
missing_required=()
|
||||
missing_optional=()
|
||||
|
||||
# Source directory: src/ or htdocs/ (either is valid for extension repos)
|
||||
SOURCE_DIR=""
|
||||
# Source directory: src/ or htdocs/ (either is valid)
|
||||
if [ -d "src" ]; then
|
||||
SOURCE_DIR="src"
|
||||
elif [ -d "htdocs" ]; then
|
||||
SOURCE_DIR="htdocs"
|
||||
elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then
|
||||
# Platform/tooling repos don't need src/
|
||||
SOURCE_DIR=""
|
||||
else
|
||||
missing_required+=("src/ or htdocs/ (source directory required)")
|
||||
fi
|
||||
|
||||
IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
|
||||
IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
|
||||
IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"
|
||||
IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}"
|
||||
|
||||
missing_required=()
|
||||
missing_optional=()
|
||||
|
||||
for item in "${required_artifacts[@]}"; do
|
||||
if printf '%s' "${item}" | grep -q '/$'; then
|
||||
d="${item%/}"
|
||||
@@ -450,8 +450,12 @@ jobs:
|
||||
fi
|
||||
done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//')
|
||||
|
||||
if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then
|
||||
missing_required+=("dev or dev/* branch")
|
||||
if [ "${#dev_paths[@]}" -eq 0 ]; then
|
||||
missing_required+=("dev/* branch (e.g. dev/01.00.00)")
|
||||
fi
|
||||
|
||||
if [ "${#dev_branches[@]}" -gt 0 ]; then
|
||||
missing_required+=("invalid branch dev (must be dev/<version>)")
|
||||
fi
|
||||
|
||||
content_warnings=()
|
||||
@@ -477,7 +481,26 @@ jobs:
|
||||
export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")"
|
||||
export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
|
||||
|
||||
report_json=$(printf '{"profile":"%s","missing_required":%d,"missing_optional":%d,"content_warnings":%d}' "$profile" "${#missing_required[@]}" "${#missing_optional[@]}" "${#content_warnings[@]}")
|
||||
report_json="$(python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||
|
||||
missing_required = os.environ.get('MISSING_REQUIRED', '').splitlines() if os.environ.get('MISSING_REQUIRED') else []
|
||||
missing_optional = os.environ.get('MISSING_OPTIONAL', '').splitlines() if os.environ.get('MISSING_OPTIONAL') else []
|
||||
content_warnings = os.environ.get('CONTENT_WARNINGS', '').splitlines() if os.environ.get('CONTENT_WARNINGS') else []
|
||||
|
||||
out = {
|
||||
'profile': profile,
|
||||
'missing_required': [x for x in missing_required if x],
|
||||
'missing_optional': [x for x in missing_optional if x],
|
||||
'content_warnings': [x for x in content_warnings if x],
|
||||
}
|
||||
|
||||
print(json.dumps(out, indent=2))
|
||||
PY
|
||||
)"
|
||||
|
||||
{
|
||||
printf '%s\n' '### Repository health'
|
||||
@@ -555,14 +578,12 @@ jobs:
|
||||
joomla_findings+=("updates.xml missing in root (required for Joomla update server)")
|
||||
fi
|
||||
|
||||
if [ -n "${SOURCE_DIR}" ]; then
|
||||
INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site")
|
||||
for dir in "${INDEX_DIRS[@]}"; do
|
||||
if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then
|
||||
joomla_findings+=("${dir}/index.html missing (directory listing protection)")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site")
|
||||
for dir in "${INDEX_DIRS[@]}"; do
|
||||
if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then
|
||||
joomla_findings+=("${dir}/index.html missing (directory listing protection)")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#joomla_findings[@]}" -gt 0 ]; then
|
||||
{
|
||||
@@ -608,29 +629,43 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ -f "${DOCS_INDEX}" ]; then
|
||||
missing_links=""
|
||||
while IFS= read -r docline; do
|
||||
for link in $(echo "$docline" | grep -oE '\]\([^)]+\)' | sed 's/\](//' | sed 's/)$//' || true); do
|
||||
case "$link" in http://*|https://*|"#"*|mailto:*) continue ;; esac
|
||||
linkpath="${link%%#*}"
|
||||
linkpath="${linkpath%%\?*}"
|
||||
[ -z "$linkpath" ] && continue
|
||||
if [ "${linkpath:0:1}" = "/" ]; then
|
||||
testpath="${linkpath#/}"
|
||||
else
|
||||
testpath="$(dirname "${DOCS_INDEX}")/${linkpath}"
|
||||
fi
|
||||
[ ! -e "$testpath" ] && missing_links="${missing_links}${testpath} "
|
||||
done
|
||||
done < "${DOCS_INDEX}"
|
||||
missing_links="$(python3 - <<'PY'
|
||||
import os
|
||||
import re
|
||||
|
||||
idx = os.environ.get('DOCS_INDEX', 'docs/docs-index.md')
|
||||
base = os.getcwd()
|
||||
|
||||
bad = []
|
||||
pat = re.compile(r'\[[^\]]+\]\(([^)]+)\)')
|
||||
|
||||
with open(idx, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
for m in pat.findall(line):
|
||||
link = m.strip()
|
||||
if link.startswith('http://') or link.startswith('https://') or link.startswith('#') or link.startswith('mailto:'):
|
||||
continue
|
||||
if link.startswith('/'):
|
||||
rel = link.lstrip('/')
|
||||
else:
|
||||
rel = os.path.normpath(os.path.join(os.path.dirname(idx), link))
|
||||
rel = rel.split('#', 1)[0]
|
||||
rel = rel.split('?', 1)[0]
|
||||
if not rel:
|
||||
continue
|
||||
p = os.path.join(base, rel)
|
||||
if not os.path.exists(p):
|
||||
bad.append(rel)
|
||||
|
||||
print('\n'.join(sorted(set(bad))))
|
||||
PY
|
||||
)"
|
||||
if [ -n "${missing_links}" ]; then
|
||||
extended_findings+=("docs/docs-index.md contains broken relative links")
|
||||
{
|
||||
printf '%s\n' '### Docs index link integrity'
|
||||
printf '%s\n' 'Broken relative links:'
|
||||
for bl in ${missing_links}; do
|
||||
printf '%s\n' "- ${bl}"
|
||||
done
|
||||
while IFS= read -r l; do [ -n "${l}" ] && printf '%s\n' "- ${l}"; done <<< "${missing_links}"
|
||||
printf '\n'
|
||||
} >> "${GITHUB_STEP_SUMMARY}"
|
||||
fi
|
||||
@@ -729,41 +764,3 @@ jobs:
|
||||
fi
|
||||
|
||||
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
|
||||
|
||||
|
||||
site-health:
|
||||
name: Site Health
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
|
||||
- name: Uptime check
|
||||
if: env.URLS != ''
|
||||
run: |
|
||||
echo "$URLS" > /tmp/urls.txt
|
||||
php monitoring/uptime-probe.php --urls /tmp/urls.txt --timeout 15 || echo "::warning::Some sites are down"
|
||||
rm -f /tmp/urls.txt
|
||||
env:
|
||||
URLS: ${{ vars.MONITORED_URLS }}
|
||||
|
||||
- name: SSL certificate check
|
||||
if: env.DOMAINS != ''
|
||||
run: |
|
||||
echo "$DOMAINS" > /tmp/domains.txt
|
||||
php monitoring/ssl-check.php --domains /tmp/domains.txt --warn-days 30 || echo "::warning::SSL certificates expiring soon"
|
||||
rm -f /tmp/domains.txt
|
||||
env:
|
||||
DOMAINS: ${{ vars.MONITORED_DOMAINS }}
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "### Site Health" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Uptime and SSL checks completed." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: MokoStandards.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.gitea/workflows/security-audit.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
||||
@@ -80,19 +80,3 @@ jobs:
|
||||
-H "Priority: high" \
|
||||
-d "Security audit found vulnerabilities. Review dependency updates." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||
|
||||
|
||||
- name: Joomla version audit
|
||||
if: always()
|
||||
run: |
|
||||
if [ -f "monitoring/joomla-version-audit.php" ] && [ -n "$JOOMLA_SITES" ]; then
|
||||
echo "$JOOMLA_SITES" > /tmp/sites.json
|
||||
php monitoring/joomla-version-audit.php --sites /tmp/sites.json || true
|
||||
echo "### Joomla Version Audit" >> $GITHUB_STEP_SUMMARY
|
||||
rm -f /tmp/sites.json
|
||||
else
|
||||
echo "Joomla audit skipped (no script or JOOMLA_SITES_JSON not configured)"
|
||||
fi
|
||||
env:
|
||||
JOOMLA_SITES: ${{ vars.JOOMLA_SITES_JSON }}
|
||||
|
||||
|
||||
@@ -1,309 +1,304 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/update-server.yml
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||
#
|
||||
# Thin wrapper around moko-platform CLI tools.
|
||||
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||
#
|
||||
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||
|
||||
name: "Update Server"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Stability tag'
|
||||
required: true
|
||||
default: 'development'
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- rc
|
||||
- stable
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
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 }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-xml:
|
||||
name: Update Server
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
|
||||
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
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform 2>/dev/null || true
|
||||
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
||||
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve stability and bump version
|
||||
id: meta
|
||||
run: |
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
|
||||
# Configure git for bot pushes
|
||||
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"
|
||||
|
||||
# Auto-bump patch version
|
||||
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
|
||||
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||
|
||||
# Determine stability from branch or manual input
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
elif [[ "$BRANCH" == rc/* ]]; then
|
||||
STABILITY="rc"
|
||||
elif [[ "$BRANCH" == beta/* ]]; then
|
||||
STABILITY="beta"
|
||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||
STABILITY="alpha"
|
||||
else
|
||||
STABILITY="development"
|
||||
fi
|
||||
|
||||
# Version suffix per stability stream
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
*) SUFFIX=""; TAG="stable" ;;
|
||||
esac
|
||||
|
||||
# Propagate version with stability suffix to all manifest files
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Re-read version (now includes suffix from version_set_platform)
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Commit version bump if changed
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Create release and upload package
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Create or update Gitea release
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||
|
||||
# Build package and upload
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push updates.xml
|
||||
git add updates.xml
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Sync updates.xml to main
|
||||
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||
python3 -c "
|
||||
import base64, json, urllib.request, sys
|
||||
with open('updates.xml', 'rb') as f:
|
||||
content = base64.b64encode(f.read()).decode()
|
||||
payload = json.dumps({
|
||||
'content': content,
|
||||
'sha': '${FILE_SHA}',
|
||||
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
|
||||
'branch': 'main'
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/contents/updates.xml',
|
||||
data=payload, method='PUT',
|
||||
headers={
|
||||
'Authorization': 'token ${GITEA_TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
try:
|
||||
urllib.request.urlopen(req)
|
||||
print('updates.xml synced to main')
|
||||
except Exception as e:
|
||||
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||
"
|
||||
fi
|
||||
|
||||
- name: SFTP deploy to dev server
|
||||
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||
env:
|
||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
run: |
|
||||
# Permission check: admin or maintain role required
|
||||
ACTOR="${{ github.actor }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||
case "$PERMISSION" in
|
||||
admin|maintain|write) ;;
|
||||
*)
|
||||
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||
|
||||
PORT="${DEV_PORT:-22}"
|
||||
REMOTE="${DEV_PATH%/}"
|
||||
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||
if [ -n "$DEV_KEY" ]; then
|
||||
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
fi
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
DISPLAY="${{ steps.meta.outputs.display_version }}"
|
||||
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/update-server.yml
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||
#
|
||||
# Thin wrapper around moko-platform CLI tools.
|
||||
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||
#
|
||||
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||
|
||||
name: "Update Server"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Stability tag'
|
||||
required: true
|
||||
default: 'development'
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- rc
|
||||
- stable
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
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 }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-xml:
|
||||
name: Update Server
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
|
||||
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
|
||||
fi
|
||||
if [ -d "/tmp/moko-platform" ]; then
|
||||
echo "moko-platform already available — skipping clone"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform 2>/dev/null || true
|
||||
fi
|
||||
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
||||
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve stability and bump version
|
||||
id: meta
|
||||
run: |
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
|
||||
# Auto-bump patch version
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
|
||||
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||
|
||||
# Propagate version to all manifest files
|
||||
php ${MOKO_CLI}/version_set_platform.php --path . --version "$VERSION" --branch "$BRANCH" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Determine stability from branch or manual input
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
elif [[ "$BRANCH" == rc/* ]]; then
|
||||
STABILITY="rc"
|
||||
elif [[ "$BRANCH" == beta/* ]]; then
|
||||
STABILITY="beta"
|
||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||
STABILITY="alpha"
|
||||
else
|
||||
STABILITY="development"
|
||||
fi
|
||||
|
||||
# Version suffix
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
*) SUFFIX=""; TAG="stable" ;;
|
||||
esac
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "display_version=${VERSION}${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Commit version bump if changed
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Create release and upload package
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Create or update Gitea release
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||
|
||||
# Build package and upload
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push updates.xml
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Sync updates.xml to main
|
||||
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
|
||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||
python3 -c "
|
||||
import base64, json, urllib.request, sys
|
||||
with open('updates.xml', 'rb') as f:
|
||||
content = base64.b64encode(f.read()).decode()
|
||||
payload = json.dumps({
|
||||
'content': content,
|
||||
'sha': '${FILE_SHA}',
|
||||
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
|
||||
'branch': 'main'
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/contents/updates.xml',
|
||||
data=payload, method='PUT',
|
||||
headers={
|
||||
'Authorization': 'token ${GA_TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
try:
|
||||
urllib.request.urlopen(req)
|
||||
print('updates.xml synced to main')
|
||||
except Exception as e:
|
||||
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||
"
|
||||
fi
|
||||
|
||||
- name: SFTP deploy to dev server
|
||||
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||
env:
|
||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
run: |
|
||||
# Permission check: admin or maintain role required
|
||||
ACTOR="${{ github.actor }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||
case "$PERMISSION" in
|
||||
admin|maintain|write) ;;
|
||||
*)
|
||||
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||
|
||||
PORT="${DEV_PORT:-22}"
|
||||
REMOTE="${DEV_PATH%/}"
|
||||
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||
if [ -n "$DEV_KEY" ]; then
|
||||
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
fi
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
DISPLAY="${{ steps.meta.outputs.display_version }}"
|
||||
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
+286
-4
@@ -19,15 +19,13 @@
|
||||
-->
|
||||
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
All notable changes to the MokoWaaS plugin will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [02.17.00] --- 2026-05-28
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- Migrated all workflow and template paths from `.github/` to `.mokogitea/`
|
||||
@@ -36,7 +34,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
||||
- `plg_webservices_perfectpublisher`: REST API for Perfect Publisher (com_autotweet) — channels, posts, requests, rules, feeds, and stats
|
||||
|
||||
### Planned
|
||||
- License/subscription check
|
||||
@@ -146,3 +143,288 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
- Heartbeat receiver accepts any 200 status (registered/updated/ok)
|
||||
- script.php uses heartbeat receiver instead of Grafana API (fixes 403 RBAC)
|
||||
|
||||
## [02.01.37] - 2026-05-23
|
||||
|
||||
### Added
|
||||
- Health check endpoint at `/?mokowaas=health` with 16 diagnostic checks (#54)
|
||||
- Core checks: database latency, filesystem writability/size, cache, extensions
|
||||
- Backup checks: Akeeba Backup last backup date/status/size, days since, frequency
|
||||
- Security checks: Admin Tools WAF status, blocked requests 24h/7d
|
||||
- SSL certificate: expiry date, days left, issuer (degraded <30d, error <7d)
|
||||
- Scheduled tasks: Joomla task scheduler status, failed tasks 24h
|
||||
- Error log: PHP error log size, recent errors, last error message
|
||||
- Database size: total MB, table count, top 5 largest tables
|
||||
- Content stats: articles, categories, menu items, modules
|
||||
- User activity: total users, active sessions, failed logins 24h, last login
|
||||
- Mail system: mailer type, from address, SMTP host, queue count
|
||||
- SEO health: robots.txt, sitemap, htaccess, SEF status
|
||||
- Template info: site/admin template names, override count
|
||||
- Configuration drift: debug mode, error reporting, force SSL, caching
|
||||
- Human-readable `reason` field explaining degraded/error status
|
||||
- Site size reporting (images, media, tmp, cache, logs directories)
|
||||
- Heartbeat provisioning via receiver at bench.mokoconsulting.tech
|
||||
- Grafana datasource auto-provisioning via YAML (no API token needed)
|
||||
- ntfy notifications on heartbeat registration (mokowaas-heartbeat topic)
|
||||
- Grafana dashboard with 9 rows covering all 16 health checks
|
||||
- Auto-generated health API token (separate from Joomla user tokens)
|
||||
|
||||
### Changed
|
||||
- Health endpoint always enabled — no config toggle needed
|
||||
- Grafana provisioning uses heartbeat receiver pattern (replaces direct API)
|
||||
- Removed config fields: enable_health_endpoint, grafana_url, grafana_api_key
|
||||
- Migrated .gitea/ to .mokogitea/ directory standard
|
||||
- Updated all references from MokoStandards to moko-platform
|
||||
- Renamed Gitea references to MokoGitea in docs
|
||||
|
||||
### Fixed
|
||||
- SSL verification disabled for Grafana cURL calls (shared hosting)
|
||||
- cURL follow redirects enabled
|
||||
- updates.xml download URL uses correct `development` tag
|
||||
|
||||
### Security
|
||||
- Plugin hidden from plugin list for non-master users
|
||||
- Plugin settings restricted to master user only
|
||||
- Self-healing lock (enforceLocked) runs every page load
|
||||
- Uninstall blocked in preflight
|
||||
- Health endpoint requires HTTPS + bearer token
|
||||
- Heartbeat shared secret for receiver authentication
|
||||
|
||||
## [02.01.08] - 2026-04-07
|
||||
|
||||
### Added
|
||||
- Template-based language overrides with `{{BRAND_NAME}}`, `{{COMPANY_NAME}}`, `{{SUPPORT_URL}}` placeholders
|
||||
- Configurable brand name, company name, and support URL via plugin params
|
||||
- Sentinel-block merge pattern that preserves existing site overrides
|
||||
- Install respects user-defined overrides (non-overwrite)
|
||||
- ~50 override keys across admin and frontend
|
||||
- Powered by links with anchor tag to support URL
|
||||
- Login support URL enforcement (mokoconsulting.tech/support, /kb, /news)
|
||||
- Atum template branding via params (logoBrandLarge, logoBrandSmall, loginLogo)
|
||||
- Shipped media assets: logo.png, favicon.ico, favicon.svg, favicon_256.png
|
||||
- Favicon injection (SVG + ICO + Apple touch icon)
|
||||
- Admin color scheme via Atum template style params (hue, link-color, special-color)
|
||||
- Custom CSS textarea injection
|
||||
- Master user enforcement (persistent super admin — "Webmaster")
|
||||
- Emergency access (DB password + file verification two-factor)
|
||||
- IP whitelist via configuration.php (empty blocks access)
|
||||
- IP whitelist display in plugin config (shows current IPs + your IP)
|
||||
- All emergency access attempts logged to Joomla Action Logs
|
||||
- Email notification on successful emergency login
|
||||
- Tenant restrictions: Extension Installer, System Info, Global Configuration, Template code editor
|
||||
- Dynamic admin menu hiding via onPreprocessMenuItems
|
||||
- Disable install-from-URL for all users
|
||||
- Force HTTPS redirect (supports reverse proxy)
|
||||
- Admin session idle timeout (default 60 min, master user exempt)
|
||||
- Password policy (min length, uppercase, number, special character)
|
||||
- Upload type and size restrictions (default 100MB)
|
||||
- Maintenance actions: reset all hits, delete all versions
|
||||
- Auto-enable plugin on first install
|
||||
- Action log extension registration in #__action_logs_extensions and #__action_log_config
|
||||
- Custom AllowedIpsField form field for IP whitelist display
|
||||
- Joomla 5.x and 6.x compatibility
|
||||
|
||||
### Fixed
|
||||
- Column heading overrides removed (broke module/plugin list views)
|
||||
- RegularLabs Position column workaround
|
||||
- Nested `<a>` tags in login support overrides
|
||||
- Emergency access moved from onUserAuthenticate to onAfterInitialise (Joomla uses isolated auth dispatcher)
|
||||
- Session created directly for emergency login (bypasses auth dispatcher)
|
||||
- Auto-complete emergency login after verify file deletion (no re-entering credentials)
|
||||
|
||||
### Changed
|
||||
- Version bumped to 02.01.08 across all files
|
||||
- Configuration guide fully rewritten with all fieldsets documented
|
||||
- Testing guide with 17 test suites
|
||||
- README updated with Usage section, new features, Joomla 5/6 badges
|
||||
|
||||
## [01.04.00] - 2026-02-22
|
||||
|
||||
### Added
|
||||
- Complete Joomla 5.x system plugin implementation with modern architecture
|
||||
- Main plugin class (`src/mokowaas.php`) with event handlers:
|
||||
- `onAfterInitialise` event hook for framework initialization
|
||||
- `onAfterRoute` event hook for routing integration
|
||||
- Plugin manifest (`src/mokowaas.xml`) with Joomla 5.x namespace support
|
||||
- Namespace: `Moko\Plugin\System\MokoWaaS`
|
||||
- Configuration parameter for enabling/disabling branding
|
||||
- Dependency injection service provider (`src/services/provider.php`)
|
||||
- DI container registration for Joomla 5.x compatibility
|
||||
- Plugin language files in `src/language/en-GB/`:
|
||||
- `plg_system_mokowaas.ini` - Plugin UI strings
|
||||
- `plg_system_mokowaas.sys.ini` - System/installation strings
|
||||
- Enhanced language overrides (57+ strings):
|
||||
- Installation sample data branding
|
||||
- Site name labels
|
||||
- Admin-specific UI elements
|
||||
- Version and About sections
|
||||
- Security `index.html` files throughout directory structure
|
||||
- Comprehensive README.md with:
|
||||
- Badges for version, license, Joomla, and PHP compatibility
|
||||
- Table of contents with 12+ major sections
|
||||
- Detailed installation instructions (2 methods)
|
||||
- Technical implementation documentation
|
||||
- Repository structure overview
|
||||
- Development and build instructions
|
||||
|
||||
### Changed
|
||||
- Updated all documentation to version 01.04.00
|
||||
- Enhanced language overrides with more comprehensive coverage
|
||||
- Improved plugin configuration options
|
||||
|
||||
### Fixed
|
||||
- Typo in language override: "ERROR OCCURED" → "ERROR OCCURRED"
|
||||
- Repository references updated from placeholders to actual GitHub URLs
|
||||
|
||||
### Technical
|
||||
- Integrates with Joomla's native language override system
|
||||
- No programmatic string loading (performance optimization)
|
||||
- Event-driven architecture for minimal overhead
|
||||
- PSR-4 autoloading through service provider
|
||||
|
||||
## [01.03.00] - 2025-12-11
|
||||
|
||||
### Changed
|
||||
- General cleanup and code organization
|
||||
- Documentation structure improvements
|
||||
|
||||
## [01.02.01] - 2025-12-11
|
||||
|
||||
### Changed
|
||||
- Version bump for release alignment
|
||||
|
||||
## [01.02.00] - 2025-12-11
|
||||
|
||||
### Added
|
||||
- Documentation directory (`/docs/`) with comprehensive guides:
|
||||
- Installation guide
|
||||
- Configuration guide
|
||||
- Build guide
|
||||
- Operations guide
|
||||
- Troubleshooting guide
|
||||
- Upgrade and versioning guide
|
||||
- Rollback and recovery guide
|
||||
- GitHub workflow for automated builds (`.github/workflows/build.yml`)
|
||||
- Image and favicon replacement feature for complete branding
|
||||
|
||||
### Changed
|
||||
- Improved documentation structure and organization
|
||||
|
||||
## [01.01.05] - 2025-12-11
|
||||
|
||||
### Changed
|
||||
- Version bump for release coordination
|
||||
|
||||
## [01.01.04] - 2025-12-11
|
||||
|
||||
### Fixed
|
||||
- Plugin manifest corrections and validation fixes
|
||||
|
||||
## [01.01.03] - 2025-12-11
|
||||
|
||||
### Fixed
|
||||
- Administrator language file location corrected
|
||||
- Language override path alignment with Joomla standards
|
||||
|
||||
## [01.01.02] - 2025-12-11
|
||||
|
||||
### Changed
|
||||
- Moved plugin code to `/src/` directory for better organization
|
||||
- Aligned repository structure with release deployment pipeline
|
||||
- Improved packaging workflow
|
||||
|
||||
### Added
|
||||
- Release deployment pipeline integration
|
||||
- Automated build and validation scripts
|
||||
|
||||
## [1.0.0] - 2025-12-11
|
||||
|
||||
### Added
|
||||
- Initial release of MokoWaaS plugin
|
||||
- Basic language override system for Joomla rebranding
|
||||
- Frontend language overrides (en-GB, en-US)
|
||||
- Administrator language overrides (en-GB, en-US)
|
||||
- Core branding replacements:
|
||||
- Footer "Powered by" text
|
||||
- Control panel welcome messages
|
||||
- Help and documentation links
|
||||
- Generic Joomla→MokoWaaS replacements
|
||||
- Basic plugin structure and manifest
|
||||
- License (GPL-3.0-or-later)
|
||||
- Contributing guidelines
|
||||
- Code of conduct
|
||||
|
||||
### Technical Details
|
||||
- Joomla 5.x compatible
|
||||
- PHP 8.1+ requirement
|
||||
- Language override mechanism using Joomla's native system
|
||||
|
||||
---
|
||||
|
||||
## Version History Summary
|
||||
|
||||
| Version | Date | Type | Summary |
|
||||
|------------|------------|-----------|-------------------------------------------|
|
||||
| 01.04.00 | 2026-02-22 | Major | Complete plugin implementation & enhanced docs |
|
||||
| 01.03.00 | 2025-12-11 | Minor | Cleanup and organization |
|
||||
| 01.02.01 | 2025-12-11 | Patch | Version alignment |
|
||||
| 01.02.00 | 2025-12-11 | Minor | Documentation and build system |
|
||||
| 01.01.05 | 2025-12-11 | Patch | Version coordination |
|
||||
| 01.01.04 | 2025-12-11 | Patch | Manifest fixes |
|
||||
| 01.01.03 | 2025-12-11 | Patch | Language location fix |
|
||||
| 01.01.02 | 2025-12-11 | Patch | Repository restructuring |
|
||||
| 1.0.0 | 2025-12-11 | Major | Initial release |
|
||||
|
||||
---
|
||||
|
||||
## Upgrade Notes
|
||||
|
||||
### Upgrading to 01.04.00
|
||||
|
||||
**Breaking Changes:** None
|
||||
|
||||
**New Features:**
|
||||
- Complete Joomla 5.x plugin implementation
|
||||
- Dependency injection support
|
||||
- Enhanced language overrides (14+ new strings)
|
||||
|
||||
**Installation:**
|
||||
1. Backup your current installation
|
||||
2. Download the latest release package
|
||||
3. Install via Joomla Extension Manager
|
||||
4. Clear Joomla cache
|
||||
5. Verify branding appears correctly
|
||||
|
||||
### Upgrading to 01.02.00
|
||||
|
||||
**New Features:**
|
||||
- Comprehensive documentation in `/docs/`
|
||||
- Automated build workflows
|
||||
|
||||
**Notes:**
|
||||
- Review new documentation for operational guidance
|
||||
- Check GitHub workflows for automated builds
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
||||
|
||||
When adding entries to this changelog:
|
||||
1. Add new changes under `[Unreleased]` section
|
||||
2. Use categories: Added, Changed, Deprecated, Removed, Fixed, Security
|
||||
3. Include clear, concise descriptions
|
||||
4. Reference issue numbers where applicable
|
||||
5. Move items from Unreleased to versioned section upon release
|
||||
|
||||
## Links
|
||||
|
||||
- [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) - Coding and documentation standards
|
||||
- [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Changelog format specification
|
||||
- [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - Version numbering specification
|
||||
- [Repository](https://github.com/mokoconsulting-tech/mokowaas) - Project repository
|
||||
|
||||
---
|
||||
|
||||
**Note:** For detailed technical documentation, see the `/docs/` directory and [README.md](README.md).
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
VERSION: 02.17.00
|
||||
VERSION: 02.13.00
|
||||
PATH: /README.md
|
||||
BRIEF: MokoWaaS platform plugin for Joomla
|
||||
-->
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<description>Minimal API-only component for MokoWaaS. Provides REST endpoints for site health, cache, updates, and backups.</description>
|
||||
<namespace path="api/src">Moko\Component\MokoWaaS\Api</namespace>
|
||||
<administration>
|
||||
|
||||
@@ -162,11 +162,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
|
||||
// Security: HTTPS redirect (runs for all clients)
|
||||
$this->enforceHttps();
|
||||
|
||||
// Site alias handling: offline page and backend redirect.
|
||||
// Must run in onAfterInitialise (not onAfterRoute) so that
|
||||
// Joomla's offline check in doExecute() sees the updated config.
|
||||
$this->handleSiteAlias();
|
||||
|
||||
// MokoWaaS API endpoints (run before routing)
|
||||
$mokoAction = $this->app->input->get('mokowaas', '');
|
||||
|
||||
@@ -931,6 +926,9 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
|
||||
*/
|
||||
public function onAfterRoute()
|
||||
{
|
||||
// Site alias handling: offline page and backend redirect
|
||||
$this->handleSiteAlias();
|
||||
|
||||
if (!$this->app->isClient('administrator'))
|
||||
{
|
||||
return;
|
||||
@@ -3083,8 +3081,7 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
|
||||
/**
|
||||
* Handle site alias logic: offline page and backend redirect.
|
||||
*
|
||||
* Runs in onAfterInitialise so that Joomla's offline check in
|
||||
* SiteApplication::doExecute() sees the updated config value.
|
||||
* Runs early in onAfterInitialise before routing occurs.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE.md</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<description>This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.</description>
|
||||
<namespace path=".">Moko\Plugin\System\MokoWaaS</namespace>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<description>Joomla Web Services API routes for MokoWaaS site management — health checks, cache, updates, backups, and site info.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\MokoWaaS</namespace>
|
||||
<files>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Web Services - Perfect Publisher</name>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.17.00</version>
|
||||
<description>Joomla Web Services API routes for Perfect Publisher (com_autotweet) — channels, posts, requests, rules, and feeds.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\PerfectPublisher</namespace>
|
||||
<files>
|
||||
<folder plugin="perfectpublisher">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
</extension>
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/services/provider.php
|
||||
* VERSION: 02.13.01
|
||||
* BRIEF: DI service provider for Perfect Publisher Web Services plugin
|
||||
*/
|
||||
|
||||
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 Moko\Plugin\WebServices\PerfectPublisher\Extension\PerfectPublisherApi;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new PerfectPublisherApi(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('webservices', 'perfectpublisher')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,539 +0,0 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php
|
||||
* VERSION: 02.13.01
|
||||
* BRIEF: Web Services API plugin for Perfect Publisher (com_autotweet)
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\WebServices\PerfectPublisher\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Event\Application\BeforeApiRouteEvent;
|
||||
use Joomla\CMS\Router\ApiRouter;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* Perfect Publisher Web Services API Plugin
|
||||
*
|
||||
* Registers REST API routes for Perfect Publisher (com_autotweet) data.
|
||||
* Provides read access to channels, posts, requests, rules, and feeds.
|
||||
* Provides write access to create publish requests.
|
||||
*
|
||||
* Routes:
|
||||
* GET /v1/perfectpublisher/channels List social channels
|
||||
* GET /v1/perfectpublisher/channels/:id Get channel detail
|
||||
* GET /v1/perfectpublisher/posts List published posts
|
||||
* GET /v1/perfectpublisher/posts/:id Get post detail
|
||||
* GET /v1/perfectpublisher/requests List pending requests
|
||||
* POST /v1/perfectpublisher/requests Create a publish request
|
||||
* GET /v1/perfectpublisher/rules List publishing rules
|
||||
* GET /v1/perfectpublisher/feeds List RSS feeds
|
||||
* GET /v1/perfectpublisher/channeltypes List channel type definitions
|
||||
* GET /v1/perfectpublisher/stats Dashboard statistics
|
||||
*
|
||||
* @since 02.13.01
|
||||
*/
|
||||
final class PerfectPublisherApi extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onBeforeApiRoute' => 'onBeforeApiRoute',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register API routes.
|
||||
*
|
||||
* @param BeforeApiRouteEvent $event The API route event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onBeforeApiRoute(BeforeApiRouteEvent $event): void
|
||||
{
|
||||
$router = $event->getRouter();
|
||||
|
||||
// All routes are handled by this plugin directly via custom callbacks
|
||||
// because com_autotweet uses FOF, not standard Joomla MVC
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/channels',
|
||||
[$this, 'getChannels']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/channels/:id',
|
||||
[$this, 'getChannel']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/posts',
|
||||
[$this, 'getPosts']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/posts/:id',
|
||||
[$this, 'getPost']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/requests',
|
||||
[$this, 'getRequests']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['POST'],
|
||||
'v1/perfectpublisher/requests',
|
||||
[$this, 'createRequest']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/rules',
|
||||
[$this, 'getRules']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/feeds',
|
||||
[$this, 'getFeeds']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/channeltypes',
|
||||
[$this, 'getChannelTypes']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/stats',
|
||||
[$this, 'getStats']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/channels
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getChannels(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
$limit = (int) $app->input->get('limit', 20);
|
||||
$offset = (int) $app->input->get('offset', 0);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('c.*, ct.name AS channeltype_name, ct.max_chars')
|
||||
->from($db->quoteName('#__autotweet_channels', 'c'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channeltypes', 'ct')
|
||||
. ' ON ' . $db->quoteName('c.channeltype_id')
|
||||
. ' = ' . $db->quoteName('ct.id')
|
||||
)
|
||||
->order($db->quoteName('c.ordering') . ' ASC');
|
||||
|
||||
$published = $app->input->get('published', null);
|
||||
if ($published !== null) {
|
||||
$query->where($db->quoteName('c.published') . ' = ' . (int) $published);
|
||||
}
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/channels/:id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getChannel(): void
|
||||
{
|
||||
$id = (int) Factory::getApplication()->input->get('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('c.*, ct.name AS channeltype_name, ct.max_chars, ct.description AS channeltype_desc')
|
||||
->from($db->quoteName('#__autotweet_channels', 'c'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channeltypes', 'ct')
|
||||
. ' ON ' . $db->quoteName('c.channeltype_id')
|
||||
. ' = ' . $db->quoteName('ct.id')
|
||||
)
|
||||
->where($db->quoteName('c.id') . ' = ' . $id);
|
||||
|
||||
$db->setQuery($query);
|
||||
$result = $db->loadObject();
|
||||
|
||||
if (!$result) {
|
||||
$this->sendJsonError('Channel not found', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip sensitive OAuth params
|
||||
if (isset($result->params)) {
|
||||
$params = json_decode($result->params, true);
|
||||
if (is_array($params)) {
|
||||
foreach (['access_token', 'access_secret', 'client_secret', 'api_secret', 'password'] as $key) {
|
||||
if (isset($params[$key])) {
|
||||
$params[$key] = '***';
|
||||
}
|
||||
}
|
||||
$result->params = json_encode($params);
|
||||
}
|
||||
}
|
||||
|
||||
$this->sendJsonResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/posts
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getPosts(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
$limit = (int) $app->input->get('limit', 20);
|
||||
$offset = (int) $app->input->get('offset', 0);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('p.*, c.name AS channel_name')
|
||||
->from($db->quoteName('#__autotweet_posts', 'p'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channels', 'c')
|
||||
. ' ON ' . $db->quoteName('p.channel_id')
|
||||
. ' = ' . $db->quoteName('c.id')
|
||||
)
|
||||
->order($db->quoteName('p.postdate') . ' DESC');
|
||||
|
||||
$pubstate = $app->input->get('pubstate', '');
|
||||
if ($pubstate !== '') {
|
||||
$query->where($db->quoteName('p.pubstate') . ' = ' . $db->quote($pubstate));
|
||||
}
|
||||
|
||||
$channel = (int) $app->input->get('channel_id', 0);
|
||||
if ($channel > 0) {
|
||||
$query->where($db->quoteName('p.channel_id') . ' = ' . $channel);
|
||||
}
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/posts/:id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getPost(): void
|
||||
{
|
||||
$id = (int) Factory::getApplication()->input->get('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('p.*, c.name AS channel_name, ct.name AS channeltype_name')
|
||||
->from($db->quoteName('#__autotweet_posts', 'p'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channels', 'c')
|
||||
. ' ON ' . $db->quoteName('p.channel_id')
|
||||
. ' = ' . $db->quoteName('c.id')
|
||||
)
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channeltypes', 'ct')
|
||||
. ' ON ' . $db->quoteName('c.channeltype_id')
|
||||
. ' = ' . $db->quoteName('ct.id')
|
||||
)
|
||||
->where($db->quoteName('p.id') . ' = ' . $id);
|
||||
|
||||
$db->setQuery($query);
|
||||
$result = $db->loadObject();
|
||||
|
||||
if (!$result) {
|
||||
$this->sendJsonError('Post not found', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendJsonResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/requests
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getRequests(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
$limit = (int) $app->input->get('limit', 20);
|
||||
$offset = (int) $app->input->get('offset', 0);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__autotweet_requests'))
|
||||
->order($db->quoteName('publish_up') . ' ASC');
|
||||
|
||||
$published = $app->input->get('published', null);
|
||||
if ($published !== null) {
|
||||
$query->where($db->quoteName('published') . ' = ' . (int) $published);
|
||||
}
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /v1/perfectpublisher/requests
|
||||
*
|
||||
* Create a new publish request. Required fields: description.
|
||||
* Optional: url, image_url, publish_up, plugin, priority.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createRequest(): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$db = Factory::getDbo();
|
||||
$data = json_decode($app->input->json->getRaw(), true);
|
||||
|
||||
if (empty($data['description'])) {
|
||||
$this->sendJsonError('Field "description" is required', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$now = Factory::getDate()->toSql();
|
||||
$user = Factory::getUser();
|
||||
|
||||
$row = (object) [
|
||||
'ref_id' => $data['ref_id'] ?? null,
|
||||
'plugin' => $data['plugin'] ?? 'manual-api',
|
||||
'priority' => (int) ($data['priority'] ?? 5),
|
||||
'publish_up' => $data['publish_up'] ?? $now,
|
||||
'description' => $data['description'],
|
||||
'typeinfo' => (int) ($data['typeinfo'] ?? 0),
|
||||
'url' => $data['url'] ?? null,
|
||||
'image_url' => $data['image_url'] ?? null,
|
||||
'created' => $now,
|
||||
'created_by' => $user->id,
|
||||
'params' => json_encode($data['params'] ?? []),
|
||||
'published' => (int) ($data['published'] ?? 1),
|
||||
];
|
||||
|
||||
$db->insertObject('#__autotweet_requests', $row, 'id');
|
||||
|
||||
$this->sendJsonResponse(
|
||||
['id' => $row->id, 'status' => 'created'],
|
||||
201
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/rules
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getRules(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('r.*, rt.name AS ruletype_name, rt.description AS ruletype_desc, c.name AS channel_name')
|
||||
->from($db->quoteName('#__autotweet_rules', 'r'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_ruletypes', 'rt')
|
||||
. ' ON ' . $db->quoteName('r.ruletype_id')
|
||||
. ' = ' . $db->quoteName('rt.id')
|
||||
)
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channels', 'c')
|
||||
. ' ON ' . $db->quoteName('r.channel_id')
|
||||
. ' = ' . $db->quoteName('c.id')
|
||||
)
|
||||
->order($db->quoteName('r.ordering') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/feeds
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getFeeds(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__autotweet_feeds'))
|
||||
->order($db->quoteName('ordering') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/channeltypes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getChannelTypes(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__autotweet_channeltypes'))
|
||||
->order($db->quoteName('id') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/stats
|
||||
*
|
||||
* Dashboard statistics: post counts by status, channel counts, recent activity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getStats(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Posts by status
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('pubstate, COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_posts'))
|
||||
->group($db->quoteName('pubstate'))
|
||||
);
|
||||
$postsByStatus = $db->loadObjectList('pubstate');
|
||||
|
||||
// Active channels
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_channels'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
);
|
||||
$activeChannels = (int) $db->loadResult();
|
||||
|
||||
// Pending requests
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_requests'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
);
|
||||
$pendingRequests = (int) $db->loadResult();
|
||||
|
||||
// Posts last 24h
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_posts'))
|
||||
->where($db->quoteName('postdate') . ' >= DATE_SUB(NOW(), INTERVAL 1 DAY)')
|
||||
);
|
||||
$posts24h = (int) $db->loadResult();
|
||||
|
||||
// Posts last 7d
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_posts'))
|
||||
->where($db->quoteName('postdate') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)')
|
||||
);
|
||||
$posts7d = (int) $db->loadResult();
|
||||
|
||||
$this->sendJsonResponse([
|
||||
'posts_by_status' => $postsByStatus,
|
||||
'active_channels' => $activeChannels,
|
||||
'pending_requests' => $pendingRequests,
|
||||
'posts_24h' => $posts24h,
|
||||
'posts_7d' => $posts7d,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON API response.
|
||||
*
|
||||
* @param mixed $data Response data
|
||||
* @param int $status HTTP status code
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sendJsonResponse($data, int $status = 200): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
$app->setHeader('Status', (string) $status);
|
||||
echo json_encode(['data' => $data], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
$app->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON error response.
|
||||
*
|
||||
* @param string $message Error message
|
||||
* @param int $status HTTP status code
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sendJsonError(string $message, int $status = 400): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
$app->setHeader('Status', (string) $status);
|
||||
echo json_encode(['error' => $message], JSON_UNESCAPED_SLASHES);
|
||||
$app->close();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<extension type="package" method="upgrade">
|
||||
<name>MokoWaaS</name>
|
||||
<packagename>mokowaas</packagename>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<creationDate>2026-05-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
@@ -16,7 +16,6 @@
|
||||
<file type="plugin" id="plg_system_mokowaas" group="system">plg_system_mokowaas.zip</file>
|
||||
<file type="component" id="com_mokowaas">com_mokowaas.zip</file>
|
||||
<file type="plugin" id="plg_webservices_mokowaas" group="webservices">plg_webservices_mokowaas.zip</file>
|
||||
<file type="plugin" id="plg_webservices_perfectpublisher" group="webservices">plg_webservices_perfectpublisher.zip</file>
|
||||
</files>
|
||||
|
||||
<updateservers>
|
||||
|
||||
+16
-16
@@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 02.17.00
|
||||
VERSION: 02.13.00
|
||||
-->
|
||||
|
||||
<updates>
|
||||
@@ -11,13 +11,13 @@
|
||||
<element>pkg_mokowaas</element>
|
||||
<type>package</type>
|
||||
<client>site</client>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<infourl title='Package - MokoWaaS'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.17.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.13.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>90c7d5dee426acf127e82690abd4c1013d64edd88db8e961d83bad98a7f20eae</sha256>
|
||||
<sha256>55508a6bc63f9f72d5f8b7c728c8061a9b50a3fc1db9948fb61f985578dd5e65</sha256>
|
||||
<tags><tag>dev</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -29,13 +29,13 @@
|
||||
<element>pkg_mokowaas</element>
|
||||
<type>package</type>
|
||||
<client>site</client>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<infourl title='Package - MokoWaaS'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.17.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.13.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>90c7d5dee426acf127e82690abd4c1013d64edd88db8e961d83bad98a7f20eae</sha256>
|
||||
<sha256>55508a6bc63f9f72d5f8b7c728c8061a9b50a3fc1db9948fb61f985578dd5e65</sha256>
|
||||
<tags><tag>alpha</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -47,13 +47,13 @@
|
||||
<element>pkg_mokowaas</element>
|
||||
<type>package</type>
|
||||
<client>site</client>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<infourl title='Package - MokoWaaS'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.17.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.13.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>90c7d5dee426acf127e82690abd4c1013d64edd88db8e961d83bad98a7f20eae</sha256>
|
||||
<sha256>55508a6bc63f9f72d5f8b7c728c8061a9b50a3fc1db9948fb61f985578dd5e65</sha256>
|
||||
<tags><tag>beta</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -65,13 +65,13 @@
|
||||
<element>pkg_mokowaas</element>
|
||||
<type>package</type>
|
||||
<client>site</client>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<infourl title='Package - MokoWaaS'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.17.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.13.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>90c7d5dee426acf127e82690abd4c1013d64edd88db8e961d83bad98a7f20eae</sha256>
|
||||
<sha256>55508a6bc63f9f72d5f8b7c728c8061a9b50a3fc1db9948fb61f985578dd5e65</sha256>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
@@ -83,13 +83,13 @@
|
||||
<element>pkg_mokowaas</element>
|
||||
<type>package</type>
|
||||
<client>site</client>
|
||||
<version>02.17.00</version>
|
||||
<version>02.13.00</version>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<infourl title='Package - MokoWaaS'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.17.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.13.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>90c7d5dee426acf127e82690abd4c1013d64edd88db8e961d83bad98a7f20eae</sha256>
|
||||
<sha256>55508a6bc63f9f72d5f8b7c728c8061a9b50a3fc1db9948fb61f985578dd5e65</sha256>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
# API Endpoints
|
||||
|
||||
MokoWaaS provides 6 remote management endpoints accessible via query string parameter. All endpoints require HTTPS and Bearer token authentication.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require the `health_api_token` as a Bearer token in the Authorization header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <health_api_token>
|
||||
```
|
||||
|
||||
The token is auto-generated during plugin installation and stored as a read-only parameter in the plugin configuration. It can also be passed as a `token` query parameter as a fallback.
|
||||
|
||||
Token validation uses `hash_equals()` for timing-safe comparison. If no token is configured, the endpoint returns HTTP 503. An invalid token returns HTTP 401.
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 1. Health Check
|
||||
|
||||
```
|
||||
GET /?mokowaas=health
|
||||
```
|
||||
|
||||
Runs 16 diagnostic checks and returns a comprehensive health report. See [Health Monitoring](Health-Monitoring) for full documentation of all checks and response format.
|
||||
|
||||
**Response**: JSON object with `status` (`ok`/`degraded`/`error`), `reason`, `timestamp`, `checks`, and `meta`.
|
||||
|
||||
**HTTP Status**: 200 (ok/degraded), 503 (error).
|
||||
|
||||
---
|
||||
|
||||
### 2. Site Info
|
||||
|
||||
```
|
||||
GET /?mokowaas=info
|
||||
```
|
||||
|
||||
Returns a compact summary of the Joomla site.
|
||||
|
||||
**Response**:
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `site_name` | Joomla site name |
|
||||
| `site_url` | Site root URL |
|
||||
| `joomla_version` | Joomla CMS version |
|
||||
| `php_version` | PHP version |
|
||||
| `db_type` | Database driver (e.g. `pdomysql`) |
|
||||
| `debug` | Whether debug mode is on |
|
||||
| `sef` | Whether SEF URLs are enabled |
|
||||
| `caching` | Whether caching is enabled |
|
||||
| `articles` | Total article count |
|
||||
| `users` | Total user count |
|
||||
| `extensions` | Number of enabled extensions |
|
||||
| `brand` | Configured brand name |
|
||||
| `plugin_version` | MokoWaaS plugin version |
|
||||
|
||||
---
|
||||
|
||||
### 3. Remote Install
|
||||
|
||||
```
|
||||
POST /?mokowaas=install
|
||||
Content-Type: application/json
|
||||
|
||||
{"url": "https://example.com/extension.zip"}
|
||||
```
|
||||
|
||||
Downloads and installs a Joomla extension from the provided URL. The extension is downloaded to a temporary directory, extracted, and installed using Joomla's installer API.
|
||||
|
||||
**Response**: JSON object with `status`, `extension` name, and `message`.
|
||||
|
||||
**HTTP Status**: 200 (success), 400 (missing URL), 405 (not POST), 500 (install failed).
|
||||
|
||||
---
|
||||
|
||||
### 4. Update Check
|
||||
|
||||
```
|
||||
POST /?mokowaas=update
|
||||
```
|
||||
|
||||
Clears the Joomla update cache and triggers a fresh update check via `Updater::findUpdates()`.
|
||||
|
||||
**Response**:
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` |
|
||||
| `updates_found` | Number of available updates |
|
||||
| `message` | Human-readable summary |
|
||||
|
||||
**HTTP Status**: 200 (success), 405 (not POST), 500 (failed).
|
||||
|
||||
---
|
||||
|
||||
### 5. Cache Clear
|
||||
|
||||
```
|
||||
POST /?mokowaas=cache
|
||||
```
|
||||
|
||||
Clears the Joomla site cache, admin cache, and PHP OPcache (if available).
|
||||
|
||||
**Response**:
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` |
|
||||
| `message` | `Cache cleared` |
|
||||
|
||||
**HTTP Status**: 200 (success), 405 (not POST), 500 (failed).
|
||||
|
||||
---
|
||||
|
||||
### 6. Backup (Akeeba)
|
||||
|
||||
```
|
||||
POST /?mokowaas=backup
|
||||
Content-Type: application/json
|
||||
|
||||
{"profile": 1}
|
||||
```
|
||||
|
||||
Triggers an Akeeba Backup using the specified profile (defaults to profile 1). Requires Akeeba Backup to be installed.
|
||||
|
||||
**Response**:
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `started` |
|
||||
| `profile` | Backup profile ID used |
|
||||
| `message` | `Backup started` |
|
||||
|
||||
**HTTP Status**: 200 (started), 404 (Akeeba not installed), 405 (not POST), 500 (failed), 501 (Akeeba Engine not loadable).
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints return errors in a consistent format:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Error description",
|
||||
"message": "Additional detail (optional)"
|
||||
}
|
||||
```
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
| HTTP Status | Meaning |
|
||||
|---|---|
|
||||
| 400 | Bad request (unknown action, missing parameters) |
|
||||
| 401 | Invalid or missing authentication token |
|
||||
| 405 | Wrong HTTP method (e.g. GET when POST is required) |
|
||||
| 500 | Server error during operation |
|
||||
| 503 | No API token configured |
|
||||
|
||||
## Unknown Actions
|
||||
|
||||
Requesting an unknown action returns HTTP 400 with the list of available actions:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Unknown action",
|
||||
"action": "invalid",
|
||||
"available": ["health", "install", "update", "cache", "backup", "info"]
|
||||
}
|
||||
```
|
||||
|
||||
## Joomla REST API Routes
|
||||
|
||||
In addition to the query-string endpoints above, MokoWaaS registers standard Joomla API routes via the `plg_webservices_mokowaas` plugin:
|
||||
|
||||
| Route | Controller |
|
||||
|---|---|
|
||||
| `GET /api/v1/mokowaas/health` | HealthController |
|
||||
| `POST /api/v1/mokowaas/cache` | CacheController |
|
||||
| `POST /api/v1/mokowaas/update` | UpdateController |
|
||||
|
||||
These routes use Joomla's standard API authentication (API token in `X-Joomla-Token` header) and are useful for integrations that already use the Joomla API framework.
|
||||
@@ -1,94 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
All MokoWaaS settings are managed in the Joomla plugin configuration under **System > Plugins > System - MokoWaaS**. Settings are organized into tabs (fieldsets).
|
||||
|
||||
## Basic (Branding)
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `enable_branding` | Yes/No | Yes | Enable white-label branding (language overrides, logos, colors) |
|
||||
| `brand_name` | Text | `MokoWaaS` | Brand name displayed throughout the admin interface |
|
||||
| `company_name` | Text | `Moko Consulting` | Company name used in footers and copyright notices |
|
||||
| `support_url` | URL | `https://mokoconsulting.tech` | Support link shown on the admin login page and dashboard |
|
||||
|
||||
## WaaS Access
|
||||
|
||||
Controls the master user system that designates a single operator account.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `enforce_master_user` | Yes/No | Yes | Enable master user enforcement; non-master Super Admins are restricted |
|
||||
| `master_username` | Text | `mokoconsulting` | Username of the designated master operator |
|
||||
| `master_email` | Email | `webmaster@mokoconsulting.tech` | Email address of the master user (for verification) |
|
||||
| `emergency_access` | Yes/No | Yes | Enable emergency access via database password + file-based 2FA |
|
||||
| `allowed_ips_display` | Display | -- | Read-only display of whitelisted IP addresses for emergency access |
|
||||
|
||||
## Maintenance
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `dev_mode` | Yes/No | No | Disable Joomla caching at runtime (does not modify `configuration.php`) |
|
||||
| `reset_hits` | Yes/No | No | Reset article hit counters on next admin load |
|
||||
| `delete_versions` | Yes/No | No | Purge content version history on next admin load |
|
||||
|
||||
## Visual Branding
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `color_primary` | Color | `#1a2744` | Primary brand color (buttons, accents) |
|
||||
| `color_sidebar` | Color | `#0f1b2d` | Admin sidebar background color |
|
||||
| `color_header` | Color | `#1a2744` | Admin header bar color |
|
||||
| `color_link` | Color | `#0051ad` | Link text color |
|
||||
| `brand_icon` | Text | -- | FontAwesome unicode code point (e.g. `f6d5`) for the brand icon |
|
||||
| `custom_css` | Textarea | -- | Custom CSS injected into every admin page |
|
||||
|
||||
## Tenant Restrictions
|
||||
|
||||
Controls what non-master Super Admin users can access.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `restrict_installer` | Yes/No | Yes | Block access to Extension Manager for non-master users |
|
||||
| `hide_sysinfo` | Yes/No | Yes | Hide System Information page from non-master users |
|
||||
| `restrict_global_config` | Yes/No | Yes | Block access to Global Configuration for non-master users |
|
||||
| `restrict_template_editing` | Yes/No | Yes | Prevent non-master users from editing template files |
|
||||
| `disable_install_url` | Yes/No | Yes | Remove the "Install from URL" tab in Extension Manager |
|
||||
| `hidden_menu_items` | Textarea | -- | Comma-separated list of admin menu item IDs to hide from non-master users |
|
||||
|
||||
## Site Aliases
|
||||
|
||||
Multi-domain support. See [Site Aliases](Site-Aliases) for full documentation.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `primary_domain` | Text | -- | The canonical domain for the site (e.g. `waas.dev.mokoconsulting.tech`) |
|
||||
| `site_aliases` | Subform | -- | Repeatable table of alias domains with per-alias settings |
|
||||
|
||||
Each alias entry contains:
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `domain` | Text | -- | Alias domain name (e.g. `www.example.com`) |
|
||||
| `offline` | Yes/No | No | Show offline page for this alias |
|
||||
| `offline_message` | Textarea | -- | Custom offline message (shown when `offline` is Yes) |
|
||||
| `robots` | List | `index, follow` | Robots meta directive for this alias |
|
||||
| `redirect_backend` | Yes/No | Yes | Redirect admin requests on this alias to the primary domain |
|
||||
|
||||
## Diagnostics
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `health_api_token` | Text (read-only) | -- | Auto-generated Bearer token for API authentication. Provisioned on install/update. Cannot be manually edited. |
|
||||
|
||||
## Security
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `force_https` | Yes/No | Yes | Redirect all HTTP requests to HTTPS (301 redirect) |
|
||||
| `admin_session_timeout` | Number | `60` | Idle timeout in minutes for admin sessions (0 = use Joomla default). Master user is exempt. |
|
||||
| `password_min_length` | Number | `12` | Minimum password length for user accounts |
|
||||
| `password_require_uppercase` | Yes/No | Yes | Require at least one uppercase letter |
|
||||
| `password_require_number` | Yes/No | Yes | Require at least one digit |
|
||||
| `password_require_special` | Yes/No | Yes | Require at least one special character |
|
||||
| `upload_allowed_types` | Text | `jpg,jpeg,png,gif,webp,svg,pdf,doc,docx,xls,xlsx` | Comma-separated list of allowed upload file extensions |
|
||||
| `upload_max_size_mb` | Number | `100` | Maximum upload file size in megabytes |
|
||||
@@ -1,127 +0,0 @@
|
||||
# Grafana Integration
|
||||
|
||||
MokoWaaS integrates with a Grafana monitoring stack hosted at `bench.mokoconsulting.tech`. The integration is automatic: on install or update, the plugin sends a heartbeat that provisions a Grafana datasource for the site.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
MokoWaaS Plugin (Joomla)
|
||||
|
|
||||
| POST /api/waas-heartbeat/register
|
||||
v
|
||||
Heartbeat Receiver (bench.mokoconsulting.tech)
|
||||
|
|
||||
|-- Writes Grafana Infinity datasource YAML
|
||||
|-- Restarts Grafana to pick up new datasource
|
||||
|-- Sends ntfy notification (mokowaas-heartbeat topic)
|
||||
v
|
||||
Grafana Dashboard
|
||||
|
|
||||
| GET /?mokowaas=health (per site, on schedule)
|
||||
v
|
||||
Health JSON from each registered site
|
||||
```
|
||||
|
||||
## Heartbeat Registration
|
||||
|
||||
### When It Fires
|
||||
|
||||
The heartbeat is sent automatically during:
|
||||
|
||||
- Plugin installation (`postflight` with type `install`)
|
||||
- Plugin update (`postflight` with type `update`)
|
||||
- Package installation (via `Pkg_MokowaasInstallerScript::sendHeartbeat()`)
|
||||
|
||||
### Payload
|
||||
|
||||
The plugin sends a POST request to `https://bench.mokoconsulting.tech/api/waas-heartbeat/register` with:
|
||||
|
||||
```json
|
||||
{
|
||||
"site_url": "https://example.com",
|
||||
"site_name": "Example Site",
|
||||
"health_token": "<health_api_token>",
|
||||
"action": "register"
|
||||
}
|
||||
```
|
||||
|
||||
Authentication uses a shared secret sent in the `X-MokoWaaS-Key` header.
|
||||
|
||||
### What the Receiver Does
|
||||
|
||||
On receiving a registration request, the heartbeat receiver:
|
||||
|
||||
1. Validates the `X-MokoWaaS-Key` header
|
||||
2. Generates a unique datasource UID from the site URL
|
||||
3. Writes a Grafana Infinity datasource YAML file to the Grafana provisioning directory
|
||||
4. Restarts Grafana to load the new datasource
|
||||
5. Sends an ntfy notification to the `mokowaas-heartbeat` topic with registration details
|
||||
|
||||
The datasource YAML configures a Grafana Infinity datasource that polls `/?mokowaas=health` on the registered site using the provided Bearer token.
|
||||
|
||||
### Response
|
||||
|
||||
On success (HTTP 200):
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"ds_uid": "mokowaas-example-com"
|
||||
}
|
||||
```
|
||||
|
||||
The `ds_uid` is logged in the Joomla admin message queue for reference.
|
||||
|
||||
## Grafana Dashboard
|
||||
|
||||
The MokoWaaS Grafana dashboard is organized into 9 rows covering all health metrics:
|
||||
|
||||
| Row | Panels |
|
||||
|---|---|
|
||||
| 1. Overview | Overall status, uptime, plugin version, Joomla version |
|
||||
| 2. Database | Connectivity, latency, driver, user count |
|
||||
| 3. Filesystem | Disk space, writable directories, site size |
|
||||
| 4. Extensions | Extension counts by type, pending updates |
|
||||
| 5. Backup | Last backup status, age, Akeeba health |
|
||||
| 6. Security | Admin Tools WAF, SSL certificate, blocked requests |
|
||||
| 7. Content | Article counts, categories, user activity |
|
||||
| 8. Infrastructure | Cache status, mail config, scheduled tasks, error log |
|
||||
| 9. Configuration | SEO settings, template info, config drift |
|
||||
|
||||
Each row contains panels that query the site's Infinity datasource using JSONPath expressions to extract values from the health check response.
|
||||
|
||||
## ntfy Notifications
|
||||
|
||||
Registration events trigger a notification to the `mokowaas-heartbeat` ntfy topic. Notifications include:
|
||||
|
||||
- Site URL
|
||||
- Site name
|
||||
- Registration action (new or update)
|
||||
- Datasource UID
|
||||
|
||||
Subscribe to notifications at `https://ntfy.sh/mokowaas-heartbeat` or use the ntfy app.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Heartbeat failed: connection error
|
||||
|
||||
The receiver at `bench.mokoconsulting.tech` may be unreachable. Check:
|
||||
|
||||
- DNS resolution for `bench.mokoconsulting.tech`
|
||||
- Outbound HTTPS connectivity from the Joomla server
|
||||
- Firewall rules allowing outbound port 443
|
||||
|
||||
Heartbeat failures are logged as warnings in Joomla's log and displayed in the admin message queue. They do not block plugin installation.
|
||||
|
||||
### Datasource not appearing in Grafana
|
||||
|
||||
- Verify the heartbeat completed successfully (check Joomla admin messages after install)
|
||||
- Check the Grafana provisioning directory on `bench.mokoconsulting.tech`
|
||||
- Ensure Grafana was restarted after provisioning
|
||||
- Verify the health endpoint is accessible from the Grafana server
|
||||
|
||||
### Health data not loading in dashboard
|
||||
|
||||
- Confirm the `health_api_token` matches between the plugin configuration and the Grafana datasource
|
||||
- Test the health endpoint directly: `curl -sk -H "Authorization: Bearer <token>" "https://example.com/?mokowaas=health"`
|
||||
- Check for SSL certificate issues between the Grafana server and the monitored site
|
||||
@@ -1,33 +0,0 @@
|
||||
# Health Endpoint
|
||||
|
||||
## Stable Release: 02.01.37
|
||||
|
||||
16 diagnostic checks via /?mokowaas=health (token-authenticated, HTTPS-only).
|
||||
|
||||
### Checks
|
||||
|
||||
Core: database, filesystem, cache, extensions
|
||||
Security: backup (Akeeba), security (Admin Tools), SSL certificate
|
||||
Operations: scheduled tasks, error log, database size, mail
|
||||
Content: articles, categories, users, sessions, failed logins
|
||||
Config: SEO, templates, debug mode, force SSL, caching
|
||||
|
||||
### Grafana Dashboard (9 rows)
|
||||
|
||||
Site Overview | Health Metrics | Infrastructure | Backup | Security | SSL/Cron | Content/Users | Mail/SEO/Config | DB/Errors
|
||||
|
||||
### Heartbeat
|
||||
|
||||
Auto-registers with Grafana via bench.mokoconsulting.tech/api/waas-heartbeat/register
|
||||
ntfy notifications on mokowaas-heartbeat topic
|
||||
|
||||
### Plugin Protection
|
||||
|
||||
Hidden from non-master users, settings blocked, self-healing lock, uninstall blocked.
|
||||
|
||||
---
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Minimum Version | 02.01.37 |
|
||||
| Platform | joomla |
|
||||
@@ -1,267 +0,0 @@
|
||||
# Health Monitoring
|
||||
|
||||
MokoWaaS includes a built-in health monitoring system that runs 16 diagnostic checks against the Joomla site. Results are returned as a JSON payload via the `/?mokowaas=health` endpoint.
|
||||
|
||||
## Endpoint
|
||||
|
||||
```
|
||||
GET https://example.com/?mokowaas=health
|
||||
Authorization: Bearer <health_api_token>
|
||||
```
|
||||
|
||||
The `health_api_token` is auto-generated during plugin installation and stored as a read-only plugin parameter. See [API Endpoints](API-Endpoints) for authentication details.
|
||||
|
||||
## Response Structure
|
||||
|
||||
The response includes an overall status, a human-readable reason string, a UTC timestamp, individual check results, and instance metadata.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | Overall status: `ok`, `degraded`, or `error` |
|
||||
| `reason` | Human-readable summary of issues (null when status is `ok`) |
|
||||
| `timestamp` | ISO 8601 UTC timestamp |
|
||||
| `checks` | Object containing all 16 check results |
|
||||
| `meta` | Instance metadata (brand, versions, server name) |
|
||||
|
||||
### Status Determination
|
||||
|
||||
- If any check returns `error`, the overall status is `error` and the HTTP status code is **503**.
|
||||
- If any check returns `degraded` (and none are `error`), the overall status is `degraded` with HTTP **200**.
|
||||
- Otherwise the overall status is `ok` with HTTP **200**.
|
||||
|
||||
## The 16 Checks
|
||||
|
||||
### 1. database
|
||||
|
||||
Tests database connectivity and query latency.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `error` |
|
||||
| `latency_ms` | Query round-trip time in milliseconds |
|
||||
| `driver` | Database driver name (e.g. `mysqli`, `pdomysql`) |
|
||||
| `users` | Total user count (sanity check) |
|
||||
|
||||
### 2. filesystem
|
||||
|
||||
Checks writable directories and disk space.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok`, `degraded` (low disk), or `error` (not writable) |
|
||||
| `tmp_writable` | Whether `/tmp` is writable |
|
||||
| `log_writable` | Whether `/administrator/logs` is writable |
|
||||
| `cache_writable` | Whether `/cache` is writable |
|
||||
| `free_disk_mb` | Free disk space in MB |
|
||||
| `total_disk_mb` | Total disk space in MB |
|
||||
| `site_size_mb` | Estimated site size in MB (images, media, tmp, cache, logs) |
|
||||
|
||||
Degraded when free disk is below 100 MB. Error when required directories are not writable.
|
||||
|
||||
### 3. cache
|
||||
|
||||
Reports Joomla cache configuration.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | Always `ok` |
|
||||
| `enabled` | Whether Joomla caching is active |
|
||||
| `handler` | Cache handler type (e.g. `file`, `redis`) |
|
||||
|
||||
### 4. extensions
|
||||
|
||||
Counts enabled extensions by type and checks for pending updates.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `degraded` (pending updates) |
|
||||
| `by_type` | Object with counts per extension type |
|
||||
| `pending_updates` | Number of available extension updates |
|
||||
|
||||
### 5. backup (Akeeba)
|
||||
|
||||
Checks Akeeba Backup status.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok`, `degraded`, or `error` |
|
||||
| `last_status` | Status of the last backup record (`complete`, `fail`, etc.) |
|
||||
| `days_since` | Days since the last backup |
|
||||
| `message` | Human-readable backup status |
|
||||
|
||||
Degraded when the last backup is older than 7 days or did not complete successfully.
|
||||
|
||||
### 6. security (Admin Tools)
|
||||
|
||||
Checks Admin Tools WAF status if installed.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok`, `degraded`, or `error` |
|
||||
| `waf_enabled` | Whether the Web Application Firewall is active |
|
||||
| `blocked_24h` | Number of blocked requests in the last 24 hours |
|
||||
|
||||
### 7. ssl
|
||||
|
||||
Checks SSL certificate validity and expiration.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok`, `degraded`, or `error` |
|
||||
| `days_left` | Days until certificate expiration |
|
||||
| `issuer` | Certificate issuer |
|
||||
| `valid_from` | Certificate start date |
|
||||
| `valid_to` | Certificate expiration date |
|
||||
|
||||
Degraded when the certificate expires within 30 days.
|
||||
|
||||
### 8. cron (Scheduled Tasks)
|
||||
|
||||
Checks Joomla scheduled task execution.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `degraded` |
|
||||
| `total_tasks` | Total number of scheduled tasks |
|
||||
| `failed_24h` | Tasks that failed in the last 24 hours |
|
||||
|
||||
### 9. errors (Error Log)
|
||||
|
||||
Analyzes recent Joomla error log entries.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `degraded` |
|
||||
| `recent_errors` | Count of recent error log entries |
|
||||
| `last_error` | Most recent error message |
|
||||
|
||||
### 10. db_size
|
||||
|
||||
Reports database size metrics.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `degraded` |
|
||||
| `total_mb` | Total database size in MB |
|
||||
| `tables` | Number of database tables |
|
||||
|
||||
### 11. content
|
||||
|
||||
Reports content statistics.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | Always `ok` |
|
||||
| `articles` | Total article count |
|
||||
| `categories` | Total category count |
|
||||
| `published` | Number of published articles |
|
||||
| `unpublished` | Number of unpublished articles |
|
||||
|
||||
### 12. users (User Activity)
|
||||
|
||||
Reports user statistics.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | Always `ok` |
|
||||
| `total` | Total user count |
|
||||
| `active_30d` | Users active in the last 30 days |
|
||||
| `blocked` | Number of blocked user accounts |
|
||||
|
||||
### 13. mail
|
||||
|
||||
Checks Joomla mail configuration.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `degraded` |
|
||||
| `mailer` | Mail handler type (e.g. `smtp`, `mail`, `sendmail`) |
|
||||
| `from` | Configured sender address |
|
||||
|
||||
### 14. seo
|
||||
|
||||
Checks SEO configuration.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `degraded` |
|
||||
| `sef` | Whether SEF URLs are enabled |
|
||||
| `sef_rewrite` | Whether URL rewriting is enabled |
|
||||
| `sitemap` | Whether a sitemap is detected |
|
||||
|
||||
### 15. template
|
||||
|
||||
Reports active template information.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | Always `ok` |
|
||||
| `site_template` | Active frontend template name |
|
||||
| `admin_template` | Active admin template name |
|
||||
|
||||
### 16. config (Config Drift)
|
||||
|
||||
Detects configuration anomalies.
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `status` | `ok` or `degraded` |
|
||||
| `issues` | Array of detected configuration problems |
|
||||
|
||||
Checks for issues such as debug mode enabled in production, error reporting set too high, or default database prefix still in use.
|
||||
|
||||
## Example Response
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "degraded",
|
||||
"reason": "2 extension updates available; SSL expires in 14 days",
|
||||
"timestamp": "2026-05-24T12:00:00Z",
|
||||
"checks": {
|
||||
"database": {
|
||||
"status": "ok",
|
||||
"latency_ms": 1.23,
|
||||
"driver": "pdomysql",
|
||||
"users": 5
|
||||
},
|
||||
"filesystem": {
|
||||
"status": "ok",
|
||||
"tmp_writable": true,
|
||||
"log_writable": true,
|
||||
"cache_writable": true,
|
||||
"free_disk_mb": 4500,
|
||||
"total_disk_mb": 20000,
|
||||
"site_size_mb": 320
|
||||
},
|
||||
"ssl": {
|
||||
"status": "degraded",
|
||||
"days_left": 14,
|
||||
"issuer": "Let's Encrypt",
|
||||
"valid_to": "2026-06-07"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"brand": "MokoWaaS",
|
||||
"plugin_version": "02.03.11",
|
||||
"joomla_version": "5.2.4",
|
||||
"php_version": "8.2.20",
|
||||
"server_name": "Example Site",
|
||||
"server_time": "2026-05-24T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(Remaining checks omitted for brevity.)
|
||||
|
||||
## Metadata
|
||||
|
||||
The `meta` object is included in every health response:
|
||||
|
||||
| Field | Description |
|
||||
|---|---|
|
||||
| `brand` | Configured brand name |
|
||||
| `plugin_version` | MokoWaaS plugin version |
|
||||
| `joomla_version` | Joomla CMS version |
|
||||
| `php_version` | PHP version |
|
||||
| `server_name` | Joomla site name |
|
||||
| `server_time` | Server UTC time |
|
||||
@@ -1,52 +0,0 @@
|
||||
# MokoWaaS
|
||||
|
||||
MokoWaaS is a Joomla 5.x / 6.x extension package that provides a configurable white-label identity layer, tenant management, health monitoring, and remote administration API for the MokoWaaS platform.
|
||||
|
||||
Developed by [Moko Consulting](https://mokoconsulting.tech). Licensed under GPL-3.0-or-later.
|
||||
|
||||
## Features
|
||||
|
||||
- **White-label branding** -- customizable brand name, colors, favicon, login page, and admin template theming
|
||||
- **Master user enforcement** -- designate a single operator account with elevated privileges; restrict other Super Admins
|
||||
- **Tenant restrictions** -- hide system info, block installer access, restrict global config and template editing, hide menu items
|
||||
- **Site aliases** -- multi-domain support with per-alias offline mode, robots directives, and backend redirects
|
||||
- **Health monitoring** -- 16 diagnostic checks exposed via authenticated JSON API
|
||||
- **Remote management API** -- 6 endpoints for health, info, install, update, cache clear, and backup
|
||||
- **Grafana integration** -- automatic heartbeat registration with Grafana Infinity datasource provisioning
|
||||
- **Plugin protection** -- protected flag, self-healing, hidden from non-master users, blocks disable/uninstall
|
||||
- **Security hardening** -- forced HTTPS, session timeouts, password policy, upload restrictions
|
||||
- **Emergency access** -- file-based two-factor verification for master user recovery
|
||||
- **Automatic updates** -- via Joomla update server hosted on Gitea
|
||||
|
||||
## Requirements
|
||||
|
||||
| Requirement | Minimum |
|
||||
|---|---|
|
||||
| Joomla | 5.0.0+ (5.x and 6.x supported) |
|
||||
| PHP | 8.1.0+ |
|
||||
|
||||
## Package Contents
|
||||
|
||||
The `pkg_mokowaas` package installs three extensions:
|
||||
|
||||
| Extension | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `plg_system_mokowaas` | System Plugin | Core branding, restrictions, health checks, API endpoints |
|
||||
| `com_mokowaas` | Component | REST API controllers (health, cache, update) |
|
||||
| `plg_webservices_mokowaas` | Webservices Plugin | Registers Joomla API routes for `v1/mokowaas/*` |
|
||||
|
||||
## Wiki Pages
|
||||
|
||||
- [Configuration](Configuration) -- All plugin settings organized by tab
|
||||
- [Health Monitoring](Health-Monitoring) -- The 16 diagnostic checks
|
||||
- [Site Aliases](Site-Aliases) -- Multi-domain management
|
||||
- [API Endpoints](API-Endpoints) -- The 6 remote management endpoints
|
||||
- [Grafana Integration](Grafana-Integration) -- Monitoring dashboard setup
|
||||
- [Plugin Protection](Plugin-Protection) -- Security measures preventing disable/uninstall
|
||||
- [Installation](Installation) -- Step-by-step install and upgrade guide
|
||||
|
||||
## Links
|
||||
|
||||
- **Repository**: [MokoWaaS on Gitea](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS)
|
||||
- **Update Server**: [updates.xml](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/raw/branch/main/updates.xml)
|
||||
- **Author**: [Moko Consulting](https://mokoconsulting.tech)
|
||||
@@ -1,100 +0,0 @@
|
||||
# Installation
|
||||
|
||||
MokoWaaS is distributed as a Joomla package (`pkg_mokowaas`) containing three extensions. It can be installed via the standard Joomla Extension Manager.
|
||||
|
||||
## Requirements
|
||||
|
||||
| Requirement | Minimum |
|
||||
|---|---|
|
||||
| Joomla | 5.0.0+ |
|
||||
| PHP | 8.1.0+ |
|
||||
|
||||
The installer checks both requirements during `preflight` and aborts with an error message if either is not met.
|
||||
|
||||
## Method 1: Upload Package File
|
||||
|
||||
1. Download the latest `pkg_mokowaas.zip` from the [Gitea Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases) page.
|
||||
2. In Joomla admin, navigate to **System > Install > Extensions**.
|
||||
3. On the **Upload Package File** tab, drag and drop or browse to select `pkg_mokowaas.zip`.
|
||||
4. Click **Upload & Install**.
|
||||
5. Verify the success message. The package installs three extensions:
|
||||
- `plg_system_mokowaas` (System Plugin)
|
||||
- `com_mokowaas` (Component)
|
||||
- `plg_webservices_mokowaas` (Webservices Plugin)
|
||||
|
||||
## Method 2: Install from URL
|
||||
|
||||
1. In Joomla admin, navigate to **System > Install > Extensions**.
|
||||
2. On the **Install from URL** tab, enter the direct download URL for the package ZIP, e.g.:
|
||||
```
|
||||
https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-latest.zip
|
||||
```
|
||||
3. Click **Install**.
|
||||
4. Verify the success message.
|
||||
|
||||
## Post-Installation Verification
|
||||
|
||||
After installation, verify the following:
|
||||
|
||||
1. **Plugin enabled**: Navigate to **System > Plugins** and confirm "System - MokoWaaS" is enabled (it is auto-enabled during installation).
|
||||
2. **Health token generated**: Open the plugin configuration and check the **Diagnostics** tab. The `health_api_token` field should contain an auto-generated token.
|
||||
3. **Branding applied**: The admin login page and dashboard should reflect MokoWaaS branding (logo, colors, footer text).
|
||||
4. **Health endpoint**: Test the health endpoint:
|
||||
```
|
||||
curl -sk -H "Authorization: Bearer <token>" "https://yoursite.com/?mokowaas=health"
|
||||
```
|
||||
5. **Grafana heartbeat**: Check the admin message queue for a Grafana heartbeat confirmation message.
|
||||
|
||||
## What the Installer Does
|
||||
|
||||
During `postflight`, the installer script performs these operations:
|
||||
|
||||
| Step | Description |
|
||||
|---|---|
|
||||
| Enable and protect plugin | Sets `enabled=1`, `protected=1`, `locked=0` in `#__extensions` |
|
||||
| Install MokoOnyx template | Installs the bundled MokoOnyx site template from the plugin payload, sets it as default |
|
||||
| Language overrides | Deploys language override files for en-GB and en-US |
|
||||
| Login support URLs | Updates `mod_loginsupport` to point to Moko Consulting support/docs/news URLs |
|
||||
| Atum branding | Applies brand colors to the Atum admin template |
|
||||
| Action log registration | Registers MokoWaaS in the Joomla action log system |
|
||||
| Health token provisioning | Generates a random API token if one does not exist |
|
||||
| Heartbeat | Sends a registration heartbeat to the Grafana monitoring receiver |
|
||||
|
||||
## Automatic Updates
|
||||
|
||||
MokoWaaS includes an update server configuration that enables automatic update notifications through Joomla's built-in update system.
|
||||
|
||||
The update server URL is:
|
||||
```
|
||||
https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/raw/branch/main/updates.xml
|
||||
```
|
||||
|
||||
When a new version is available:
|
||||
|
||||
1. Joomla's update checker detects the new version from `updates.xml`.
|
||||
2. A notification appears in the admin dashboard.
|
||||
3. Navigate to **System > Update > Extensions** to install the update.
|
||||
4. The `postflight` script runs again, re-applying all configuration steps.
|
||||
|
||||
## Upgrading from Standalone Plugin to Package
|
||||
|
||||
If you previously installed the standalone `plg_system_mokowaas` plugin (before it was packaged as `pkg_mokowaas`), the package installer handles migration automatically:
|
||||
|
||||
1. Install the `pkg_mokowaas.zip` package using either method above.
|
||||
2. The package installer detects the existing standalone plugin and upgrades it in place.
|
||||
3. The additional extensions (`com_mokowaas`, `plg_webservices_mokowaas`) are installed alongside.
|
||||
4. All existing plugin settings are preserved.
|
||||
5. The `protected` flag is set on all package extensions.
|
||||
|
||||
No manual cleanup of the old standalone plugin is required.
|
||||
|
||||
## Uninstallation
|
||||
|
||||
Uninstallation is restricted to the master user. See [Plugin Protection](Plugin-Protection) for details.
|
||||
|
||||
When the master user uninstalls via the Extension Manager:
|
||||
|
||||
1. Language override files are removed from Joomla's global override directories.
|
||||
2. Action log registration is cleaned up.
|
||||
3. An uninstall notification is sent to the monitoring system.
|
||||
4. The package and all sub-extensions are removed.
|
||||
@@ -1,89 +0,0 @@
|
||||
# Plugin Protection
|
||||
|
||||
MokoWaaS uses multiple layers of protection to prevent accidental or unauthorized disabling and uninstallation. The master user retains full control over the plugin at all times.
|
||||
|
||||
## Protection Layers
|
||||
|
||||
### 1. Protected Flag (`protected=1`)
|
||||
|
||||
During installation and on every admin session, MokoWaaS sets the `protected` column to `1` in the `#__extensions` database table for both `mokowaas` and `pkg_mokowaas` entries.
|
||||
|
||||
The Joomla framework itself enforces this flag: protected extensions cannot be disabled or uninstalled through the standard admin interface.
|
||||
|
||||
The `locked` column is set to `0` so the extension can still receive updates and configuration changes.
|
||||
|
||||
### 2. Self-Healing
|
||||
|
||||
The `ensureProtectedFlag()` method runs once per admin session (using a static flag to avoid repeated queries). If the `protected` column has been reset to `0` (e.g., by a database modification), it is automatically restored to `1`.
|
||||
|
||||
This runs in the `protectPlugin()` method, which is called from `onBeforeRender()` on every admin page load.
|
||||
|
||||
### 3. Hidden from Plugin List
|
||||
|
||||
For non-master users, MokoWaaS injects JavaScript on the `com_plugins` and `com_installer` pages that hides any table row containing "mokowaas" or "MokoWaaS". This prevents non-master users from seeing the plugin in the extension list.
|
||||
|
||||
The `hidePluginFromList()` method adds an inline script that runs on `DOMContentLoaded`:
|
||||
|
||||
```javascript
|
||||
document.querySelectorAll('tr').forEach(function(row) {
|
||||
var text = row.textContent || '';
|
||||
if (text.indexOf('mokowaas') !== -1 || text.indexOf('MokoWaaS') !== -1) {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4. onExtensionBeforeSave Interception
|
||||
|
||||
The `onExtensionBeforeSave` event handler intercepts save attempts on the plugin configuration. If a non-master user attempts to save the plugin with `enabled = 0`, the handler:
|
||||
|
||||
1. Displays an error message: "MokoWaaS cannot be disabled."
|
||||
2. Forces `enabled` back to `1` on the table object
|
||||
3. Returns `true` to allow the save to proceed (with the corrected value)
|
||||
|
||||
### 5. protectPlugin() -- Uninstall and Disable Blocking
|
||||
|
||||
The `protectPlugin()` method runs on every admin page request and checks for active uninstall or disable attempts:
|
||||
|
||||
**Uninstall blocking**: If the current request is to `com_installer` with task `manage.remove`, and the extension IDs include any MokoWaaS extension, the request is blocked with an error message and a redirect back to the installer manage view.
|
||||
|
||||
**Disable blocking**: If the current request is to `com_plugins` with task `plugins.publish`, and the extension IDs include MokoWaaS, the request is blocked with an error message and a redirect back to the plugins list.
|
||||
|
||||
The `isOurExtension()` helper method checks extension IDs against the database to determine if they belong to MokoWaaS (matching on element name `mokowaas` or `pkg_mokowaas`).
|
||||
|
||||
## Master User Exemption
|
||||
|
||||
All protection checks call `isMasterUser()` first. If the current user is the designated master user (matching the configured `master_username`), all protections are bypassed. The master user can:
|
||||
|
||||
- See MokoWaaS in the plugin and extension lists
|
||||
- Disable the plugin via the configuration page
|
||||
- Uninstall the plugin via the Extension Manager
|
||||
- Modify all plugin settings
|
||||
|
||||
## Installation-Time Protection
|
||||
|
||||
The package installer script (`Pkg_MokowaasInstallerScript`) and the plugin installer script both set `protected=1` during `postflight`:
|
||||
|
||||
- `enableAndLockPlugin()` sets `enabled=1`, `locked=1`, `protected=1` on the system plugin
|
||||
- `protectExtensions()` sets `protected=1`, `locked=0` on all MokoWaaS extensions (plugin and package)
|
||||
|
||||
This ensures protection is active immediately after installation, before the first admin page load triggers the self-healing logic.
|
||||
|
||||
## Summary of Protection Flow
|
||||
|
||||
```
|
||||
Installation
|
||||
-> postflight sets protected=1, enabled=1
|
||||
|
||||
Every admin page load (onBeforeRender)
|
||||
-> protectPlugin()
|
||||
-> ensureProtectedFlag() (once per session, restores protected=1 if needed)
|
||||
-> if not master user:
|
||||
-> block uninstall attempts (com_installer manage.remove)
|
||||
-> block disable attempts (com_plugins plugins.publish)
|
||||
-> hidePluginFromList() for non-master users
|
||||
|
||||
Plugin config save (onExtensionBeforeSave)
|
||||
-> if not master user and enabled=0:
|
||||
-> force enabled=1, show error
|
||||
```
|
||||
@@ -1,69 +0,0 @@
|
||||
# Site Aliases
|
||||
|
||||
MokoWaaS supports multi-domain configurations through the Site Aliases system. This allows a single Joomla installation to respond to multiple domain names, each with independent settings for offline mode, robots directives, and backend access.
|
||||
|
||||
## Configuration
|
||||
|
||||
Site aliases are configured in the plugin settings under the **Site Aliases** tab.
|
||||
|
||||
### Primary Domain
|
||||
|
||||
Set `primary_domain` to the canonical domain for the site (e.g. `waas.dev.mokoconsulting.tech`). This is the domain that:
|
||||
|
||||
- Serves as the canonical URL for SEO purposes
|
||||
- Hosts the admin backend (when `redirect_backend` is enabled on aliases)
|
||||
- Is used in heartbeat registration with Grafana
|
||||
|
||||
### Alias Entries
|
||||
|
||||
Add alias domains using the repeatable subform table. Each alias entry has the following options:
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `domain` | Text | (required) | The alias domain name, e.g. `www.example.com` |
|
||||
| `offline` | Yes/No | No | When Yes, visitors to this alias see the offline page |
|
||||
| `offline_message` | Textarea | -- | Custom message displayed when the alias is offline (only shown when `offline` is Yes) |
|
||||
| `robots` | List | `index, follow` | Robots meta tag directive for this alias |
|
||||
| `redirect_backend` | Yes/No | Yes | When Yes, admin URLs (`/administrator`) on this alias redirect to the primary domain |
|
||||
|
||||
### Robots Options
|
||||
|
||||
Each alias can have its own robots directive:
|
||||
|
||||
| Value | Effect |
|
||||
|---|---|
|
||||
| `index, follow` | Normal indexing (default) |
|
||||
| `noindex, follow` | Do not index pages, but follow links |
|
||||
| `index, nofollow` | Index pages, do not follow links |
|
||||
| `noindex, nofollow` | Do not index or follow |
|
||||
| `none` | Equivalent to `noindex, nofollow` |
|
||||
|
||||
## How Canonical URLs Work
|
||||
|
||||
When a request arrives on an alias domain, MokoWaaS:
|
||||
|
||||
1. Matches the `HTTP_HOST` against configured alias domains
|
||||
2. Applies the alias-specific robots meta tag
|
||||
3. If the alias is marked offline, renders the offline page with the custom message
|
||||
4. If `redirect_backend` is enabled and the request is for `/administrator`, issues a 301 redirect to the primary domain's admin
|
||||
5. Sets the canonical URL to the primary domain equivalent of the current page
|
||||
|
||||
This prevents duplicate content issues when the same site is accessible from multiple domains.
|
||||
|
||||
## Grafana Monitoring for Aliases
|
||||
|
||||
When the plugin sends a heartbeat to the Grafana monitoring receiver, it registers both the primary domain and all alias domains. The monitoring dashboard can then track health status for each domain independently.
|
||||
|
||||
Each alias appears as a separate entry in the Grafana Infinity datasource, pointing to the same health endpoint but accessed via the alias domain. This ensures SSL certificate checks and DNS resolution are validated per-domain.
|
||||
|
||||
## DreamHost Mirror Setup
|
||||
|
||||
For sites hosted on DreamHost, alias domains are typically configured as "mirror" domains in the DreamHost panel:
|
||||
|
||||
1. In DreamHost panel, add the alias domain as a **Mirror** of the primary domain
|
||||
2. Ensure DNS for the alias domain points to the DreamHost server
|
||||
3. Add the alias domain to the MokoWaaS Site Aliases configuration
|
||||
4. Set `redirect_backend` to Yes (recommended) so admin access always uses the primary domain
|
||||
5. Set `robots` to `noindex, nofollow` if the alias is a staging or preview domain
|
||||
|
||||
DreamHost mirrors serve the same filesystem, so no additional Joomla configuration is needed beyond the MokoWaaS alias entry.
|
||||
Reference in New Issue
Block a user