Compare commits

...

210 Commits

Author SHA1 Message Date
gitea-actions[bot] 1a4c02a098 chore(release): build 02.15.00 [skip ci] 2026-05-28 18:14:13 +00:00
jmiller 99f3bd47e0 Merge pull request 'fix: package install path - add folder=packages to manifest' (#73) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
2026-05-28 18:13:59 +00:00
gitea-actions[bot] 6907046dae chore(version): patch bump to 02.14.01 [skip ci]
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 17s
2026-05-28 18:13:30 +00:00
Jonathan Miller 7e597674ac Merge remote-tracking branch 'origin/main' into dev
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Joomla: Repo Health / Release configuration (pull_request) Blocked by required conditions
Joomla: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Joomla: Repo Health / Repository health (pull_request) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Joomla: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Successful in 6s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 7s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: PR Check / Changelog Updated (pull_request) Failing after 6s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Update Server / Update Server (push) Failing after 10s
# Conflicts:
#	README.md
#	src/packages/com_mokowaas/mokowaas.xml
#	src/packages/plg_system_mokowaas/mokowaas.xml
#	src/packages/plg_webservices_mokowaas/mokowaas.xml
#	src/packages/plg_webservices_perfectpublisher/perfectpublisher.xml
#	src/pkg_mokowaas.xml
2026-05-28 13:13:20 -05:00
gitea-actions[bot] 47aeb98201 chore(version): patch bump to 02.13.04 [skip ci] 2026-05-28 18:10:20 +00:00
Jonathan Miller 58f2571dc4 fix: add folder=\"packages\" to pkg_mokowaas.xml files element
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Update Server / Update Server (push) Failing after 10s
The release_package.php builds sub-package ZIPs into a packages/
subdirectory, but the manifest referenced them at root level. Joomla's
package installer needs the folder attribute to find the ZIPs.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-28 13:10:09 -05:00
gitea-actions[bot] e3ba98499e chore(release): build 02.14.00 [skip ci] 2026-05-28 17:41:26 +00:00
jmiller 5b17f5c5ec Merge pull request 'chore: cascade main → dev (9b9e876) [skip ci]' (#71) from main into dev
chore: cascade main → dev [skip ci]
2026-05-28 17:41:16 +00:00
jmiller 9b9e8764da Merge pull request 'fix: push release commit to main instead of detached HEAD' (#70) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
2026-05-28 17:41:11 +00:00
gitea-actions[bot] 26646eac57 chore(version): patch bump to 02.13.03 [skip ci]
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 19s
2026-05-28 17:38:42 +00:00
Jonathan Miller e0518c20fe fix: push release commit to refs/heads/main instead of detached HEAD
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Root cause: Gitea Actions checks out the merge commit SHA for PR events,
putting git in detached HEAD state. "git push origin HEAD" then creates
a dangling ref instead of updating main.

Fix: use "git push origin HEAD:refs/heads/main" to explicitly target
the main branch.

Also: fail the workflow if .mokogitea/manifest.xml is missing.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-28 12:38:35 -05:00
jmiller 278e5d45f6 Merge pull request 'chore: cascade main → dev (0d24862) [skip ci]' (#69) from main into dev
chore: cascade main → dev [skip ci]
2026-05-28 17:30:08 +00:00
jmiller 0d24862302 Merge pull request 'feat: add Perfect Publisher web services API plugin' (#68) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
2026-05-28 17:30:01 +00:00
gitea-actions[bot] 94d45169ef chore(version): patch bump to 02.13.02 [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 15s
2026-05-28 17:24:51 +00:00
Jonathan Miller 17fd3d6b0e feat: add Perfect Publisher web services API plugin
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Update Server / Update Server (push) Failing after 14s
New plg_webservices_perfectpublisher provides REST API for Perfect
Publisher (com_autotweet):

- GET  /v1/perfectpublisher/channels      List social channels
- GET  /v1/perfectpublisher/channels/:id   Channel detail (OAuth redacted)
- GET  /v1/perfectpublisher/posts          List posts (filter by status/channel)
- GET  /v1/perfectpublisher/posts/:id      Post detail
- GET  /v1/perfectpublisher/requests       Pending publish requests
- POST /v1/perfectpublisher/requests       Create publish request
- GET  /v1/perfectpublisher/rules          Publishing rules
- GET  /v1/perfectpublisher/feeds          RSS feeds
- GET  /v1/perfectpublisher/channeltypes   Channel type definitions
- GET  /v1/perfectpublisher/stats          Dashboard statistics

Added to pkg_mokowaas.xml package manifest.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-28 12:24:33 -05:00
jmiller f26595bed4 Merge pull request 'chore: cascade main → dev (7074893) [skip ci]' (#67) from main into dev
chore: cascade main → dev [skip ci]
2026-05-28 16:15:58 +00:00
jmiller 70748938d2 Merge pull request 'fix: second version_set_platform pass before release commit' (#66) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 2s
2026-05-28 16:15:52 +00:00
gitea-actions[bot] dcdc3debb8 chore(version): patch bump to 02.13.01 [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 18s
2026-05-28 16:15:10 +00:00
Jonathan Miller e2782b4fb7 Merge remote-tracking branch 'origin/main' into dev
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Joomla: Repo Health / Release configuration (pull_request) Blocked by required conditions
Joomla: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Joomla: Repo Health / Repository health (pull_request) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Joomla: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Successful in 6s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 8s
Universal: PR Check / Changelog Updated (pull_request) Failing after 7s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Update Server / Update Server (push) Failing after 11s
# Conflicts:
#	README.md
#	src/packages/com_mokowaas/mokowaas.xml
#	src/packages/plg_system_mokowaas/mokowaas.xml
#	src/packages/plg_webservices_mokowaas/mokowaas.xml
#	src/pkg_mokowaas.xml
2026-05-28 11:14:57 -05:00
gitea-actions[bot] 178ca0499e chore(version): patch bump to 02.12.02 [skip ci] 2026-05-28 16:12:17 +00:00
Jonathan Miller 324baff9b9 fix: add second version_set_platform pass before release commit
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Run version_set_platform.php and version_check.php --fix a second time
right before the commit to catch any manifest files that got stale
during the build/release steps. Also add debug output to diagnose
commit step issues.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-28 11:12:06 -05:00
jmiller 22f0bb9a6f fix: updates.xml all channels at 02.13.00 with SHA [skip ci] 2026-05-28 16:10:17 +00:00
jmiller 616e82ae26 fix: updates.xml to 02.13.00 with SHA [skip ci] 2026-05-28 16:09:54 +00:00
jmiller ec5a22b37f fix: align README.md to 02.13.00 release [skip ci] 2026-05-28 16:09:51 +00:00
jmiller 445f5e7060 fix: align src/packages/plg_webservices_mokowaas/mokowaas.xml to 02.13.00 release [skip ci] 2026-05-28 16:09:48 +00:00
jmiller eaf46e7ea3 fix: align src/packages/com_mokowaas/mokowaas.xml to 02.13.00 release [skip ci] 2026-05-28 16:09:46 +00:00
jmiller 303af17971 fix: align src/packages/plg_system_mokowaas/mokowaas.xml to 02.13.00 release [skip ci] 2026-05-28 16:09:42 +00:00
jmiller 7e0aa36ffa fix: align src/pkg_mokowaas.xml to 02.13.00 release [skip ci] 2026-05-28 16:09:40 +00:00
jmiller 102bea980b Merge pull request 'chore: cascade main → dev (ed95dcb) [skip ci]' (#65) from main into dev
chore: cascade main → dev [skip ci]
2026-05-28 16:01:36 +00:00
jmiller ed95dcb7af Merge pull request 'fix: rewrite release workflows and fix version propagation' (#64) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
2026-05-28 16:01:29 +00:00
gitea-actions[bot] 56abe3af7f chore(version): patch bump to 02.12.01 [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 18s
2026-05-28 16:01:25 +00:00
Jonathan Miller 5b5245c170 Merge remote-tracking branch 'origin/main' into dev
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Joomla: Repo Health / Release configuration (pull_request) Blocked by required conditions
Joomla: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Joomla: Repo Health / Repository health (pull_request) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Repo Health / Access control (pull_request) Successful in 3s
Joomla: Extension CI / Release Readiness Check (pull_request) Successful in 7s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 9s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: PR Check / Changelog Updated (pull_request) Failing after 7s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Update Server / Update Server (push) Failing after 12s
# Conflicts:
#	README.md
#	src/packages/com_mokowaas/mokowaas.xml
#	src/packages/plg_system_mokowaas/mokowaas.xml
#	src/packages/plg_webservices_mokowaas/mokowaas.xml
#	src/pkg_mokowaas.xml
2026-05-28 11:01:13 -05:00
gitea-actions[bot] 167a7c0dfd chore(version): patch bump to 02.12.01 [skip ci] 2026-05-28 15:59:48 +00:00
Jonathan Miller 62788853ea fix: rewrite release workflows to use CLI and fix version propagation
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
auto-release.yml:
- Defer commit until AFTER updates.xml is written so version bump +
  manifests + updates.xml all go in one atomic push
- Add final version_check.php --fix before commit to catch missed files
- Remove || true from critical git push

update-server.yml:
- Replace ~400 lines of inline bash/python with CLI tool calls
- Use updates_xml_build.php for XML generation (handles name prefix,
  SHA cascade, creationDate, all 5 channels)
- Use release_create.php + release_package.php for Gitea releases
- Keep SFTP deploy and main sync as-is

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-28 10:59:38 -05:00
jmiller 6f7cb11e39 fix: align README version to 02.12.00 release [skip ci] 2026-05-28 15:49:37 +00:00
jmiller df22d7f7c0 fix: align manifest version to 02.12.00 release [skip ci] 2026-05-28 15:49:24 +00:00
jmiller 5984529569 fix: align manifest version to 02.12.00 release [skip ci] 2026-05-28 15:49:21 +00:00
jmiller 8be05b75b7 fix: align manifest version to 02.12.00 release [skip ci] 2026-05-28 15:49:18 +00:00
jmiller a02e466456 fix: align manifest version to 02.12.00 release [skip ci] 2026-05-28 15:49:15 +00:00
jmiller 2ede62b8b9 fix: updates.xml with all 5 channels at 02.12.00 [skip ci] 2026-05-28 15:44:09 +00:00
jmiller f5d06e6e25 fix: updates.xml with all 5 channels at 02.12.00 [skip ci] 2026-05-28 15:43:54 +00:00
jmiller 7370757e46 Merge pull request 'chore: cascade main → dev (3aa7364) [skip ci]' (#63) from main into dev
chore: cascade main → dev [skip ci]
2026-05-28 15:31:49 +00:00
jmiller 3aa7364783 Merge pull request 'fix: clean up updates.xml and remove duplicate update server' (#62) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
fix: clean up updates.xml, remove duplicate update server, auto-cleanup stale plugin update sites
2026-05-28 15:31:44 +00:00
gitea-actions[bot] df711f9a17 chore(version): patch bump to 02.11.04 [skip ci]
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 13s
2026-05-28 15:30:15 +00:00
Jonathan Miller 1d2252e8b4 fix: auto-cleanup stale plugin update site on install/update
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Joomla: Repo Health / Release configuration (pull_request) Blocked by required conditions
Joomla: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Joomla: Repo Health / Repository health (pull_request) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Successful in 4s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Changelog Updated (pull_request) Successful in 6s
Update Server / Update updates.xml (push) Failing after 8s
Add cleanupPluginUpdateSite() to script.php postflight that removes
the plugin-level update site entry from #__update_sites and
#__update_sites_extensions. This prevents Joomla from checking for
plugin-level updates that don't exist, which caused download failures
on stable-only sites.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-28 10:30:08 -05:00
gitea-actions[bot] c19d4da411 chore(version): patch bump to 02.11.03 [skip ci] 2026-05-28 15:24:46 +00:00
Jonathan Miller 45e9091fd0 fix: clean up updates.xml and remove duplicate plugin update server
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 4s
Update Server / Update updates.xml (push) Failing after 8s
- Remove stale pre-release entries pointing to non-existent dev artifacts
- Remove legacy plugin update entry (element=mokowaas, type=plugin) that
  caused stable-only sites to attempt dev downloads
- Remove <updateservers> from inner plugin manifest — only the package-level
  manifest (pkg_mokowaas.xml) should register the update server
- Keep only the stable channel entry (only release that exists on server)

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-28 10:24:12 -05:00
jmiller fb0ca184b9 docs: add infrastructure changes to CHANGELOG [skip ci] 2026-05-27 05:46:02 +00:00
jmiller c1e668e644 docs: update CHANGELOG with infrastructure changes [skip ci] 2026-05-27 05:28:33 +00:00
jmiller 985034650b chore: add branch-cleanup workflow [skip ci] 2026-05-27 03:52:36 +00:00
gitea-actions[bot] 0fdc91d50c feat(ci): add version branch creation on stable release [skip ci] 2026-05-27 02:19:27 +00:00
jmiller 9a7d5b8359 Merge pull request 'chore: cascade main → dev (9c9a1a7) [skip ci]' (#61) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 23:47:22 +00:00
jmiller 9c9a1a7b52 Merge pull request 'fix: trusted IP session bypass + CI workflow syncs' (#60) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
2026-05-26 23:47:18 +00:00
gitea-actions[bot] 21de2fa115 chore(version): patch bump to 02.11.02 [skip ci]
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 13s
2026-05-26 23:46:20 +00:00
Jonathan Miller 9f1848d218 fix: move trusted IP session bypass to boot() for early execution
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 4s
Update Server / Update updates.xml (push) Failing after 8s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Joomla validates sessions during initialise(), before onAfterInitialise
fires. The previous ini_set approach ran too late — the session was
already expired. Now implements BootableExtensionInterface so the
session lifetime is extended before Joomla's session handler runs.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 18:43:13 -05:00
jmiller ecb456d91e chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:50:50 +00:00
jmiller d9ce74cf38 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:50:50 +00:00
jmiller 91e9465233 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:49:38 +00:00
jmiller 3bbaee7c86 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:49:37 +00:00
jmiller d494e7366e chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:48:25 +00:00
jmiller 05c3f5fd1f chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:48:24 +00:00
jmiller c91b44ad34 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:36:53 +00:00
jmiller e86cc2b48b chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:36:53 +00:00
jmiller c28c2de936 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:35:32 +00:00
jmiller 1a81267d38 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:35:31 +00:00
jmiller 343ef64ea2 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:25:08 +00:00
jmiller f47a4d3c77 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:25:08 +00:00
jmiller cfb05c5964 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:23:51 +00:00
jmiller ebbd1058f3 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:23:51 +00:00
jmiller 9e356fa4b5 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:13:16 +00:00
jmiller e030d85886 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:13:15 +00:00
jmiller ea9ac21d1a chore(ci): add auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:12:03 +00:00
jmiller e256acbcbb chore(ci): add auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:12:03 +00:00
jmiller b4d11df2a2 Merge pull request 'chore: cascade main → dev (2c0ed08) [skip ci]' (#59) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 21:44:02 +00:00
jmiller 2c0ed08368 Merge pull request 'feat: show current IP in security tab' (#58) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Joomla: Repo Health / Access control (push) Successful in 2s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
2026-05-26 21:43:59 +00:00
jmiller 12fe6c196d chore: sync updates.xml from [skip ci] 2026-05-26 21:42:28 +00:00
gitea-actions[bot] 0415972c7d chore: update updates.xml (development: 02.11.01-dev) [skip ci]
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 12s
2026-05-26 21:42:27 +00:00
gitea-actions[bot] 6c7bb35ac3 chore(version): auto-bump patch 02.11.01 [skip ci] 2026-05-26 21:42:26 +00:00
Jonathan Miller 834b1325b5 Merge remote-tracking branch 'origin/main' into dev
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Joomla: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Joomla: Extension CI / Release Readiness Check (pull_request) Successful in 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Changelog Updated (pull_request) Successful in 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Update Server / Update updates.xml (push) Failing after 11s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Joomla: Repo Health / Release configuration (pull_request) Has been cancelled
Joomla: Repo Health / Scripts governance (pull_request) Has been cancelled
Joomla: Repo Health / Repository health (pull_request) Has been cancelled
# Conflicts:
#	.mokogitea/manifest.xml
#	.mokogitea/workflows/update-server.yml
#	README.md
#	src/packages/com_mokowaas/mokowaas.xml
#	src/packages/plg_system_mokowaas/mokowaas.xml
#	src/packages/plg_webservices_mokowaas/mokowaas.xml
#	src/pkg_mokowaas.xml
2026-05-26 16:42:01 -05:00
jmiller 4a1b2ea143 chore: sync updates.xml from [skip ci] 2026-05-26 21:30:16 +00:00
gitea-actions[bot] a748ee863c chore: update updates.xml (development: 02.10.06-dev) [skip ci] 2026-05-26 21:30:15 +00:00
gitea-actions[bot] 0546e1eaae chore(version): auto-bump patch 02.10.06 [skip ci] 2026-05-26 21:30:15 +00:00
Jonathan Miller 4595db209e feat: show current IP above trusted IPs table
Joomla: Repo Health / Access control (push) Successful in 2s
Update Server / Update updates.xml (push) Failing after 7s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Display the admin's current IP address with a hint to add it,
making it easy to configure trusted IP entries without looking
up the IP separately.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 16:29:56 -05:00
gitea-actions[bot] 3f3ff49573 chore(release): build 02.11.00 [skip ci] 2026-05-26 20:16:41 +00:00
jmiller 14318c90c2 chore: sync .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-26 20:12:32 +00:00
gitea-actions[bot] 3d79fe9aeb chore(release): build 02.10.05 [skip ci] 2026-05-26 20:12:26 +00:00
jmiller d2e24741af chore: sync .mokogitea/workflows/pre-release.yml from moko-platform [skip ci] 2026-05-26 20:10:36 +00:00
jmiller cb6582ef16 fix(ci): use release_package.php for Joomla package builds [skip ci] 2026-05-26 19:54:40 +00:00
jmiller d0c3a563d1 fix(ci): use release_package.php for Joomla package builds [skip ci] 2026-05-26 19:54:40 +00:00
jmiller 70b5c8de08 Merge pull request 'chore: cascade main → dev (a2eaf54) [skip ci]' (#56) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 19:44:26 +00:00
jmiller a2eaf549af Merge pull request 'feat: trusted IPs bypass admin session timeout' (#55) from dev into main
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Merge pull request #55: feat: trusted IPs bypass admin session timeout
2026-05-26 19:44:22 +00:00
gitea-actions[bot] c97c29f9ed chore: update updates.xml (development: 02.10.05-dev) [skip ci]
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 9s
2026-05-26 19:40:54 +00:00
jmiller ea48f61f8c chore: sync updates.xml from [skip ci] 2026-05-26 19:40:54 +00:00
gitea-actions[bot] d92df704c4 chore(version): auto-bump patch 02.10.05 [skip ci] 2026-05-26 19:40:52 +00:00
Jonathan Miller ad4c658b3d feat: trusted IPs bypass admin session timeout
Joomla: Repo Health / Access control (push) Successful in 1s
Update Server / Update updates.xml (push) Failing after 11s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Add configurable repeatable rows of trusted IP addresses that bypass
the admin session timeout. Supports exact IPs, CIDR ranges, and
wildcard patterns with per-entry labels and enabled toggles.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 14:40:32 -05:00
jmiller 0788e8e2ab Merge pull request 'chore: cascade main → dev (a68e90d) [skip ci]' (#54) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 19:34:05 +00:00
Jonathan Miller a68e90df9d Merge dev: fix(joomla6) webservices plugin + updated workflows
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
2026-05-26 14:34:00 -05:00
jmiller bacc0eba19 chore: sync .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-26 19:03:56 +00:00
gitea-actions[bot] c8f4e38f6b chore(release): build 02.10.03 [skip ci] 2026-05-26 18:58:54 +00:00
jmiller 0dcb8a4a1d chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 18:52:46 +00:00
jmiller fa31455619 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 18:52:45 +00:00
jmiller bf4dfac2a0 chore(ci): update pre-release.yml — remove paths filter [skip ci] 2026-05-26 18:51:33 +00:00
jmiller d3ceea0e80 chore(ci): update auto-release.yml — remove paths filter [skip ci] 2026-05-26 18:51:32 +00:00
jmiller 49a7418830 chore: sync updates.xml from [skip ci] 2026-05-26 18:42:43 +00:00
gitea-actions[bot] 1b9fc4e0f8 chore: update updates.xml (development: 02.10.04-dev) [skip ci] 2026-05-26 18:42:43 +00:00
gitea-actions[bot] 426853aef7 chore(version): auto-bump patch 02.10.04 [skip ci] 2026-05-26 18:42:41 +00:00
Jonathan Miller 3f20ad985c fix(joomla6): update webservices plugin for Joomla 6 typed event API
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Failing after 9s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Joomla 6 changed SubscriberInterface event handlers to receive a typed
event object instead of pass-by-reference parameters. Update
onBeforeApiRoute() to accept BeforeApiRouteEvent and extract the
router via $event->getRouter().

Fixes #48

Authored-by: Moko Consulting
2026-05-26 13:42:32 -05:00
jmiller ffa50f6460 fix: updates.xml all channels 02.11.00 [skip ci] 2026-05-26 17:41:23 +00:00
jmiller 08e2f171eb chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 17:36:27 +00:00
jmiller 9d49968272 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 17:35:16 +00:00
jmiller be98c55e46 Merge pull request 'chore: cascade main → dev (c6c9b21) [skip ci]' (#47) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 17:27:08 +00:00
jmiller c6c9b217a1 Merge pull request 'Release 02.11.00: Help menu redirect, support URL fix, CI tag fixes' (#46) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
2026-05-26 17:27:03 +00:00
jmiller 657928a01a fix: stable download URL [skip ci]
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 12s
2026-05-26 12:12:33 -05:00
jmiller 91ad0353a6 fix: updates.xml version 02.09.00 [skip ci] 2026-05-26 12:12:33 -05:00
jmiller 431c907391 chore: sync updates.xml from [skip ci] 2026-05-26 17:11:07 +00:00
gitea-actions[bot] 38d5a8eb90 chore: update updates.xml (development: 02.10.03-dev) [skip ci] 2026-05-26 17:11:07 +00:00
gitea-actions[bot] 19ab206f56 chore(version): auto-bump patch 02.10.03 [skip ci] 2026-05-26 17:11:06 +00:00
Jonathan Miller 642aca10fe fix: default support URL to mokoconsulting.tech/support
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Failing after 9s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Updated manifest default, PHP fallbacks in MokoWaaS.php and script.php.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 12:10:57 -05:00
jmiller a5b6d7a42a chore: sync updates.xml from [skip ci] 2026-05-26 17:01:34 +00:00
gitea-actions[bot] 9003570c5a chore: update updates.xml (development: 02.10.02-dev) [skip ci] 2026-05-26 17:01:33 +00:00
gitea-actions[bot] c241463bb1 chore(version): auto-bump patch 02.10.02 [skip ci] 2026-05-26 17:01:33 +00:00
Jonathan Miller 317c4e900a feat: redirect admin Help menu to configured support URL
Joomla: Repo Health / Access control (push) Successful in 2s
Joomla: Update Server / Update updates.xml (push) Failing after 9s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Replaces hardcoded help.joomla.org and docs.joomla.org links in
the Atum template header with the WaaS support URL from plugin
config. Uses JS injection in onBeforeCompileHead.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 12:01:21 -05:00
jmiller e948074c6a chore: sync updates.xml from [skip ci] 2026-05-26 16:12:53 +00:00
gitea-actions[bot] 848f07429c chore: update updates.xml (development: 02.10.01-dev) [skip ci] 2026-05-26 16:12:53 +00:00
gitea-actions[bot] 203327f5ed chore(version): auto-bump patch 02.10.01 [skip ci] 2026-05-26 16:12:52 +00:00
Jonathan Miller 92261be464 chore: bump dev to 02.10.00 (ahead of main 02.09.00)
Joomla: Repo Health / Access control (push) Successful in 2s
Joomla: Update Server / Update updates.xml (push) Failing after 9s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 11:12:40 -05:00
gitea-actions[bot] 28e61b8f8a chore(version): pre-release bump to 02.08.04 [skip ci] 2026-05-26 07:06:54 +00:00
jmiller 188db2d4b8 sync: update-server.yml with updates.xml integrity check [skip ci] 2026-05-26 04:47:46 +00:00
jmiller 1f0b4596ff sync: update-server.yml with updates.xml integrity check [skip ci] 2026-05-26 04:47:46 +00:00
jmiller 1ed11dca03 fix: stable download URL [skip ci] 2026-05-26 04:40:33 +00:00
jmiller ecc5d624d5 fix: updates.xml version 02.09.00 [skip ci] 2026-05-26 04:40:04 +00:00
jmiller dac39212d7 Merge pull request 'chore: cascade main → dev (43abc65) [skip ci]' (#45) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 04:37:41 +00:00
jmiller 43abc6514e Merge pull request 'Release 02.09.00: CI fixes, update server standard, Joomla skill' (#44) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
2026-05-26 04:37:36 +00:00
Jonathan Miller 8d42ef40c5 Merge remote-tracking branch 'origin/main' into dev
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Joomla: Repo Health / Access control (pull_request) Successful in 1s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 3s
Joomla: Extension CI / Release Readiness Check (pull_request) Successful in 4s
Universal: PR Check / Changelog Updated (pull_request) Successful in 3s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 11s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been cancelled
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been cancelled
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been cancelled
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Joomla: Repo Health / Release configuration (pull_request) Has been cancelled
Joomla: Repo Health / Scripts governance (pull_request) Has been cancelled
Joomla: Repo Health / Repository health (pull_request) Has been cancelled
# Conflicts:
#	.mokogitea/workflows/auto-release.yml
#	.mokogitea/workflows/pre-release.yml
#	README.md
#	src/packages/com_mokowaas/mokowaas.xml
#	src/packages/plg_system_mokowaas/mokowaas.xml
#	src/packages/plg_webservices_mokowaas/mokowaas.xml
#	src/pkg_mokowaas.xml
#	updates.xml
2026-05-25 23:37:11 -05:00
gitea-actions[bot] 0546dde89f chore(version): pre-release bump to 02.08.03 [skip ci] 2026-05-26 04:00:46 +00:00
jmiller 598ec0712c fix: updates.xml tag dev + client site [skip ci] 2026-05-26 03:52:18 +00:00
Jonathan Miller 6f9df77f79 fix(ci): remove dead XML_TAG code, CLI handles tag mapping
Joomla: Repo Health / Access control (push) Successful in 3s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
The updates_xml_build.php CLI now maps 'development' → 'dev' internally.
Removed the dead shell case block that was setting XML_TAG (unused since
the inline PHP updater was replaced with the CLI).

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 22:51:19 -05:00
jmiller 89aaef14e7 fix: updates.xml same version all channels + client tag [skip ci] 2026-05-26 03:33:21 +00:00
gitea-actions[bot] f72cafe4d7 chore(version): pre-release bump to 02.08.02 [skip ci] 2026-05-26 03:26:36 +00:00
Jonathan Miller a965bcf0ef refactor(ci): clean up auto-release, move logic to CLI tools
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
- Remove duplicate asset deletion loop (new loop handles all assets)
- Remove inline Python updates.xml updater (Step 5 CLI handles it)
- Remove dead Step 8 sync-to-main code (Step 5 commit+push handles it)
- Step 8b uses release_body_update.php CLI with fallback
- Step 6 tag creation always runs (removed never-set is_minor gate)
- Net: -154 lines, +25 lines

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 22:20:28 -05:00
Jonathan Miller bd6eec88af feat(ci): checksums as [filename].sha256 assets, not in release body
Joomla: Repo Health / Access control (push) Successful in 4s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Upload per-file .sha256 checksum files alongside packages instead of
embedding SHA-256 in the release description. Format: "hash  filename"
matching standard sha256sum output.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 22:11:53 -05:00
Jonathan Miller ce7e36f779 feat(ci): add version_check.php to pre-release and auto-release workflows
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Runs --fix mode after version bump to auto-correct any drift between
README.md and manifest XML files before building.

Refs: moko-platform#122

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 22:08:40 -05:00
jmiller 46b1469121 chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-05-26 03:07:18 +00:00
jmiller 1e936a67c4 chore: sync .mokogitea/workflows/pre-release.yml from moko-platform [skip ci] 2026-05-26 03:05:25 +00:00
jmiller 0903a4b335 fix: sync updates.xml with all channels and correct versions [skip ci] 2026-05-26 03:05:10 +00:00
gitea-actions[bot] a7823c6440 chore: update updates.xml (development: 02.08.01-dev) [skip ci] 2026-05-26 03:04:40 +00:00
jmiller ed720b2ea9 chore: sync updates.xml from [skip ci] 2026-05-26 03:04:40 +00:00
gitea-actions[bot] 263ac78515 chore(version): auto-bump patch 02.08.01 [skip ci] 2026-05-26 03:04:39 +00:00
Jonathan Miller b9f83c43bc chore: reset versions to 02.08.00, sync all update streams
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Successful in 9s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Dev is now ahead of main (02.07.00). All manifests, README, and
updates.xml aligned. Updates.xml has all 6 channels with correct
pkg_mokowaas element.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 22:04:25 -05:00
Jonathan Miller f4609088e3 fix(ci): auto-release preserves all update channels [skip ci]
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 21:56:10 -05:00
Jonathan Miller d9326ea34b fix(ci): auto-release fetches updates.xml from main before building
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Failing after 8s
Joomla: Repo Health / Repository health (push) Failing after 9s
Joomla: Repo Health / Scripts governance (push) Successful in 12s
Preserves all existing channel entries (dev, alpha, beta, rc) when
adding/updating the stable entry. Previously the file on disk was
empty or stale, so the preserve logic had nothing to keep.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 21:54:42 -05:00
jmiller 6589adcf75 fix: restore all channel entries in updates.xml [skip ci] 2026-05-26 02:51:58 +00:00
jmiller 2e2c1b82b3 chore: sync updates.xml 02.07.00 [skip ci] 2026-05-26 02:50:57 +00:00
gitea-actions[bot] 0451fa2138 chore(version): pre-release bump to 02.06.04 [skip ci] 2026-05-26 02:50:52 +00:00
jmiller 66b90754f8 Merge pull request 'chore: cascade main → dev (e66b7e9) [skip ci]' (#43) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 02:50:37 +00:00
jmiller e66b7e9a79 Merge pull request 'Release 02.07.00: Update site fix, settings protection, master god privs' (#42) from dev into main
Joomla: Repo Health / Access control (push) Successful in 3s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 6s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
2026-05-26 02:49:08 +00:00
Jonathan Miller 4f056763e9 Merge remote-tracking branch 'origin/main' into dev
Joomla: Repo Health / Access control (push) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 6s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been skipped
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been skipped
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 9s
Joomla: Repo Health / Access control (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 15s
Universal: PR Check / Changelog Updated (pull_request) Successful in 10s
Universal: PR Check / Build RC Package (pull_request) Has been skipped
Joomla: Update Server / Update updates.xml (push) Failing after 25s
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been skipped
Joomla: Repo Health / Release configuration (push) Failing after 6s
Joomla: Repo Health / Scripts governance (push) Successful in 5s
Joomla: Repo Health / Repository health (push) Failing after 5s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 24s
Joomla: Repo Health / Release configuration (pull_request) Has been cancelled
Joomla: Repo Health / Scripts governance (pull_request) Has been cancelled
Joomla: Repo Health / Repository health (pull_request) Has been cancelled
# Conflicts:
#	README.md
#	updates.xml
2026-05-25 21:48:49 -05:00
jmiller de70224728 fix: updates.xml all entries use pkg_mokowaas element [skip ci] 2026-05-26 02:36:40 +00:00
gitea-actions[bot] 6f69af666f chore: update updates.xml (development: 02.05.05-dev) [skip ci] 2026-05-26 02:34:05 +00:00
jmiller 1f7278022c chore: sync updates.xml from [skip ci] 2026-05-26 02:34:05 +00:00
gitea-actions[bot] b5e8d3dfe2 chore(version): auto-bump patch 02.05.05 [skip ci] 2026-05-26 02:34:04 +00:00
Jonathan Miller 3edec0687c fix: re-enable update site disabled by Joomla's protected flag
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Successful in 8s
Joomla: Repo Health / Release configuration (push) Failing after 8s
Joomla: Repo Health / Scripts governance (push) Successful in 8s
Joomla: Repo Health / Repository health (push) Failing after 6s
Joomla automatically disables update sites for extensions with
protected=1. ensureProtectedFlag() now also checks and re-enables
the MokoWaaS update site each admin session.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 21:33:53 -05:00
jmiller a503e12ef9 fix: updates.xml with all channels (stable, rc, beta, alpha, dev, legacy) [skip ci] 2026-05-26 02:20:26 +00:00
Jonathan Miller ea60ac60ba fix(ci): pre-release uses updates_xml_build CLI with preserve logic
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
The inline PHP regex approach only updated existing entries — if no
matching channel entry existed, nothing was added. Now uses the
moko-platform CLI which creates new entries and preserves other channels.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 21:18:22 -05:00
jmiller 825820f7b9 fix: add RC and legacy entries to updates.xml [skip ci] 2026-05-26 02:17:42 +00:00
gitea-actions[bot] ba4a806cd7 chore(version): pre-release bump to 02.06.03 [skip ci] 2026-05-26 02:09:25 +00:00
Jonathan Miller effd1fd588 fix(ci): use absolute paths in package build step [skip ci]
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 21:09:09 -05:00
Jonathan Miller bf2b01df2d fix(ci): use absolute paths in package build step
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Relative path ../../build/package/ broke when cd'd 3 levels deep into
src/packages/ext_name/. Now captures REPO_ROOT=$(pwd) and uses it
for zip output and cd back.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 21:08:41 -05:00
gitea-actions[bot] 4581088a0a chore(version): pre-release bump to 02.06.02 [skip ci] 2026-05-26 02:05:10 +00:00
gitea-actions[bot] 863dbb02f4 chore(version): pre-release bump to 02.06.01 [skip ci] 2026-05-26 02:01:47 +00:00
jmiller 8fd8015b19 chore: sync updates.xml 02.06.00 [skip ci] 2026-05-26 01:58:56 +00:00
jmiller 83ddbf0d73 Merge pull request 'chore: cascade main → dev (fad0170) [skip ci]' (#41) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 01:58:45 +00:00
jmiller fad0170cef Merge pull request 'Release 02.07.00-rc: Master god privs, settings protection, install API fix' (#40) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 6s
Joomla: Repo Health / Access control (push) Successful in 2s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
2026-05-26 01:58:39 +00:00
jmiller a734d381ac chore: sync updates.xml from [skip ci] 2026-05-26 01:55:08 +00:00
gitea-actions[bot] 0b8f492613 chore: update updates.xml (development: 02.05.04-dev) [skip ci]
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 18s
2026-05-26 01:55:07 +00:00
gitea-actions[bot] 11c3488438 chore(version): auto-bump patch 02.05.04 [skip ci] 2026-05-26 01:55:06 +00:00
Jonathan Miller cc709a0231 security: master user bypasses all tenant restrictions
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Successful in 8s
Joomla: Repo Health / Release configuration (push) Has been cancelled
Joomla: Repo Health / Scripts governance (push) Has been cancelled
Joomla: Repo Health / Repository health (push) Has been cancelled
Moved isMasterUser() check to top of enforceAdminRestrictions() so
master user is never blocked by any restriction including install
from URL, global config, sysinfo, installer, and template editing.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 20:54:58 -05:00
jmiller 0a0d998208 chore: sync updates.xml from [skip ci] 2026-05-26 01:38:26 +00:00
gitea-actions[bot] 03839601bb chore: update updates.xml (development: 02.05.03-dev) [skip ci] 2026-05-26 01:38:25 +00:00
gitea-actions[bot] 3e28dd4fae chore(version): auto-bump patch 02.05.03 [skip ci] 2026-05-26 01:38:24 +00:00
Jonathan Miller 2674111e0b security: block non-master users from editing MokoWaaS settings
Joomla: Repo Health / Access control (push) Successful in 2s
Joomla: Update Server / Update updates.xml (push) Successful in 8s
Joomla: Repo Health / Release configuration (push) Failing after 4s
Joomla: Repo Health / Scripts governance (push) Successful in 4s
Joomla: Repo Health / Repository health (push) Failing after 4s
Non-master users navigating to the plugin edit page are redirected
back to the plugins list with a warning message.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 20:38:14 -05:00
gitea-actions[bot] 7488225aa6 chore(version): pre-release bump to 02.05.02 [skip ci] 2026-05-26 01:30:19 +00:00
jmiller c1a9816c57 fix: updates.xml element=pkg_mokowaas + legacy plugin entry [skip ci] 2026-05-26 01:25:32 +00:00
jmiller 2d1932719a chore: sync updates.xml 02.06.00 [skip ci] 2026-05-26 01:12:15 +00:00
jmiller 315be81e20 Merge pull request 'chore: cascade main → dev (65d9aa3) [skip ci]' (#39) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 01:12:07 +00:00
jmiller 65d9aa3e9f Merge pull request 'Release 02.06.00: Alias offline bypass, install API fix, stream tags' (#38) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Failing after 3s
Joomla: Repo Health / Scripts governance (push) Successful in 4s
Joomla: Repo Health / Repository health (push) Failing after 4s
2026-05-26 01:12:02 +00:00
jmiller 8243e8c49d chore: sync updates.xml from [skip ci] 2026-05-26 01:11:51 +00:00
gitea-actions[bot] c9d31b3ba4 chore: update updates.xml (development: 02.05.01-dev) [skip ci]
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 13s
2026-05-26 01:11:50 +00:00
gitea-actions[bot] 29cfee7154 chore(version): auto-bump patch 02.05.01 [skip ci] 2026-05-26 01:11:49 +00:00
Jonathan Miller bbae842fdb Merge remote-tracking branch 'origin/main' into dev
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Joomla: Repo Health / Access control (pull_request) Successful in 1s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 5s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Has been skipped
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Has been skipped
Joomla: Extension CI / PHPStan Analysis (pull_request) Has been skipped
Universal: PR Check / Changelog Updated (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Universal: PR Check / Build RC Package (pull_request) Has been skipped
Joomla: Extension CI / Build RC Pre-Release (pull_request) Has been skipped
Joomla: Update Server / Update updates.xml (push) Successful in 10s
Joomla: Repo Health / Release configuration (push) Failing after 4s
Joomla: Repo Health / Scripts governance (push) Successful in 4s
Joomla: Repo Health / Repository health (push) Failing after 4s
Joomla: Repo Health / Release configuration (pull_request) Failing after 4s
Joomla: Repo Health / Scripts governance (pull_request) Successful in 4s
Joomla: Repo Health / Repository health (pull_request) Failing after 5s
# Conflicts:
#	README.md
2026-05-25 20:11:38 -05:00
jmiller 85e966a3f4 chore: sync updates.xml from [skip ci] 2026-05-26 01:04:00 +00:00
gitea-actions[bot] 3d8bfb6112 chore: update updates.xml (development: 02.04.02-dev) [skip ci] 2026-05-26 01:03:59 +00:00
gitea-actions[bot] 7822064045 chore(version): auto-bump patch 02.04.02 [skip ci] 2026-05-26 01:03:59 +00:00
Jonathan Miller 906861638f fix: install API extracts ZIP before passing to Joomla Installer
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Successful in 8s
Joomla: Repo Health / Release configuration (push) Failing after 5s
Joomla: Repo Health / Scripts governance (push) Successful in 5s
Joomla: Repo Health / Repository health (push) Failing after 6s
Installer::install() expects a directory path, not a ZIP file. Now
downloads → extracts to tmp dir → installs from extracted dir → cleans up.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 20:03:47 -05:00
Jonathan Miller 78dd453a9b chore: promote CHANGELOG for 02.06.00 release
Joomla: Repo Health / Access control (push) Successful in 3s
Joomla: Repo Health / Release configuration (push) Failing after 4s
Joomla: Repo Health / Scripts governance (push) Successful in 4s
Joomla: Repo Health / Repository health (push) Failing after 5s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 20:01:14 -05:00
jmiller 204520d9c9 chore: sync updates.xml from [skip ci] 2026-05-26 00:59:11 +00:00
gitea-actions[bot] 72b967c0ab chore: update updates.xml (development: 02.04.01-dev) [skip ci] 2026-05-26 00:59:10 +00:00
gitea-actions[bot] 781266885f chore(version): auto-bump patch 02.04.01 [skip ci] 2026-05-26 00:59:09 +00:00
Jonathan Miller a869619fcd feat: alias offline=No bypasses Joomla global offline setting
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Update Server / Update updates.xml (push) Successful in 9s
Joomla: Repo Health / Release configuration (push) Failing after 3s
Joomla: Repo Health / Scripts governance (push) Successful in 4s
Joomla: Repo Health / Repository health (push) Failing after 4s
When an alias domain has offline=No, the plugin overrides Joomla's
configuration.php offline=1 setting. This allows the dev/staging alias
to remain accessible while the main site shows the offline page.

Use case: put clarksvillefurs.com offline for maintenance while
clarksvillefurs.dev.mokoconsulting.tech stays accessible for testing.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 19:58:52 -05:00
Jonathan Miller 625965e129 fix(ci): use stream tag 'stable' instead of version tag 'vXX'
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Failing after 3s
Joomla: Repo Health / Scripts governance (push) Successful in 4s
Joomla: Repo Health / Repository health (push) Failing after 4s
Release tags should use update stream names (stable, development,
release-candidate) not version-based tags (v02, v03). This aligns
with the pre-release workflow which already uses stream tags.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 19:22:05 -05:00
jmiller 91504c663b fix: use stream tag 'stable' not version tag in updates.xml [skip ci] 2026-05-26 00:21:50 +00:00
jmiller 6cd690b737 feat(ci): add issue-branch.yml [skip ci] 2026-05-25 05:12:32 +00:00
Jonathan Miller 3b2fe37ce1 chore: update CHANGELOG for 02.05.00 stable release
Joomla: Repo Health / Access control (push) Successful in 1s
Joomla: Repo Health / Release configuration (push) Failing after 3s
Joomla: Repo Health / Scripts governance (push) Successful in 3s
Joomla: Repo Health / Repository health (push) Failing after 4s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-24 23:15:29 -05:00
jmiller 8fea27e8b6 fix(ci): update pre-release.yml - PHP CLI tools, fix broken platform detection [skip ci] 2026-05-25 04:12:59 +00:00
jmiller cbea5752d1 fix: correct updates.xml stable entries [skip ci] 2026-05-25 04:07:11 +00:00
jmiller 34e789298b chore: sync updates.xml 02.05.00 [skip ci] 2026-05-25 03:59:36 +00:00
gitea-actions[bot] 62c49eab5a chore(release): ZIP + tar.gz for 02.05.00 [skip ci] 2026-05-25 03:59:35 +00:00
gitea-actions[bot] 2f8c81792d chore(release): build 02.05.00 [skip ci] 2026-05-25 03:59:33 +00:00
jmiller 9a356cdd04 Merge pull request 'chore: cascade main → dev (7b5a83c) [skip ci]' (#37) from main into dev
chore: cascade main → dev [skip ci]
2026-05-25 03:59:26 +00:00
jmiller 69ff510bac Merge pull request 'chore: cascade main → dev (8c9e3e6) [skip ci]' (#36) from main into dev
chore: cascade main → dev [skip ci]
2026-05-24 23:23:45 +00:00
33 changed files with 3455 additions and 1580 deletions
+1
View File
@@ -8,6 +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.15.00</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+84
View File
@@ -0,0 +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.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
+48
View File
@@ -0,0 +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.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
+73
View File
@@ -0,0 +1,73 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
on:
issues:
types: [opened]
permissions:
contents: write
issues: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
create-branch:
name: Create feature branch
runs-on: ubuntu-latest
steps:
- name: Create branch and comment
run: |
TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}"
# Build slug from title: lowercase, replace non-alnum with dash, trim
SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
# Check dev branch exists
DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \
-H "Authorization: token ${TOKEN}" \
"${API}/branches/dev" 2>/dev/null || echo "000")
if [ "${DEV_EXISTS}" != "200" ]; then
echo "No dev branch -- skipping"
exit 0
fi
# Create branch from dev
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API}/branches" \
-d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000")
if [ "${HTTP}" = "201" ]; then
echo "Created branch: ${BRANCH}"
# Comment on issue with branch link
REPO_URL="${GITEA_URL}/${{ github.repository }}"
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
curl -sf -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API}/issues/${ISSUE_NUM}/comments" \
-d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
echo "Commented on issue #${ISSUE_NUM}"
else
echo "Failed to create branch (HTTP ${HTTP}) -- may already exist"
fi
+223 -260
View File
@@ -1,260 +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.00.00
# BRIEF: Manual pre-release builds dev/alpha/beta/rc packages from any branch
name: "Universal: Pre-Release"
on:
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 }})"
runs-on: release
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GA_TOKEN }}
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
fi
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
git clone --depth 1 --branch main --quiet "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" /tmp/moko-platform-api
- name: Detect platform
id: platform
run: |
php /tmp/moko-platform-api/cli/manifest_read.php --path . --github-output
- name: Resolve metadata
id: meta
run: |
STABILITY="${{ inputs.stability }}"
MOKO_API="/tmp/moko-platform-api/cli"
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 from manifest (priority) or README — no bump yet
VERSION=$(php ${MOKO_API}/version_read.php --path .)
echo "Version: ${VERSION}"
# Ensure platform-specific manifest matches
php ${MOKO_API}/version_set_platform.php --path . --version "${VERSION}"
# Git setup for later commits
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"
# Detect element from Joomla/Dolibarr manifest
set +o pipefail
PLATFORM="${{ steps.platform.outputs.platform }}"
EXT_ELEMENT=$(php ${MOKO_API}/manifest_read.php --path . --field name 2>/dev/null | tr -d ' ' | tr '[:upper:]' '[:lower:]' || true)
# For Joomla, prefer <element> tag
if [ "$PLATFORM" = "joomla" ]; then
MANIFEST=$(find . -maxdepth 4 -name "*.xml" ! -path "./.git/*" -print0 2>/dev/null | xargs -0 grep -l '<extension' 2>/dev/null | head -1 || true)
if [ -n "$MANIFEST" ]; then
ELEM=$(grep -oP "<element>\K[^<]+" "$MANIFEST" 2>/dev/null | head -1 || true)
[ -n "$ELEM" ] && EXT_ELEMENT="$ELEM"
fi
fi
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
set -o pipefail
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: Build package
id: zip
run: |
VERSION="${{ steps.meta.outputs.version }}"
SUFFIX="${{ steps.meta.outputs.suffix }}"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ "$PLATFORM" = "joomla" ]; then
php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --suffix "${SUFFIX}" --output build --github-output
else
# Generic build: zip src/ directory
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "::error::No src/ or htdocs/"; exit 1; }
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
mkdir -p build
cd "$SOURCE_DIR" && zip -r "../build/${ZIP_NAME}" . && cd ..
SHA256=$(sha256sum "build/${ZIP_NAME}" | cut -d' ' -f1)
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "zip_path=build/${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
fi
- name: Create or replace Gitea release
id: release
continue-on-error: true
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
ZIP_NAME="${{ steps.zip.outputs.zip_name }}"
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
BRANCH=$(git branch --show-current)
BODY="## ${VERSION} ($(date +%Y-%m-%d))
**Channel:** ${STABILITY}
**SHA-256:** \`${SHA256}\`"
# Delete existing release
EXISTING_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
"${API}/releases/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$EXISTING_ID" ]; then
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/releases/${EXISTING_ID}" 2>/dev/null || true
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/tags/${TAG}" 2>/dev/null || true
fi
# Create release
RELEASE_ID=$(curl -sS -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API}/releases" \
-d "$(jq -n \
--arg tag "$TAG" \
--arg target "$BRANCH" \
--arg name "${EXT_ELEMENT} ${VERSION} (${STABILITY})" \
--arg body "$BODY" \
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: true}'
)" | jq -r '.id')
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
# Upload ZIP
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
--data-binary "@${{ steps.zip.outputs.zip_path }}"
echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
- name: "Update updates.xml"
if: steps.platform.outputs.platform == 'joomla'
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
php /tmp/moko-platform-api/cli/updates_xml_build.php --path . --version "$VERSION" --stability "$STABILITY" --sha "$SHA256" --gitea-url "$GITEA_URL" --org "$GITEA_ORG" --repo "$GITEA_REPO"
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: |
php /tmp/moko-platform-api/cli/updates_xml_sync.php --path . --current "${{ github.ref_name }}" --branches main,dev --version "${{ steps.meta.outputs.version }}" --token "${{ secrets.GA_TOKEN }}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" --gitea-url "${GITEA_URL}"
- 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 }}"
STABILITY="${{ steps.meta.outputs.stability }}"
# Cascade: rc → beta,alpha,dev | beta → alpha,dev | alpha → dev | dev → nothing
case "$STABILITY" in
release-candidate) TAGS_TO_DELETE="beta alpha development" ;;
beta) TAGS_TO_DELETE="alpha development" ;;
alpha) TAGS_TO_DELETE="development" ;;
*) TAGS_TO_DELETE="" ;;
esac
[ -z "$TAGS_TO_DELETE" ] && exit 0
for TAG in $TAGS_TO_DELETE; do
RELEASE_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/${TAG}" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/${RELEASE_ID}" 2>/dev/null || true
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/tags/${TAG}" 2>/dev/null || true
echo "Deleted: ${TAG} (id: ${RELEASE_ID})"
fi
done
- name: "Post-release version bump"
run: |
MOKO_API="/tmp/moko-platform-api/cli"
VERSION="${{ steps.meta.outputs.version }}"
# Bump patch for next dev cycle
BUMP_OUTPUT=$(php ${MOKO_API}/version_bump.php --path .)
NEXT=$(echo "$BUMP_OUTPUT" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
[ -z "$NEXT" ] && exit 0
# Update platform-specific manifest to next version
php ${MOKO_API}/version_set_platform.php --path . --version "${NEXT}"
git add -A
git diff --cached --quiet || {
git commit -m "chore: update development channel ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# 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
+304 -464
View File
@@ -1,464 +1,304 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Joomla
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/joomla/update-server.yml.template
# VERSION: 04.06.00
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
#
# Writes updates.xml with multiple <update> entries:
# - <tag>stable</tag> on push to main (from auto-release)
# - <tag>rc</tag> on push to rc/**
# - <tag>development</tag> on push to dev or dev/**
#
# Joomla filters by user's "Minimum Stability" setting.
name: "Joomla: 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 updates.xml
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.GA_TOKEN }}
fetch-depth: 0
- name: Setup MokoStandards 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
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Generate updates.xml entry
id: update
run: |
BRANCH="${{ github.ref_name }}"
REPO="${{ github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
# Auto-bump patch on all branches (dev, alpha, beta, rc)
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true)
if [ -n "$BUMPED" ]; then
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
git add -A
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
git push 2>/dev/null || true
fi
# Determine stability from branch or 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"
elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then
STABILITY="development"
else
STABILITY="stable"
fi
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
# Parse manifest (portable — no grep -P)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "No Joomla manifest found — skipping"
exit 0
fi
# Extract fields using sed (works on all runners)
EXT_NAME=$(sed -n 's/.*<name>\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
EXT_CLIENT=$(sed -n 's/.*<extension[^>]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_VERSION=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
# Fallbacks
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
# Derive element if not in manifest: try XML filename, then repo name
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
case "$EXT_ELEMENT" in
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac
fi
# Use manifest version if README version is empty
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
CLIENT_TAG=""
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
[ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="<client>site</client>"
FOLDER_TAG=""
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
PHP_TAG=""
[ -n "$PHP_MINIMUM" ] && PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
# Version suffix for non-stable
DISPLAY_VERSION="$VERSION"
case "$STABILITY" in
development) DISPLAY_VERSION="${VERSION}-dev" ;;
alpha) DISPLAY_VERSION="${VERSION}-alpha" ;;
beta) DISPLAY_VERSION="${VERSION}-beta" ;;
rc) DISPLAY_VERSION="${VERSION}-rc" ;;
esac
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
# Each stability level has its own release tag
case "$STABILITY" in
development) RELEASE_TAG="development" ;;
alpha) RELEASE_TAG="alpha" ;;
beta) RELEASE_TAG="beta" ;;
rc) RELEASE_TAG="release-candidate" ;;
*) RELEASE_TAG="v${MAJOR}" ;;
esac
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}"
# -- Build install packages (ZIP + tar.gz) --------------------
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ -d "$SOURCE_DIR" ]; then
EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES
cd ..
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
--exclude='.ftpignore' --exclude='sftp-config*' \
--exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
# Ensure release exists on Gitea
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -z "$RELEASE_ID" ]; then
# Create release
RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases" \
-d "$(python3 -c "import json; print(json.dumps({
'tag_name': '${RELEASE_TAG}',
'name': '${RELEASE_TAG} (${DISPLAY_VERSION})',
'body': '${STABILITY} release',
'prerelease': True,
'target_commitish': 'main'
}))")" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
fi
if [ -n "$RELEASE_ID" ]; then
# Delete existing assets with same name before uploading
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do
ASSET_ID=$(echo "$ASSETS" | python3 -c "
import sys,json
assets = json.load(sys.stdin)
for a in assets:
if a['name'] == '${ASSET_FILE}':
print(a['id']); break
" 2>/dev/null || true)
if [ -n "$ASSET_ID" ]; then
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
fi
done
# Upload both formats
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${PACKAGE_NAME}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${TAR_NAME}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
fi
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
else
SHA256=""
fi
# -- Build the new entry (canonical format matching release.yml) --
NEW_ENTRY=""
NEW_ENTRY="${NEW_ENTRY} <update>\n"
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} ${STABILITY} build.</description>\n"
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
[ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n"
[ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n"
NEW_ENTRY="${NEW_ENTRY} <version>${VERSION}</version>\n"
NEW_ENTRY="${NEW_ENTRY} <creationDate>$(date +%Y-%m-%d)</creationDate>\n"
NEW_ENTRY="${NEW_ENTRY} <infourl title='${EXT_NAME}'>https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}</infourl>\n"
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
NEW_ENTRY="${NEW_ENTRY} <downloadurl type='full' format='zip'>${DOWNLOAD_URL}</downloadurl>\n"
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>${SHA256}</sha256>\n"
NEW_ENTRY="${NEW_ENTRY} <tags><tag>${STABILITY}</tag></tags>\n"
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
NEW_ENTRY="${NEW_ENTRY} <targetplatform name='joomla' version='(5|6).*'/>\n"
[ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} <php_minimum>${PHP_MINIMUM}</php_minimum>\n"
NEW_ENTRY="${NEW_ENTRY} </update>"
# -- Write new entry to temp file --------------------------------
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
# -- Merge into updates.xml ----------------------------------------
# Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev
CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development"
TARGETS=""
for entry in $CASCADE_MAP; do
key="${entry%%:*}"
vals="${entry#*:}"
if [ "$key" = "${STABILITY}" ]; then
TARGETS="$vals"
break
fi
done
[ -z "$TARGETS" ] && TARGETS="${STABILITY}"
echo "Cascade: ${STABILITY} → ${TARGETS}"
# Create updates.xml if missing
if [ ! -f "updates.xml" ]; then
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>" > updates.xml
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting -->" >> updates.xml
printf '%s\n' "<updates>" >> updates.xml
printf '%s\n' "</updates>" >> updates.xml
fi
# Update existing blocks or create missing ones
export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)"
python3 << 'PYEOF'
import re, os
targets = os.environ["PY_TARGETS"].split(",")
version = os.environ["PY_VERSION"]
date = os.environ["PY_DATE"]
with open("updates.xml") as f:
content = f.read()
with open("/tmp/new_entry.xml") as f:
new_entry_template = f.read()
for tag in targets:
tag = tag.strip()
# Build entry with this tag's name
new_entry = re.sub(r"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
# Try to find existing block (handles both single-line and multi-line <tags>)
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)"
match = re.search(block_pattern, content, re.DOTALL)
if match:
# Update in place — replace entire block
content = content.replace(match.group(1), new_entry.strip())
print(f" UPDATED: <tag>{tag}</tag> → {version}")
else:
# Create — insert before </updates>
content = content.replace("</updates>", "\n" + new_entry.strip() + "\n\n</updates>")
print(f" CREATED: <tag>{tag}</tag> → {version}")
# Clean up excessive blank lines
content = re.sub(r"\n{3,}", "\n\n", content)
with open("updates.xml", "w") as f:
f.write(content)
PYEOF
# Commit
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 updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push
}
# -- Sync updates.xml to main (for non-main branches) ----------------------
- name: Sync updates.xml to main
if: github.ref_name != 'main'
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
CONTENT=$(base64 -w0 updates.xml)
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/contents/updates.xml" \
-d "$(python3 -c "import json; print(json.dumps({
'content': '${CONTENT}',
'sha': '${FILE_SHA}',
'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
'branch': 'main'
}))")" > /dev/null 2>&1 \
&& echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
|| echo "WARNING: failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
else
echo "WARNING: could not get updates.xml SHA from main" >> $GITHUB_STEP_SUMMARY
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 }}"
REPO="${{ github.repository }}"
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 /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
php /tmp/mokostandards-api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
elif [ -f "/tmp/mokostandards-api/deploy/deploy-sftp.php" ]; then
php /tmp/mokostandards-api/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: |
echo "## Joomla 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_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $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
+61
View File
@@ -27,10 +27,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- Migrated all workflow and template paths from `.github/` to `.mokogitea/`
- Template source paths updated: `templates/gitea/` to `templates/mokogitea/`
- HCL definition files removed -- Template repos are now the canonical source
### 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
- System email template branding (DB approach)
### Added
- Trusted IPs: configurable repeatable rows of IP addresses, CIDR ranges, and wildcards that bypass admin session timeout
- Supports exact IPs (192.168.1.100), CIDR (10.0.0.0/24), and wildcards (192.168.1.*)
- Each entry has a label and enabled toggle for easy management
- Current IP display above trusted IPs table so admins can easily add their own IP
### Fixed
- Trusted IP session bypass: moved from `onAfterInitialise` to `boot()` so Joomla's session lifetime is extended before the session handler validates it (was too late, Joomla expired the session first)
- updates.xml: removed stale pre-release entries pointing to non-existent dev artifacts, legacy plugin update entry that caused stable sites to attempt dev downloads
- Removed duplicate `<updateservers>` from inner plugin manifest — only the package-level manifest should register the update server
- Auto-cleanup of stale plugin-level update site entries on install/update (cleans `#__update_sites` and `#__update_sites_extensions`)
## [02.06.00] - 2026-05-25
### Added
- Alias offline bypass: aliases with offline=No override Joomla's global offline setting, allowing access via alias domain while main site is down
- Block non-master users from viewing or editing MokoWaaS plugin settings
- Master user bypasses ALL tenant restrictions (install from URL, global config, sysinfo, installer, templates)
- Admin Help menu redirected to configured support URL (replaces help.joomla.org)
### Fixed
- Install API endpoint: extract ZIP to temp directory before passing to Joomla Installer (was passing ZIP path directly)
- Clean up extracted temp directory on success or failure
- Update site disabled by Joomla when protected=1 — ensureProtectedFlag() now re-enables it
- CI: auto-release fetches updates.xml from main before building (preserves all channels)
- CI: version_check.php --fix runs after version bump in both workflows
- CI: checksums attached as `[filename].sha256` files instead of in release body
- CI: auto-release cleaned up — removed duplicate asset loops, inline Python updater, dead code
- CI: Step 8b uses `release_body_update.php` CLI instead of inline Python
- CI: Step 6 tag creation no longer gated by `is_minor` (was never set)
### Changed
- CI: auto-release uses stream tag `stable` instead of version tag `vXX`
- CI: pre-release package build uses absolute paths (fixes relative path zip error)
## [02.05.00] - 2026-05-24
### Added
- Joomla `protected=1` flag on all MokoWaaS extensions (framework-level disable/uninstall prevention)
- Self-healing protected flag — restored each admin session if cleared
- Block non-master disable via plugin list toggle (`plugins.publish`)
- Package script sets `protected=1, locked=0` on every install/update
- Legacy plugin entry in updates.xml for sites upgrading from standalone plugin
### Fixed
- CI: auto-release workflow `pkg_pkg_` duplication in release names, ZIP filenames, and SHA256 paths
- CI: auto-release now strips existing type prefix and uses `<packagename>` for packages
- CI: `updates_xml_build` was cascading entries for all lower channels on stable release — now writes only current channel
- CI: `targetplatform` regex `((5.[0-9])|(6.[0-9]))` caused Gitea 500 on XML render — simplified to `(5|6)\..*`
- updates.xml stable entry now has correct `<tag>stable</tag>` and download URL
- README slimmed to overview, detailed content moved to wiki
## [02.03.10] - 2026-05-24
### Added
+1 -1
View File
@@ -9,7 +9,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
VERSION: 02.04.00
VERSION: 02.15.00
PATH: /README.md
BRIEF: MokoWaaS platform plugin for Joomla
-->
+1 -1
View File
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.04.00</version>
<version>02.15.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>
@@ -31,6 +31,7 @@ namespace Moko\Plugin\System\MokoWaaS\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Plugin\CMSPlugin;
@@ -38,6 +39,7 @@ use Joomla\CMS\Router\Route;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\UserHelper;
use Psr\Container\ContainerInterface;
/**
* MokoWaaS Brand System Plugin
@@ -47,7 +49,7 @@ use Joomla\CMS\User\UserHelper;
*
* @since 01.04.00
*/
class MokoWaaS extends CMSPlugin
class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
{
/**
* Obfuscated Grafana URL (XOR + base64).
@@ -114,6 +116,37 @@ class MokoWaaS extends CMSPlugin
*/
protected $app;
/**
* Boot the extension — runs BEFORE Joomla creates the session.
*
* Extends the Joomla session lifetime for trusted IPs so the
* session handler does not destroy the session before
* onAfterInitialise can run.
*
* @param ContainerInterface $container The DI container.
*
* @return void
*
* @since 02.11.00
*/
public function boot(ContainerInterface $container): void
{
$timeout = (int) $this->params->get('admin_session_timeout', 0);
if ($timeout <= 0)
{
return;
}
if ($this->ipIsTrusted())
{
// Set both PHP and Joomla session lifetimes before the
// session handler runs its expiry check.
ini_set('session.gc_maxlifetime', 315360000);
Factory::getConfig()->set('lifetime', 525600);
}
}
/**
* Event triggered after the framework has loaded and the application initialise method has been called.
*
@@ -653,7 +686,7 @@ class MokoWaaS extends CMSPlugin
return [
'{{BRAND_NAME}}' => $this->params->get('brand_name', 'MokoWaaS'),
'{{COMPANY_NAME}}' => $this->params->get('company_name', 'Moko Consulting'),
'{{SUPPORT_URL}}' => $this->params->get('support_url', 'https://mokoconsulting.tech'),
'{{SUPPORT_URL}}' => $this->params->get('support_url', 'https://mokoconsulting.tech/support'),
];
}
@@ -936,6 +969,7 @@ class MokoWaaS extends CMSPlugin
}
$this->injectFavicon($doc);
$this->redirectHelpMenu($doc);
// Hide MokoWaaS from plugin list for non-master users
if (!$this->isMasterUser())
@@ -975,6 +1009,32 @@ class MokoWaaS extends CMSPlugin
");
}
/**
* Redirect the admin Help menu link to the configured support URL.
*
* Joomla's Atum template hardcodes the Help link to help.joomla.org.
* This replaces it with the WaaS support URL via JS injection.
*
* @param \Joomla\CMS\Document\HtmlDocument $doc Document object
*
* @return void
*
* @since 02.10.00
*/
protected function redirectHelpMenu($doc)
{
$supportUrl = $this->params->get('support_url', 'https://mokoconsulting.tech/support');
$doc->addScriptDeclaration("
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('a[href*=\"help.joomla.org\"], a[href*=\"docs.joomla.org\"]').forEach(function(link) {
link.href = " . json_encode($supportUrl) . ";
link.target = '_blank';
});
});
");
}
/**
* Protect the plugin from being disabled or uninstalled by non-master users.
* Does NOT self-heal (no lock) — master users can still disable if needed.
@@ -1025,6 +1085,31 @@ class MokoWaaS extends CMSPlugin
$this->app->redirect('index.php?option=com_plugins');
}
}
// Block non-master from viewing or editing MokoWaaS plugin settings
if ($option === 'com_plugins')
{
$view = $this->app->input->get('view', '');
$layout = $this->app->input->get('layout', '');
$extensionId = (int) $this->app->input->get('extension_id', 0);
if (($view === 'plugin' || $layout === 'edit') && $extensionId > 0)
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('extension_id') . ' = ' . $extensionId)
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
if ((int) $db->setQuery($query)->loadResult() > 0)
{
$this->app->enqueueMessage('MokoWaaS settings are restricted to the master user.', 'warning');
$this->app->redirect('index.php?option=com_plugins');
}
}
}
}
/**
@@ -1042,6 +1127,8 @@ class MokoWaaS extends CMSPlugin
try
{
$db = Factory::getDbo();
// Set protected=1, locked=0 on MokoWaaS extensions
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('protected') . ' = 1')
@@ -1051,6 +1138,18 @@ class MokoWaaS extends CMSPlugin
->where($db->quoteName('protected') . ' = 0');
$db->setQuery($query);
$db->execute();
// Ensure update site stays enabled (protected extensions get their update site disabled by Joomla)
$query = $db->getQuery(true)
->update($db->quoteName('#__update_sites') . ' AS us')
->join('INNER', $db->quoteName('#__update_sites_extensions') . ' AS use2 ON us.update_site_id = use2.update_site_id')
->join('INNER', $db->quoteName('#__extensions') . ' AS e ON use2.extension_id = e.extension_id')
->set('us.enabled = 1')
->where('us.enabled = 0')
->where('(' . $db->quoteName('e.element') . ' = ' . $db->quote('mokowaas')
. ' OR ' . $db->quoteName('e.element') . ' = ' . $db->quote('pkg_mokowaas') . ')');
$db->setQuery($query);
$db->execute();
}
catch (\Throwable $e)
{
@@ -2747,11 +2846,36 @@ class MokoWaaS extends CMSPlugin
file_put_contents($tmpFile, $zipData);
// Extract ZIP to temp directory
$extractDir = $this->app->getConfig()->get('tmp_path', JPATH_ROOT . '/tmp')
. '/mokowaas_extract_' . md5($url);
if (is_dir($extractDir))
{
$this->rmdirRecursive($extractDir);
}
mkdir($extractDir, 0755, true);
$zip = new \ZipArchive();
if ($zip->open($tmpFile) !== true)
{
@unlink($tmpFile);
$this->sendHealthResponse(500, ['error' => 'Failed to open ZIP']);
return;
}
$zip->extractTo($extractDir);
$zip->close();
@unlink($tmpFile);
// Install using Joomla's installer
$installer = \Joomla\CMS\Installer\Installer::getInstance();
$result = $installer->install($tmpFile);
$result = $installer->install($extractDir);
@unlink($tmpFile);
$this->rmdirRecursive($extractDir);
if ($result)
{
@@ -2774,6 +2898,11 @@ class MokoWaaS extends CMSPlugin
{
@unlink($tmpFile ?? '');
if (!empty($extractDir) && is_dir($extractDir))
{
$this->rmdirRecursive($extractDir);
}
$this->sendHealthResponse(500, [
'error' => 'Install exception',
'message' => $e->getMessage(),
@@ -2782,6 +2911,42 @@ class MokoWaaS extends CMSPlugin
}
}
/**
* Recursively remove a directory.
*
* @param string $dir Directory path
*
* @return void
*
* @since 02.06.00
*/
protected function rmdirRecursive(string $dir): void
{
if (!is_dir($dir))
{
return;
}
$items = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item)
{
if ($item->isDir())
{
rmdir($item->getPathname());
}
else
{
unlink($item->getPathname());
}
}
rmdir($dir);
}
// ------------------------------------------------------------------
// Site Alias handling
// ------------------------------------------------------------------
@@ -2944,25 +3109,33 @@ class MokoWaaS extends CMSPlugin
}
// Offline: use Joomla's native offline mode for frontend requests
if (!empty($alias->offline) && (string) $alias->offline === '1'
&& $this->app->isClient('site'))
if ($this->app->isClient('site'))
{
// Allow health API to still respond
if ($this->app->input->get('mokowaas', '') !== '')
if (!empty($alias->offline) && (string) $alias->offline === '1')
{
return;
// Allow health API to still respond
if ($this->app->input->get('mokowaas', '') !== '')
{
return;
}
// Set custom offline message if provided
$message = $alias->offline_message ?? '';
if (!empty($message))
{
$this->app->getConfig()->set('offline_message', $message);
}
// Enable Joomla's native offline mode
$this->app->getConfig()->set('offline', 1);
}
// Set custom offline message if provided
$message = $alias->offline_message ?? '';
if (!empty($message))
else
{
$this->app->getConfig()->set('offline_message', $message);
// Alias is NOT offline — override Joomla's global offline setting
// This allows access via the alias domain even when the main site is offline
$this->app->getConfig()->set('offline', 0);
}
// Enable Joomla's native offline mode — renders through the template's offline.php
$this->app->getConfig()->set('offline', 1);
}
}
@@ -3203,6 +3376,12 @@ class MokoWaaS extends CMSPlugin
return;
}
// Trusted IPs — session lifetime already extended in boot()
if ($this->ipIsTrusted())
{
return;
}
$session = Factory::getSession();
$lastHit = $session->get('mokowaas.last_activity', 0);
$now = time();
@@ -3220,6 +3399,93 @@ class MokoWaaS extends CMSPlugin
$session->set('mokowaas.last_activity', $now);
}
/**
* Check whether the current request IP matches any trusted IP entry.
*
* Supports exact IPs, CIDR notation (e.g. 10.0.0.0/8), and
* wildcard patterns (e.g. 192.168.1.*).
*
* @return bool True if the current IP is in the trusted list.
*
* @since 02.11.00
*/
protected function ipIsTrusted(): bool
{
$entries = $this->params->get('trusted_ips', '');
if (empty($entries))
{
return false;
}
// Subform stores as JSON string or array
if (\is_string($entries))
{
$entries = json_decode($entries, true);
}
if (!\is_array($entries))
{
return false;
}
$ip = $this->app
? $this->app->input->server->getString('REMOTE_ADDR', '')
: ($_SERVER['REMOTE_ADDR'] ?? '');
$ipLong = ip2long($ip);
if ($ipLong === false)
{
return false;
}
foreach ($entries as $entry)
{
if (empty($entry['enabled']) || empty($entry['ip']))
{
continue;
}
$range = trim($entry['ip']);
// Wildcard: 192.168.1.*
if (str_contains($range, '*'))
{
$pattern = '/^' . str_replace(['.', '*'], ['\\.', '\\d+'], $range) . '$/';
if (preg_match($pattern, $ip))
{
return true;
}
continue;
}
// CIDR: 10.0.0.0/8
if (str_contains($range, '/'))
{
[$subnet, $bits] = explode('/', $range, 2);
$subnetLong = ip2long($subnet);
$mask = -1 << (32 - (int) $bits);
if ($subnetLong !== false && ($ipLong & $mask) === ($subnetLong & $mask))
{
return true;
}
continue;
}
// Exact match
if ($ip === $range)
{
return true;
}
}
return false;
}
/**
* Override Joomla upload restrictions at runtime.
@@ -3328,12 +3594,18 @@ class MokoWaaS extends CMSPlugin
*/
protected function enforceAdminRestrictions()
{
// Master user bypasses ALL restrictions
if ($this->isMasterUser())
{
return;
}
$input = $this->app->input;
$option = $input->get('option', '');
$view = $input->get('view', '');
$task = $input->get('task', '');
// Disable install-from-URL for ALL users (safety net)
// Disable install-from-URL for non-master users
if ($this->params->get('disable_install_url', 1)
&& $option === 'com_installer'
&& stripos($task, 'install') !== false
@@ -3344,12 +3616,6 @@ class MokoWaaS extends CMSPlugin
return;
}
// Remaining restrictions only apply to non-master users
if ($this->isMasterUser())
{
return;
}
$blocked = [];
if ($this->params->get('restrict_installer', 1))
@@ -0,0 +1,40 @@
<?php
/**
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
* VERSION: 02.11.00
* PATH: /src/Field/CurrentIpField.php
* BRIEF: Read-only field that displays the current user's IP address
*/
namespace Moko\Plugin\System\MokoWaaS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Form\FormField;
class CurrentIpField extends FormField
{
protected $type = 'CurrentIp';
protected function getInput()
{
$currentIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
return '<div class="alert alert-info mb-0 py-2">'
. '<strong>Your current IP:</strong> '
. '<code>' . htmlspecialchars($currentIp) . '</code> '
. '<small class="text-muted">&mdash; add this to the table below to keep your session alive.</small>'
. '</div>';
}
protected function getLabel()
{
return '';
}
}
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<form>
<field
name="ip"
type="text"
label="PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ADDR_LABEL"
description="PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ADDR_DESC"
required="true"
hint="e.g. 192.168.1.100 or 10.0.0.0/24"
/>
<field
name="label"
type="text"
label="PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_LABEL_LABEL"
description="PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_LABEL_DESC"
hint="e.g. Office network"
/>
<field
name="enabled"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ENABLED_LABEL"
default="1"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</form>
@@ -120,6 +120,13 @@ PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_LABEL="Force HTTPS"
PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_DESC="Redirect all HTTP requests to HTTPS. Supports reverse proxy setups."
PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_LABEL="Admin Session Timeout"
PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_DESC="Minutes of idle time before admin sessions expire. 0 uses the Joomla default."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_LABEL="Trusted IPs (No Session Timeout)"
PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_DESC="Sessions from these IP addresses or ranges will never time out. Supports exact IPs, CIDR notation (e.g. 10.0.0.0/24), and wildcards (e.g. 192.168.1.*)."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ADDR_LABEL="IP / CIDR"
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ADDR_DESC="An IP address, CIDR range, or wildcard pattern."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_LABEL_LABEL="Label"
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_LABEL_DESC="A descriptive label for this entry (e.g. Office, VPN)."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ENABLED_LABEL="Enabled"
PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_LABEL="Minimum Password Length"
PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_DESC="Minimum number of characters required for user passwords."
PLG_SYSTEM_MOKOWAAS_PASSWORD_UPPER_LABEL="Require Uppercase"
@@ -120,6 +120,13 @@ PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_LABEL="Force HTTPS"
PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_DESC="Redirect all HTTP requests to HTTPS. Supports reverse proxy setups."
PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_LABEL="Admin Session Timeout"
PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_DESC="Minutes of idle time before admin sessions expire. 0 uses the Joomla default."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_LABEL="Trusted IPs (No Session Timeout)"
PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_DESC="Sessions from these IP addresses or ranges will never time out. Supports exact IPs, CIDR notation (e.g. 10.0.0.0/24), and wildcards (e.g. 192.168.1.*)."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ADDR_LABEL="IP / CIDR"
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ADDR_DESC="An IP address, CIDR range, or wildcard pattern."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_LABEL_LABEL="Label"
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_LABEL_DESC="A descriptive label for this entry (e.g. Office, VPN)."
PLG_SYSTEM_MOKOWAAS_TRUSTED_IP_ENABLED_LABEL="Enabled"
PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_LABEL="Minimum Password Length"
PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_DESC="Minimum number of characters required for user passwords."
PLG_SYSTEM_MOKOWAAS_PASSWORD_UPPER_LABEL="Require Uppercase"
+19 -7
View File
@@ -30,16 +30,11 @@
<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.04.00</version>
<version>02.15.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>
<!-- Update server configuration -->
<updateservers>
<server type="extension" priority="1" name="MokoWaaS Update Server (Gitea)">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/raw/branch/main/updates.xml</server>
</updateservers>
<files>
<filename plugin="mokowaas">script.php</filename>
<folder>Extension</folder>
@@ -108,7 +103,7 @@
type="url"
label="PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_LABEL"
description="PLG_SYSTEM_MOKOWAAS_SUPPORT_URL_DESC"
default="https://mokoconsulting.tech"
default="https://mokoconsulting.tech/support"
/>
</fieldset>
<fieldset name="waas_access"
@@ -310,6 +305,7 @@
<fieldset name="security"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_DESC"
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
>
<field name="force_https" type="radio" default="1"
label="PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_LABEL"
@@ -322,6 +318,22 @@
label="PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_LABEL"
description="PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_DESC"
default="60" hint="Minutes (0 = Joomla default)" />
<field
name="current_ip_display"
type="CurrentIp"
label=""
/>
<field
name="trusted_ips"
type="subform"
label="PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_DESC"
formsource="plugins/system/mokowaas/forms/trusted_ip_entry.xml"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
groupByFieldset="false"
buttons="add,remove,move"
/>
<field name="password_min_length" type="number" default="12"
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_LABEL"
description="PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_DESC" />
+73 -1
View File
@@ -123,6 +123,7 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
if ($type === 'install' || $type === 'update')
{
$this->enableAndLockPlugin();
$this->cleanupPluginUpdateSite();
$this->ensureMokoCassiopeia();
$this->installLanguageOverrides();
$this->updateLoginSupportUrls();
@@ -210,6 +211,77 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
$db->execute();
}
/**
* Remove the plugin-level update site so only the package-level one remains.
*
* Earlier versions registered an update server in the plugin manifest
* (plg_system_mokowaas) in addition to the package manifest (pkg_mokowaas).
* This caused Joomla to check for plugin-level updates that don't exist,
* leading to failed downloads. Only the package update site should exist.
*
* @return void
*
* @since 02.11.02
*/
private function cleanupPluginUpdateSite()
{
$db = Factory::getDbo();
// Find the extension_id for the plugin
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$db->setQuery($query);
$pluginId = (int) $db->loadResult();
if (!$pluginId)
{
return;
}
// Find update_site_ids linked to the plugin (not the package)
$query = $db->getQuery(true)
->select($db->quoteName('update_site_id'))
->from($db->quoteName('#__update_sites_extensions'))
->where($db->quoteName('extension_id') . ' = ' . $pluginId);
$db->setQuery($query);
$siteIds = $db->loadColumn();
if (empty($siteIds))
{
return;
}
// Delete the link rows
$db->setQuery(
$db->getQuery(true)
->delete($db->quoteName('#__update_sites_extensions'))
->where($db->quoteName('extension_id') . ' = ' . $pluginId)
)->execute();
// Delete orphaned update_sites rows (only if no other extension uses them)
foreach ($siteIds as $siteId)
{
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__update_sites_extensions'))
->where($db->quoteName('update_site_id') . ' = ' . (int) $siteId);
$db->setQuery($query);
if ((int) $db->loadResult() === 0)
{
$db->setQuery(
$db->getQuery(true)
->delete($db->quoteName('#__update_sites'))
->where($db->quoteName('update_site_id') . ' = ' . (int) $siteId)
)->execute();
}
}
}
/**
* Ensure MokoOnyx is installed, locked, and set as default.
*
@@ -482,7 +554,7 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
return [
'{{BRAND_NAME}}' => $params->get('brand_name', 'MokoWaaS'),
'{{COMPANY_NAME}}' => $params->get('company_name', 'Moko Consulting'),
'{{SUPPORT_URL}}' => $params->get('support_url', 'https://mokoconsulting.tech'),
'{{SUPPORT_URL}}' => $params->get('support_url', 'https://mokoconsulting.tech/support'),
];
}
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.04.00</version>
<version>02.15.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>
@@ -11,6 +11,7 @@ namespace Moko\Plugin\WebServices\MokoWaaS\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Event\Application\BeforeApiRouteEvent;
use Joomla\CMS\Router\ApiRouter;
use Joomla\Event\SubscriberInterface;
@@ -36,14 +37,16 @@ final class MokoWaaSApi extends CMSPlugin implements SubscriberInterface
/**
* Register API routes for MokoWaaS.
*
* @param ApiRouter $router The API router
* @param BeforeApiRouteEvent $event The API route event (Joomla 6 typed event)
*
* @return void
*
* @since 1.0.0
*/
public function onBeforeApiRoute(&$router): void
public function onBeforeApiRoute(BeforeApiRouteEvent $event): void
{
$router = $event->getRouter();
$router->createCRUDRoutes(
'v1/mokowaas/health',
'health',
@@ -0,0 +1,17 @@
<?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.15.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>
@@ -0,0 +1,42 @@
<?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;
}
);
}
};
@@ -0,0 +1,539 @@
<?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 -1
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade">
<name>MokoWaaS</name>
<packagename>mokowaas</packagename>
<version>02.04.00</version>
<version>02.15.00</version>
<creationDate>2026-05-23</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -16,6 +16,7 @@
<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>
+59 -44
View File
@@ -1,83 +1,98 @@
<?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.04.00
VERSION: 02.15.00
-->
<updates>
<update>
<name>MokoWaaS</name>
<description>MokoWaaS update</description>
<element>mokowaas</element>
<name>Package - MokoWaaS</name>
<description>Package - MokoWaaS development build.</description>
<element>pkg_mokowaas</element>
<type>package</type>
<version>02.04.00-dev</version>
<tags><tag>development</tag></tags>
<infourl title="MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development</infourl>
<client>site</client>
<version>02.15.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/development/pkg_mokowaas-02.04.00-dev.zip</downloadurl>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.15.00.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="(5|6)\..*" />
<sha256>2bd856769eaeb1de09df0d4335dc6eb92f67c7e7a4f72f286f8b703c05854e06</sha256>
<tags><tag>dev</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*" />
</update>
<update>
<name>MokoWaaS</name>
<description>MokoWaaS update</description>
<element>mokowaas</element>
<name>Package - MokoWaaS</name>
<description>Package - MokoWaaS alpha build.</description>
<element>pkg_mokowaas</element>
<type>package</type>
<version>02.04.00-alpha</version>
<client>site</client>
<version>02.15.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.15.00.zip</downloadurl>
</downloads>
<sha256>2bd856769eaeb1de09df0d4335dc6eb92f67c7e7a4f72f286f8b703c05854e06</sha256>
<tags><tag>alpha</tag></tags>
<infourl title="MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/alpha</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/alpha/pkg_mokowaas-02.04.00-alpha.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*" />
</update>
<update>
<name>MokoWaaS</name>
<description>MokoWaaS update</description>
<element>mokowaas</element>
<name>Package - MokoWaaS</name>
<description>Package - MokoWaaS beta build.</description>
<element>pkg_mokowaas</element>
<type>package</type>
<version>02.04.00-beta</version>
<client>site</client>
<version>02.15.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.15.00.zip</downloadurl>
</downloads>
<sha256>2bd856769eaeb1de09df0d4335dc6eb92f67c7e7a4f72f286f8b703c05854e06</sha256>
<tags><tag>beta</tag></tags>
<infourl title="MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/beta</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/beta/pkg_mokowaas-02.04.00-beta.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*" />
</update>
<update>
<name>MokoWaaS</name>
<description>MokoWaaS update</description>
<element>mokowaas</element>
<name>Package - MokoWaaS</name>
<description>Package - MokoWaaS rc build.</description>
<element>pkg_mokowaas</element>
<type>package</type>
<version>02.04.00-rc</version>
<client>site</client>
<version>02.15.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.15.00.zip</downloadurl>
</downloads>
<sha256>2bd856769eaeb1de09df0d4335dc6eb92f67c7e7a4f72f286f8b703c05854e06</sha256>
<tags><tag>rc</tag></tags>
<infourl title="MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/rc</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/rc/pkg_mokowaas-02.04.00-rc.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="(5|6)\..*" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*" />
</update>
<update>
<name>MokoWaaS</name>
<description>MokoWaaS update</description>
<element>mokowaas</element>
<name>Package - MokoWaaS</name>
<description>Package - MokoWaaS stable build.</description>
<element>pkg_mokowaas</element>
<type>package</type>
<version>02.04.00</version>
<tags><tag>stable</tag></tags>
<infourl title="MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable</infourl>
<client>site</client>
<version>02.15.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.04.00.zip</downloadurl>
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.15.00.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="(5|6)\..*" />
<sha256>2bd856769eaeb1de09df0d4335dc6eb92f67c7e7a4f72f286f8b703c05854e06</sha256>
<tags><tag>stable</tag></tags>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
<targetplatform name="joomla" version="(5|6)\..*" />
</update>
</updates>
+181
View File
@@ -0,0 +1,181 @@
# 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.
+94
View File
@@ -0,0 +1,94 @@
# 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 |
+127
View File
@@ -0,0 +1,127 @@
# 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
+33
View File
@@ -0,0 +1,33 @@
# 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 |
+267
View File
@@ -0,0 +1,267 @@
# 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 |
+52
View File
@@ -0,0 +1,52 @@
# 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)
+100
View File
@@ -0,0 +1,100 @@
# 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.
+89
View File
@@ -0,0 +1,89 @@
# 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
```
+69
View File
@@ -0,0 +1,69 @@
# 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.