Compare commits

...

35 Commits

Author SHA1 Message Date
jmiller 1c256bba7a chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-24 11:51:45 +00:00
jmiller cd4dc6efd2 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-24 11:51:44 +00:00
jmiller 2e6b71ac97 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-24 11:51:41 +00:00
jmiller 5b4f84bad7 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-24 11:51:39 +00:00
jmiller 847bb9bd0f chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-24 11:51:37 +00:00
jmiller ebcaf44b63 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-24 11:51:32 +00:00
jmiller 17b05f9a13 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-06-24 11:51:30 +00:00
gitea-actions[bot] 2ac4923d74 chore: promote changelog [Unreleased] → [01.43.00]
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Universal: PR Check / Secret Scan (pull_request) Successful in 6s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 43s
2026-06-24 11:49:15 +00:00
gitea-actions[bot] adc4935587 chore(release): build 01.43.00 [skip ci] 2026-06-24 11:49:12 +00:00
jmiller 8f7b747c59 fix: add missing module entry point for cpanel module install (#144)
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 47s
2026-06-24 11:49:01 +00:00
gitea-actions[bot] 42b7503d7b chore(version): pre-release bump to 01.42.04-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 17s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 4m51s
2026-06-24 00:05:44 +00:00
jmiller 9ac8757a8c Merge pull request 'fix: add missing module entry point for cpanel module install' (#143) from fix/cpanel-module-install into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 12s
fix: add missing module entry point for cpanel module install (#143)
2026-06-24 00:05:30 +00:00
gitea-actions[bot] ef3fde1c39 chore(version): pre-release bump to 01.42.03-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-06-23 23:53:35 +00:00
Jonathan Miller 5750e71d15 fix: add missing module entry point for cpanel module install
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
Joomla's module installer requires a <filename module="..."> element
in the manifest's <files> section. Without it, installation fails with
"No module file specified." Added the stub PHP file and manifest entry.
2026-06-23 18:53:00 -05:00
gitea-actions[bot] c8e022d46b chore(version): pre-release bump to 01.42.02-dev [skip ci] 2026-06-23 23:06:28 +00:00
jmiller 21f2ba0eff Merge pull request 'chore: sync main into dev (preserves dev-only changes)' (#142) from chore/sync-main-to-dev into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 15s
2026-06-23 23:06:15 +00:00
gitea-actions[bot] 821c4bae11 chore(version): pre-release bump to 01.42.01-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
2026-06-23 23:05:52 +00:00
Jonathan Miller e86c104276 Merge remote-tracking branch 'origin/main' into chore/sync-main-to-dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 41s
2026-06-23 18:05:20 -05:00
gitea-actions[bot] af2a1a2dae chore: promote changelog [Unreleased] → [01.42.00]
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 36s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-06-23 23:00:23 +00:00
gitea-actions[bot] c88b163de0 chore(release): build 01.42.00 [skip ci] 2026-06-23 23:00:20 +00:00
jmiller 358a7eb68a Merge pull request 'docs: Comprehensive CHANGELOG consolidation + wiki update + testing parameters' (#140) from chore/changelog-wiki-testing into main 2026-06-23 23:00:08 +00:00
Jonathan Miller 898520d1db chore: sync auto-release.yml from Template-Generic [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 3s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 18s
2026-06-23 17:58:49 -05:00
gitea-actions[bot] e633d0cc0a chore(version): pre-release bump to 01.41.03-dev [skip ci] 2026-06-23 22:20:21 +00:00
Jonathan Miller ff7418721d fix: review findings — key desc, missing changelog, [HOST] domain resolution
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
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 4s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 7s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 8s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 14s
- Language: "encrypted" → "base64-encoded" for SSH key description
- CHANGELOG: added 3 missing bug fix entries (fields_values scope, CSRF
  token on Run Backup, SFTP showon/required)
- [HOST] placeholder: resolve domain from Joomla live_site config when
  HTTP_HOST is unavailable (CLI), instead of falling back to system
  hostname (joomla.invalid). Applied to both PlaceholderResolver and
  FolderPickerField.
2026-06-23 17:20:05 -05:00
gitea-actions[bot] 0b2b885163 chore(version): pre-release bump to 01.41.02-dev [skip ci] 2026-06-23 22:10:35 +00:00
Jonathan Miller 6c47838b30 fix: clean up wordy field descriptions — shorter, punchier text
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
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Secret Scan (pull_request) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 13s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 18s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 48s
Backup dir, archive name, MokoRestore, SFTP key, sanitization,
encryption descriptions all shortened. Removed redundant placeholder
lists (now handled by clickable pills and help modal).
2026-06-23 17:09:59 -05:00
gitea-actions[bot] 0f95cb6e9f chore(version): pre-release bump to 01.41.01-dev [skip ci] 2026-06-23 22:01:24 +00:00
Jonathan Miller 1da2fdb856 docs: comprehensive CHANGELOG consolidation for v01.41.00
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 12s
Consolidated all fragmented changelog entries from the session into
a single clean v01.41.00 release entry organized by feature area.
Covers: multi-remote, snapshots, SFTP, MokoRestore, sanitization,
engine improvements, admin UI, CLI/API, notifications, security.
2026-06-23 17:01:11 -05:00
gitea-actions[bot] 4df70531e2 chore(version): pre-release bump to 01.39.02-dev [skip ci]
Publish to Composer / Publish Package (release) Failing after 7s
2026-06-23 18:13:45 +00:00
gitea-actions[bot] 845b856cda chore(version): auto-bump patch 01.28.03-dev [skip ci] 2026-06-23 18:12:45 +00:00
jmiller 633e9b7f1e chore: remove security-audit.yml -- handled by MokoGitea
Universal: Auto Version Bump / Version Bump (push) Successful in 20s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 43s
2026-06-23 18:05:04 +00:00
gitea-actions[bot] ec0b7eb8a4 chore(version): auto-bump patch 01.28.02-dev [skip ci] 2026-06-23 18:04:50 +00:00
jmiller 7d119565da chore: remove deploy-manual.yml -- no longer needed
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 21s
2026-06-23 17:59:42 +00:00
gitea-actions[bot] 9db7331a72 chore(version): pre-release bump to 01.28.01-dev [skip ci]
Publish to Composer / Publish Package (release) Failing after 5s
2026-06-21 23:58:07 +00:00
gitea-actions[bot] 32931c1e37 chore(version): auto-bump patch 01.27.04-dev [skip ci] 2026-06-21 23:57:56 +00:00
21 changed files with 2116 additions and 1827 deletions
+66 -66
View File
@@ -1,66 +1,66 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml # PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00 # VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits) # BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
name: "Universal: Auto Version Bump" name: "Universal: Auto Version Bump"
on: on:
push: push:
branches: branches:
- dev - dev
- rc - rc
- 'feature/**' - 'feature/**'
- 'patch/**' - 'patch/**'
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions: permissions:
contents: write contents: write
jobs: jobs:
bump: bump:
name: Version Bump name: Version Bump
runs-on: release runs-on: release
if: >- if: >-
!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') && !contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request') !startsWith(github.event.head_commit.message, 'Merge pull request')
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1 fetch-depth: 1
- name: Setup mokocli tools - name: Setup mokocli tools
run: | run: |
if ! command -v composer &> /dev/null; then if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi fi
if [ -d "/opt/mokocli/cli" ]; then if [ -d "/opt/mokocli/cli" ]; then
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else else
git clone --depth 1 --branch main --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \ "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV" echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
fi fi
- name: Bump version - name: Bump version
run: | run: |
php ${MOKO_CLI}/version_auto_bump.php \ php ${MOKO_CLI}/version_auto_bump.php \
--path . --branch "${GITHUB_REF_NAME}" \ --path . --branch "${GITHUB_REF_NAME}" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" --repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+467 -466
View File
@@ -1,466 +1,467 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template # PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00 # VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from manifest.xml
# #
# +=======================================================================+ # +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE | # | UNIVERSAL BUILD & RELEASE PIPELINE |
# +=======================================================================+ # +=======================================================================+
# | | # | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | | # | |
# | Platform-specific: | # | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages | # | joomla: XML manifest, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset | # | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream | # | generic: README-only, no update stream |
# | | # | |
# +=======================================================================+ # +=======================================================================+
name: "Universal: Build & Release" name: "Universal: Build & Release"
on: on:
pull_request: pull_request:
types: [opened, closed] types: [opened, synchronize, closed]
branches: branches:
- main - main
paths-ignore: paths-ignore:
- '.mokogitea/workflows/**' - '.mokogitea/workflows/**'
- '*.md' - '*.md'
- 'wiki/**' - 'wiki/**'
- '.editorconfig' - '.editorconfig'
- '.gitignore' - '.gitignore'
- '.gitattributes' - '.gitattributes'
- '.gitmessage' - '.gitmessage'
- 'LICENSE' - 'LICENSE'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
action: action:
description: 'Action to perform' description: 'Action to perform'
required: false required: false
type: choice type: choice
default: release default: release
options: options:
- release - release
- promote-rc - promote-rc
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions: permissions:
contents: write contents: write
jobs: jobs:
# ── PR Opened → Rename branch to RC and build RC release ───────────────────────── # ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
promote-rc: promote-rc:
name: Promote to RC name: Promote to RC
runs-on: release runs-on: release
if: >- if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) || (github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') (github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository steps:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Checkout repository
with: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
token: ${{ secrets.MOKOGITEA_TOKEN }} with:
fetch-depth: 1 token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup mokocli tools
env: - name: Setup mokocli tools
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} env:
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then run: |
echo Using pre-installed /opt/mokocli if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV echo Using pre-installed /opt/mokocli
else echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
echo Falling back to fresh clone else
if ! command -v composer > /dev/null 2>&1; then echo Falling back to fresh clone
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 if ! command -v composer > /dev/null 2>&1; then
fi 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
rm -rf /tmp/mokocli fi
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git rm -rf /tmp/mokocli
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
cd /tmp/mokocli git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
composer install --no-dev --no-interaction --quiet cd /tmp/mokocli
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV composer install --no-dev --no-interaction --quiet
fi echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Rename branch to rc
run: | - name: Rename branch to rc
php ${MOKO_CLI}/branch_rename.php \ run: |
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \ php ${MOKO_CLI}/branch_rename.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \ --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--pr "${{ github.event.pull_request.number }}" --api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git
run: | - name: Checkout rc and configure git
git fetch origin rc run: |
git checkout rc git fetch origin rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git checkout rc
git config --local user.name "gitea-actions[bot]" git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
run: | - name: Publish RC release
php ${MOKO_CLI}/release_publish.php \ run: |
--path . --stability rc --bump minor --branch rc \ php ${MOKO_CLI}/release_publish.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Update RC release notes from CHANGELOG.md
run: | - name: Update RC release notes from CHANGELOG.md
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" run: |
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Extract [Unreleased] section from changelog
NOTES="" # Extract [Unreleased] section from changelog
if [ -f "CHANGELOG.md" ]; then NOTES=""
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) if [ -f "CHANGELOG.md" ]; then
fi NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release candidate" fi
[ -z "$NOTES" ] && NOTES="Release candidate"
# Find the RC release and update its body
RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \ # Find the RC release and update its body
"${API_BASE}/releases/tags/release-candidate" \ RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) "${API_BASE}/releases/tags/release-candidate" \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c " if [ -n "$RELEASE_ID" ]; then
import json, urllib.request python3 -c "
body = open('/dev/stdin').read() import json, urllib.request
payload = json.dumps({'body': body}).encode() body = open('/dev/stdin').read()
req = urllib.request.Request( payload = json.dumps({'body': body}).encode()
'${API_BASE}/releases/${RELEASE_ID}', req = urllib.request.Request(
data=payload, method='PATCH', '${API_BASE}/releases/${RELEASE_ID}',
headers={ data=payload, method='PATCH',
'Authorization': 'token ${TOKEN}', headers={
'Content-Type': 'application/json' 'Authorization': 'token ${TOKEN}',
}) 'Content-Type': 'application/json'
urllib.request.urlopen(req) })
" <<< "$NOTES" urllib.request.urlopen(req)
echo "RC release notes updated from CHANGELOG.md" " <<< "$NOTES"
fi echo "RC release notes updated from CHANGELOG.md"
fi
- name: Summary
if: always() - name: Summary
run: | if: always()
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY run: |
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
release: # ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
name: Build & Release Pipeline release:
runs-on: release name: Build & Release Pipeline
if: >- runs-on: release
github.event.pull_request.merged == true || if: >-
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository steps:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Checkout repository
with: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
token: ${{ secrets.MOKOGITEA_TOKEN }} with:
fetch-depth: 0 token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: | - name: Configure git for bot pushes
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" run: |
git config --local user.name "gitea-actions[bot]" git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: | - name: Check for merge conflict markers
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) run: |
if [ -n "$CONFLICTS" ]; then CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
echo "::error::Merge conflict markers found — aborting release" if [ -n "$CONFLICTS" ]; then
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY echo "::error::Merge conflict markers found — aborting release"
echo '```' >> $GITHUB_STEP_SUMMARY echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
exit 1 echo '```' >> $GITHUB_STEP_SUMMARY
fi exit 1
echo "No conflict markers found" fi
echo "No conflict markers found"
- name: Setup mokocli tools
env: - name: Setup mokocli tools
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} env:
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then run: |
echo Using pre-installed /opt/mokocli if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV echo Using pre-installed /opt/mokocli
else echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
echo Falling back to fresh clone else
if ! command -v composer > /dev/null 2>&1; then echo Falling back to fresh clone
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 if ! command -v composer > /dev/null 2>&1; then
fi 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
rm -rf /tmp/mokocli fi
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git rm -rf /tmp/mokocli
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
cd /tmp/mokocli git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
composer install --no-dev --no-interaction --quiet cd /tmp/mokocli
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV composer install --no-dev --no-interaction --quiet
fi echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: "Detect platform"
id: platform - name: "Detect platform"
run: | id: platform
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true run: |
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
- name: "Determine version bump level"
id: bump - name: "Determine version bump level"
run: | id: bump
# Fix/patch branches: version was already bumped by pre-release, just strip suffix run: |
# Feature/dev branches: bump minor for the new stable release # Fix/patch branches: version was already bumped by pre-release, just strip suffix
HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}" # Feature/dev branches: bump minor for the new stable release
case "$HEAD_REF" in HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}"
fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;; case "$HEAD_REF" in
*) BUMP="minor" ;; fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;;
esac *) BUMP="minor" ;;
echo "level=${BUMP}" >> "$GITHUB_OUTPUT" esac
echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})" echo "level=${BUMP}" >> "$GITHUB_OUTPUT"
echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})"
- name: "Publish stable release"
run: | - name: "Publish stable release"
BUMP_FLAG="" run: |
if [ "${{ steps.bump.outputs.level }}" != "none" ]; then BUMP_FLAG=""
BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}" if [ "${{ steps.bump.outputs.level }}" != "none" ]; then
fi BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}"
php ${MOKO_CLI}/release_publish.php \ fi
--path . --stability stable ${BUMP_FLAG} --branch main \ php ${MOKO_CLI}/release_publish.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: "Read published version"
id: version - name: "Read published version"
run: | id: version
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "") run: |
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT" VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" [ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
PLATFORM="${{ steps.platform.outputs.platform }}" echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
if [[ "$PLATFORM" == joomla* ]]; then PLATFORM="${{ steps.platform.outputs.platform }}"
echo "tag=stable" >> "$GITHUB_OUTPUT" if [[ "$PLATFORM" == joomla* ]]; then
echo "release_tag=stable" >> "$GITHUB_OUTPUT" echo "tag=stable" >> "$GITHUB_OUTPUT"
else echo "release_tag=stable" >> "$GITHUB_OUTPUT"
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" else
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT" echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
fi echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "branch=main" >> "$GITHUB_OUTPUT" fi
echo "Published version: ${VERSION}" echo "branch=main" >> "$GITHUB_OUTPUT"
echo "Published version: ${VERSION}"
- name: "Create semver tag for non-Joomla repos"
id: semver - name: "Create semver tag for non-Joomla repos"
if: | id: semver
steps.version.outputs.skip != 'true' && if: |
!startsWith(steps.platform.outputs.platform, 'joomla') steps.version.outputs.skip != 'true' &&
run: | !startsWith(steps.platform.outputs.platform, 'joomla')
VERSION="${{ steps.version.outputs.version }}" run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" VERSION="${{ steps.version.outputs.version }}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
SEMVER_TAG="v${VERSION}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
SEMVER_TAG="v${VERSION}"
echo "Creating semver tag: ${SEMVER_TAG}"
echo "Creating semver tag: ${SEMVER_TAG}"
# Create the git tag via API
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \ # Create the git tag via API
-X POST -H "Authorization: token ${TOKEN}" \ HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-H "Content-Type: application/json" \ -X POST -H "Authorization: token ${TOKEN}" \
"${API_BASE}/tags" \ -H "Content-Type: application/json" \
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000") "${API_BASE}/tags" \
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "Created semver tag: ${SEMVER_TAG}" if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
elif [ "$HTTP_CODE" = "409" ]; then echo "Created semver tag: ${SEMVER_TAG}"
echo "Semver tag ${SEMVER_TAG} already exists (skipped)" elif [ "$HTTP_CODE" = "409" ]; then
else echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})" else
fi echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
fi
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
- name: Update release notes and promote changelog
run: | - name: Update release notes and promote changelog
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" run: |
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Get the stable release info (version and ID)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \ # Get the stable release info (version and ID)
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}') RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true) "${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
# Extract version from release name (e.g. "06.17.00" or "v06.17.00") RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
VERSION=$(python3 -c " # Extract version from release name (e.g. "06.17.00" or "v06.17.00")
import json, sys, re VERSION=$(python3 -c "
r = json.load(sys.stdin) import json, sys, re
name = r.get('name', '') r = json.load(sys.stdin)
m = re.search(r'(\d+\.\d+\.\d+)', name) name = r.get('name', '')
print(m.group(1) if m else '') m = re.search(r'(\d+\.\d+\.\d+)', name)
" <<< "$RELEASE_JSON" 2>/dev/null || true) print(m.group(1) if m else '')
" <<< "$RELEASE_JSON" 2>/dev/null || true)
# Extract [Unreleased] section from changelog
NOTES="" # Extract [Unreleased] section from changelog
if [ -f "CHANGELOG.md" ]; then NOTES=""
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) if [ -f "CHANGELOG.md" ]; then
fi NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Stable release" fi
[ -z "$NOTES" ] && NOTES="Stable release"
# Update release body via API
if [ -n "$RELEASE_ID" ]; then # Update release body via API
python3 -c " if [ -n "$RELEASE_ID" ]; then
import json, urllib.request python3 -c "
body = open('/dev/stdin').read() import json, urllib.request
payload = json.dumps({'body': body}).encode() body = open('/dev/stdin').read()
req = urllib.request.Request( payload = json.dumps({'body': body}).encode()
'${API_BASE}/releases/${RELEASE_ID}', req = urllib.request.Request(
data=payload, method='PATCH', '${API_BASE}/releases/${RELEASE_ID}',
headers={ data=payload, method='PATCH',
'Authorization': 'token ${TOKEN}', headers={
'Content-Type': 'application/json' 'Authorization': 'token ${TOKEN}',
}) 'Content-Type': 'application/json'
urllib.request.urlopen(req) })
" <<< "$NOTES" urllib.request.urlopen(req)
echo "Release notes updated from CHANGELOG.md" " <<< "$NOTES"
fi echo "Release notes updated from CHANGELOG.md"
fi
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then # Promote [Unreleased] → [version] in CHANGELOG.md and reset
DATE=$(date +%Y-%m-%d) if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
python3 -c " DATE=$(date +%Y-%m-%d)
import sys python3 -c "
version, date = sys.argv[1], sys.argv[2] import sys
content = open('CHANGELOG.md').read() version, date = sys.argv[1], sys.argv[2]
old = '## [Unreleased]' content = open('CHANGELOG.md').read()
new = f'## [Unreleased]\n\n## [{version}] --- {date}' old = '## [Unreleased]'
content = content.replace(old, new, 1) new = f'## [Unreleased]\n\n## [{version}] --- {date}'
open('CHANGELOG.md', 'w').write(content) content = content.replace(old, new, 1)
" "$VERSION" "$DATE" open('CHANGELOG.md', 'w').write(content)
git add CHANGELOG.md " "$VERSION" "$DATE"
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true git add CHANGELOG.md
git push origin main || true git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
echo "Changelog promoted: [Unreleased] → [${VERSION}]" git push origin main || true
fi echo "Changelog promoted: [Unreleased] → [${VERSION}]"
fi
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub" # -- STEP 9: Mirror to GitHub (stable only) --------------------------------
if: >- - name: "Step 9: Mirror release to GitHub"
steps.version.outputs.skip != 'true' && if: >-
secrets.GH_MIRROR_TOKEN != '' steps.version.outputs.skip != 'true' &&
continue-on-error: true secrets.GH_MIRROR_TOKEN != ''
run: | continue-on-error: true
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" run: |
RELEASE_TAG="${{ steps.version.outputs.release_tag }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
php ${MOKO_CLI}/release_mirror.php \ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
--version "$VERSION" --tag "$RELEASE_TAG" \ php ${MOKO_CLI}/release_mirror.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --version "$VERSION" --tag "$RELEASE_TAG" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--branch main 2>&1 || true --gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY --branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
- name: "Step 10: Push main to GitHub mirror" # -- STEP 10: Sync main branch to GitHub mirror ----------------------------
if: >- - name: "Step 10: Push main to GitHub mirror"
steps.version.outputs.skip != 'true' && if: >-
secrets.GH_MIRROR_TOKEN != '' steps.version.outputs.skip != 'true' &&
continue-on-error: true secrets.GH_MIRROR_TOKEN != ''
run: | continue-on-error: true
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" run: |
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1) GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2) GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \ GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
git fetch origin main --depth=1 git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
git push github origin/main:refs/heads/main --force 2>/dev/null \ git fetch origin main --depth=1
&& echo "main branch pushed to GitHub mirror" \ git push github origin/main:refs/heads/main --force 2>/dev/null \
|| echo "WARNING: GitHub mirror push failed" && echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch and recreate dev from main"
if: steps.version.outputs.skip != 'true' - name: "Step 11: Delete rc branch and recreate dev from main"
continue-on-error: true if: steps.version.outputs.skip != 'true'
run: | continue-on-error: true
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" run: |
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ # Delete rc branch (ephemeral — created by promote-rc)
"${API_BASE}/branches/rc" 2>/dev/null \ curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
&& echo "Deleted rc branch" || echo "rc branch not found" "${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ # Delete dev branch
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch" curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \ # Recreate dev from main (now includes version bump + changelog promotion)
-H "Content-Type: application/json" \ curl -sf -X POST -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches" \ -H "Content-Type: application/json" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main" "${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true' - name: "Step 12: Create version branch from main"
continue-on-error: true if: steps.version.outputs.skip != 'true'
run: | continue-on-error: true
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" run: |
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
BRANCH_NAME="version/${VERSION}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
MAIN_SHA=$(git rev-parse HEAD) BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD)
# Delete old version branch if it exists (same version re-release)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}" # Delete old version branch if it exists (same version re-release)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
# Create version/XX.YY.ZZ from main
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed" # Create version/XX.YY.ZZ from main
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Post-release: Reset dev version" # -- Dolibarr post-release: Reset dev version -----------------------------
if: steps.version.outputs.skip != 'true' - name: "Post-release: Reset dev version"
continue-on-error: true if: steps.version.outputs.skip != 'true'
run: | continue-on-error: true
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" run: |
php ${MOKO_CLI}/version_reset_dev.php \ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ php ${MOKO_CLI}/version_reset_dev.php \
--branch dev --path . 2>&1 || true --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary # -- Summary --------------------------------------------------------------
if: always() - name: Pipeline Summary
run: | if: always()
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" run: |
PLATFORM="${{ steps.platform.outputs.platform }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then PLATFORM="${{ steps.platform.outputs.platform }}"
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
else echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY else
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
fi echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation # INGROUP: mokocli.Automation
# VERSION: 01.41.00 # VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch" name: "Universal: Issue Branch"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
# VERSION: 01.00.00
# BRIEF: Set or reset the extension version across all version-bearing files
name: "Joomla: Set Version"
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g. 01.00.00)"
required: true
type: string
branch:
description: "Branch to update (default: current)"
required: false
type: string
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
VERSION="${{ inputs.version }}"
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
exit 1
fi
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1
- name: Update manifest version
run: |
MANIFEST=""
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla extension manifest found — skipping manifest update"
else
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
fi
- name: Update README.md version
run: |
if [ -f "README.md" ]; then
if grep -qP '^\s*VERSION:\s*\d' README.md; then
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
echo "README.md version updated to ${VERSION}"
else
echo "::warning::No VERSION line found in README.md — skipping"
fi
fi
- name: Update CHANGELOG.md
run: |
if [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
# Check if this version already has an entry
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
else
# Insert new version entry after [Unreleased] or at the top after header
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
else
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
fi
echo "CHANGELOG.md: added entry for ${VERSION}"
fi
else
echo "::warning::No CHANGELOG.md found — skipping"
fi
- name: Update FILE INFORMATION blocks
run: |
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
while IFS= read -r -d '' FILE; do
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
echo "Updated FILE INFORMATION VERSION in ${FILE}"
fi
done
- name: Commit and push
run: |
git config user.name "Moko Consulting [bot]"
git config user.email "hello@mokoconsulting.tech"
git add -A
if git diff --cached --quiet; then
echo "No version changes detected — nothing to commit"
else
git commit -m "chore: set version to ${VERSION} [skip bump]
Authored-by: Moko Consulting"
git push
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
fi
+126 -28
View File
@@ -1,38 +1,136 @@
# Changelog # Changelog
## [Unreleased] ## [Unreleased]
## [01.41.00] --- 2026-06-23 ## [01.43.00] --- 2026-06-24
## [01.41.00] --- 2026-06-23
### Added
- Multi-remote storage: new `#__mokosuitebackup_remotes` table for multiple destinations per profile (#97)
- Remote destinations UI: AJAX-driven add/edit/delete/toggle modal on profile edit view
- Engine integration: BackupEngine and SteppedBackupEngine upload to all enabled destinations
- Migration SQL: auto-migrates existing SFTP/S3/GDrive/FTP configs to new table
- Backward compatibility: falls back to legacy single-remote columns if remotes table is empty
- Secrets masked in API responses, merged from DB on save to prevent leakage
## [01.40.00] --- 2026-06-23
## [01.40.00] --- 2026-06-23 ## [01.43.00] --- 2026-06-24
## [01.39.01] --- 2026-06-23 ## [01.42.00] --- 2026-06-23
## [01.39.01] --- 2026-06-23
### Added ## [01.42.00] --- 2026-06-23
- MokoRestore: post-restore reset options — passwords, hits, versions, sessions, cache (#131)
- MokoRestore: per-table conflict resolution — replace, skip, merge, data-only per table (#132) ## [01.41.00] — 2026-06-23
- MokoRestore: preset buttons — "All Replace", "All Skip", "Everything except users"
- MokoRestore: auto-detect sanitized passwords and prompt for reset ### Added — Multi-Remote Storage
- Data sanitization: passwords, emails, sessions in backup profile settings (#129) - New `#__mokosuitebackup_remotes` table for multiple destinations per profile
- Manual purge: delete all backups older than a selected date with count preview (#119) - Remote destinations UI: AJAX-driven add/edit/delete/toggle modal on profile edit
- CPanel admin dashboard module with backup status, quick actions, and profile buttons (#105) - Engine uploads to ALL enabled destinations (BackupEngine + SteppedBackupEngine)
- 7z archive format via system 7za/7z binary with optional password encryption (#122) - Migration auto-converts existing SFTP/S3/GDrive/FTP profile columns to new table
- SFTP remote file browser: browse remote server directories to select backup path (#98) - Backward compatibility: falls back to legacy single-remote columns if table empty
- Secrets masked in API responses, merged from DB on save
### Added — Content Snapshots
- Lightweight JSON snapshots of articles, categories, and modules
- Includes tags, custom fields, workflow associations, field values
- Restore modes: Replace (clean slate), Merge (upsert), Selective (per-article)
- Snapshot retention: max count + max age with automatic cleanup
- Scheduled snapshot task via com_scheduler
- CLI: `mokosuitebackup:snapshot create|restore|list|delete`
- REST API: create, list, restore, delete, download snapshots
- Tabbed browse modal: Articles / Categories / Modules with item counts
### Added — SFTP Remote Storage
- SFTP support with SSH key file authentication (key stored base64 in database)
- Auth type dropdown: Password / Key File / Key File + Passphrase
- SshKeyField: file upload via FileReader, key never exposed in HTML
- SFTP remote directory browser for path selection
- `__KEEP_EXISTING__` sentinel preserves key on profile re-save
### Added — MokoRestore Wizard (9 steps)
- Per-table conflict resolution: Replace / Skip / Merge / Data Only
- Preset buttons: "All Replace", "All Skip", "Everything except users"
- Post-restore actions: reset passwords, hits, versions, sessions, cache
- Auto-detect sanitized passwords and prompt for reset (random temp password)
- Standalone mode: restore.php scans directory for ZIP files
- Wrapped mode: restore.php bundled inside backup ZIP
- Security gate with filesystem verification + path traversal protection
### Added — Data Sanitization
- Sanitize user passwords: replace hashes with invalid sentinel
- Sanitize user emails: replace with dummy values
- Clear session data: exclude `#__session` table
- Preserve super admin credentials (optional)
- GDPR-friendly backup sharing for demos and staging sites
### Added — Backup Engine
- Pre-flight validation: directory, disk space, extensions, credentials, running backups
- Auto-verify archive integrity after creation (ZIP, tar.gz, 7z)
- 7z archive format via system 7za/7z CLI binary with native encryption
- Streaming database dump to temp file (prevents OOM on large sites)
- S3 streaming upload via CURLOPT_PUT (prevents OOM)
- Graceful remote degradation: local backup preserved if upload fails
- DatabaseDumper::dumpToFile() for memory-efficient operation
### Added — Admin UI
- Dashboard: snapshot widget, 30-day backup trend chart, per-profile storage breakdown
- CPanel admin dashboard module (mod_mokosuitebackup_cpanel) with quick actions
- Backup type filter dropdown in backups list
- Backup comparison: select two backups for side-by-side diff
- Archive browser: view files inside backup without extracting
- Manual purge: delete backups older than a date with count preview
- Run Backup button on profile list and edit views with backup count badges
- "Do not navigate away" warning in backup/restore progress modals
- Clickable placeholder pills for backup directory and archive name fields
- Comprehensive help modal with absolute/relative/placeholder path documentation
- Placeholder resolution display with EXAMPLE prefix
- All placeholders UPPERCASE: [HOST], [SITE_NAME], [DATE], [DATETIME], etc.
### Added — CLI & API
- `mokosuitebackup:restore` with --files-only, --db-only, --password options
- `mokosuitebackup:snapshot` with create, restore, list, delete actions
- REST API for snapshots: create, list, restore, delete, download
- Profile credentials masked in API responses
### Added — Notifications & Logging
- Email/ntfy notifications for site restore, snapshot create/restore
- Joomla Action Logs for restore, snapshot, and snapshot restore events
- Global ntfy server/topic/token settings (fallback for profiles)
### Added — Security & Configuration
- Webcron secret field with CSPRNG generator + strength meter
- IP whitelist field with current IP detection + one-click "Add my IP"
- 10 ACL permissions with full enforcement audit across all controllers
- Config defaults: archive format, MokoRestore mode, sanitization settings
- Path traversal protection on all archive extraction (ZIP, tar.gz, JPA)
### Fixed ### Fixed
- MokoRestore: data-only mode now uses REPLACE INTO to handle existing rows - CLI RestoreCommand passed wrong arguments (filepath instead of record ID)
- MokoRestore: temporary password is now randomly generated (not hardcoded "changeme") - JPA path traversal: reject `../` in archive entry paths
- S3Uploader OOM: streaming upload instead of file_get_contents
- DatabaseDumper OOM: streaming to file instead of in-memory string
- AkeebaImporter: removed unserialize() (PHP object injection risk)
- BackupTable: delete DB row before file (prevents data loss)
- RestoreEngine: staging path sanitized with preg_replace
- API profiles: sensitive fields masked with `***`
- Webcron: missing return after sendJsonResponse on auth failure
- loadFormData(): cast array to object (PHP 8.x TypeError fix)
- MokoRestore data-only mode: uses REPLACE INTO for existing rows
- Plaintext archive deleted on encryption failure
- TarGzArchiver: intermediate .tar cleaned in finally block
- Install script: single-line comments converted to block comments
- Orphaned root-level webservices plugin files removed
- include_mokorestore column: TINYINT changed to VARCHAR(20)
- Snapshot fields_values: scoped dump and restore to com_content.article (previously destroyed values for contacts, users, etc.)
- Run Backup button: accept CSRF token from GET (fixes "token did not match" on profile edit)
- SFTP fields: moved into remote fieldset for showon visibility; removed required attr that blocked non-SFTP saves
- Script.php merge conflict markers resolved
## [01.24.00] — 2026-06-02
### Added
- Initial release: full-site backup and restore for Joomla 6
- Database, files, and configuration backup
- ZIP and tar.gz archive formats with AES-256 encryption
- Differential backups based on file manifests
- FTP/FTPS, S3, Google Drive remote storage
- MokoRestore standalone restore wizard
- CLI backup and restore commands
- REST API for remote management
- Scheduled tasks via com_scheduler
- Email and ntfy push notifications
- Per-profile retention, exclusions, and notifications
- Akeeba Backup migration tool
- Admin dashboard with system health checks
@@ -127,15 +127,15 @@ COM_MOKOJOOMBACKUP_COMPRESSION_FASTEST="Low (fast)"
COM_MOKOJOOMBACKUP_COMPRESSION_NORMAL="Normal (balanced)" COM_MOKOJOOMBACKUP_COMPRESSION_NORMAL="Normal (balanced)"
COM_MOKOJOOMBACKUP_COMPRESSION_BEST="Maximum (smallest)" COM_MOKOJOOMBACKUP_COMPRESSION_BEST="Maximum (smallest)"
COM_MOKOJOOMBACKUP_FIELD_ENCRYPTION_PASSWORD="Encryption Password" COM_MOKOJOOMBACKUP_FIELD_ENCRYPTION_PASSWORD="Encryption Password"
COM_MOKOJOOMBACKUP_FIELD_ENCRYPTION_PASSWORD_DESC="Set a password to encrypt the backup archive with AES-256. Leave blank for no encryption. Required to restore encrypted backups." COM_MOKOJOOMBACKUP_FIELD_ENCRYPTION_PASSWORD_DESC="AES-256 encryption password. Leave blank for no encryption. Required to restore."
COM_MOKOJOOMBACKUP_FIELD_SPLIT_SIZE="Split Size (MB)" COM_MOKOJOOMBACKUP_FIELD_SPLIT_SIZE="Split Size (MB)"
COM_MOKOJOOMBACKUP_FIELD_SPLIT_SIZE_DESC="Split archive into parts of this size in MB. 0 = no splitting." COM_MOKOJOOMBACKUP_FIELD_SPLIT_SIZE_DESC="Split archive into parts of this size in MB. 0 = no splitting."
COM_MOKOJOOMBACKUP_FIELD_BACKUP_DIR="Backup Directory" COM_MOKOJOOMBACKUP_FIELD_BACKUP_DIR="Backup Directory"
COM_MOKOJOOMBACKUP_FIELD_BACKUP_DIR_DESC="Directory where backup archives are stored. Supports placeholders: [HOME] (user home directory), [HOST], [DATE], [YEAR], [MONTH], [DAY], [PROFILE_NAME], [SITE_NAME], [TYPE]. Use [HOME]/backups to store outside the web root. Absolute paths (starting with /) are used as-is; relative paths resolve from the Joomla root." COM_MOKOJOOMBACKUP_FIELD_BACKUP_DIR_DESC="Where backups are stored. Use placeholders like [HOME]/backups for portability. Click the ? icon for full documentation."
COM_MOKOJOOMBACKUP_FIELD_ARCHIVE_NAME_FORMAT="Archive Name Format" COM_MOKOJOOMBACKUP_FIELD_ARCHIVE_NAME_FORMAT="Archive Name Format"
COM_MOKOJOOMBACKUP_FIELD_ARCHIVE_NAME_FORMAT_DESC="Filename template for backup archives (without extension). Placeholders: [HOST] hostname, [DATE] Ymd, [TIME] His, [DATETIME] Ymd_His, [YEAR] [MONTH] [DAY] [HOUR] [MINUTE] [SECOND], [PROFILE_ID], [PROFILE_NAME], [SITE_NAME], [TYPE], [RANDOM]." COM_MOKOJOOMBACKUP_FIELD_ARCHIVE_NAME_FORMAT_DESC="Filename template (without extension). Click the placeholder buttons below to insert tokens."
COM_MOKOJOOMBACKUP_FIELD_INCLUDE_MOKORESTORE="MokoRestore Script" COM_MOKOJOOMBACKUP_FIELD_INCLUDE_MOKORESTORE="MokoRestore Script"
COM_MOKOJOOMBACKUP_FIELD_INCLUDE_MOKORESTORE_DESC="Include the MokoRestore standalone restore wizard. 'Wrapped' bundles it inside the backup ZIP. 'Standalone' generates a separate restore.php that scans for backup ZIPs in its directory — ideal for remote servers." COM_MOKOJOOMBACKUP_FIELD_INCLUDE_MOKORESTORE_DESC="None: no restore script. Wrapped: bundled inside the ZIP. Standalone: separate restore.php file (ideal for remote servers)."
COM_MOKOJOOMBACKUP_MOKORESTORE_NONE="None" COM_MOKOJOOMBACKUP_MOKORESTORE_NONE="None"
COM_MOKOJOOMBACKUP_MOKORESTORE_WRAPPED="Wrapped (inside backup ZIP)" COM_MOKOJOOMBACKUP_MOKORESTORE_WRAPPED="Wrapped (inside backup ZIP)"
COM_MOKOJOOMBACKUP_MOKORESTORE_STANDALONE="Standalone (separate restore.php)" COM_MOKOJOOMBACKUP_MOKORESTORE_STANDALONE="Standalone (separate restore.php)"
@@ -143,13 +143,13 @@ COM_MOKOJOOMBACKUP_MOKORESTORE_STANDALONE="Standalone (separate restore.php)"
; Data Sanitization ; Data Sanitization
COM_MOKOJOOMBACKUP_FIELDSET_SANITIZATION="Data Sanitization" COM_MOKOJOOMBACKUP_FIELDSET_SANITIZATION="Data Sanitization"
COM_MOKOJOOMBACKUP_FIELD_SANITIZE_PASSWORDS="Sanitize User Passwords" COM_MOKOJOOMBACKUP_FIELD_SANITIZE_PASSWORDS="Sanitize User Passwords"
COM_MOKOJOOMBACKUP_FIELD_SANITIZE_PASSWORDS_DESC="Replace all user password hashes with an invalid value. Users will not be able to log in with the restored backup without resetting their password. Ideal for sharing backups, creating demo/staging sites, or GDPR compliance." COM_MOKOJOOMBACKUP_FIELD_SANITIZE_PASSWORDS_DESC="Replace password hashes with invalid values. Users must reset passwords after restore. For demos, staging, or GDPR."
COM_MOKOJOOMBACKUP_FIELD_PRESERVE_SUPER_ADMIN="Preserve Super Admin Password" COM_MOKOJOOMBACKUP_FIELD_PRESERVE_SUPER_ADMIN="Preserve Super Admin Password"
COM_MOKOJOOMBACKUP_FIELD_PRESERVE_SUPER_ADMIN_DESC="Keep the password for Super Users (group ID 8) intact. You will still be able to log in as a Super Admin after restoring." COM_MOKOJOOMBACKUP_FIELD_PRESERVE_SUPER_ADMIN_DESC="Keep the password for Super Users (group ID 8) intact. You will still be able to log in as a Super Admin after restoring."
COM_MOKOJOOMBACKUP_FIELD_SANITIZE_EMAILS="Sanitize User Emails" COM_MOKOJOOMBACKUP_FIELD_SANITIZE_EMAILS="Sanitize User Emails"
COM_MOKOJOOMBACKUP_FIELD_SANITIZE_EMAILS_DESC="Replace all user email addresses with dummy values (user123@sanitized.example.com). Prevents accidental emails being sent to real users from a cloned/staging site. Super admin emails are preserved if 'Preserve Super Admin' is enabled." COM_MOKOJOOMBACKUP_FIELD_SANITIZE_EMAILS_DESC="Replace emails with dummy values. Prevents accidental emails from cloned sites. Super admin preserved if enabled above."
COM_MOKOJOOMBACKUP_FIELD_SANITIZE_SESSIONS="Clear Session Data" COM_MOKOJOOMBACKUP_FIELD_SANITIZE_SESSIONS="Clear Session Data"
COM_MOKOJOOMBACKUP_FIELD_SANITIZE_SESSIONS_DESC="Exclude active session data from the backup. This logs out all users and prevents session hijacking when the backup is restored on another server. Enabled by default." COM_MOKOJOOMBACKUP_FIELD_SANITIZE_SESSIONS_DESC="Exclude session data. Logs out all users on restore, prevents session hijacking. Enabled by default."
; Exclusion filter fields ; Exclusion filter fields
COM_MOKOJOOMBACKUP_FIELD_EXCLUDE_DIRS="Exclude Directories" COM_MOKOJOOMBACKUP_FIELD_EXCLUDE_DIRS="Exclude Directories"
@@ -277,7 +277,7 @@ COM_MOKOJOOMBACKUP_FIELD_SFTP_USERNAME_DESC="Username for SSH authentication"
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD="SSH Password" COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD="SSH Password"
COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD_DESC="Password for SSH authentication. Leave blank if using a key file." COM_MOKOJOOMBACKUP_FIELD_SFTP_PASSWORD_DESC="Password for SSH authentication. Leave blank if using a key file."
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY="SSH Private Key" COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY="SSH Private Key"
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Upload or paste your SSH private key (e.g. id_rsa or id_ed25519). The key is stored securely in the database and written to a temp file with 0600 permissions only during upload, then deleted. Leave blank to use password authentication." COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_DESC="Upload your SSH private key (id_rsa, id_ed25519). Stored base64-encoded in DB, written to temp file during upload only. Leave blank for password auth."
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_UPLOAD="Upload Key File" COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_UPLOAD="Upload Key File"
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_REPLACE="Replace Key" COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_REPLACE="Replace Key"
COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED="Key loaded" COM_MOKOJOOMBACKUP_FIELD_SFTP_KEY_LOADED="Key loaded"
@@ -7,7 +7,7 @@
--> -->
<extension type="component" method="upgrade"> <extension type="component" method="upgrade">
<name>MokoSuiteBackup</name> <name>MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -51,7 +51,32 @@ class PlaceholderResolver
public function __construct(object $profile) public function __construct(object $profile)
{ {
$now = new \DateTimeImmutable('now'); $now = new \DateTimeImmutable('now');
$hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? php_uname('n'));
/* Resolve hostname: prefer HTTP_HOST (web), then try Joomla config (CLI), then system hostname */
$rawHost = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '';
if (empty($rawHost) || $rawHost === 'localhost') {
try {
$app = Factory::getApplication();
$liveSite = $app->get('live_site', '');
if (!empty($liveSite)) {
$parsed = parse_url($liveSite, PHP_URL_HOST);
if (!empty($parsed)) {
$rawHost = $parsed;
}
}
} catch (\Throwable $e) {
/* fallback */
}
}
if (empty($rawHost)) {
$rawHost = php_uname('n');
}
$hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $rawHost);
$siteName = ''; $siteName = '';
@@ -38,7 +38,30 @@ class FolderPickerField extends FormField
} }
// Build placeholder map for JS resolution // Build placeholder map for JS resolution
$hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? php_uname('n')); /* Resolve hostname: prefer HTTP_HOST, then Joomla live_site config, then system hostname */
$rawHost = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '';
if (empty($rawHost) || $rawHost === 'localhost') {
try {
$liveSite = Factory::getApplication()->get('live_site', '');
if (!empty($liveSite)) {
$parsed = parse_url($liveSite, PHP_URL_HOST);
if (!empty($parsed)) {
$rawHost = $parsed;
}
}
} catch (\Throwable $e) {
/* fallback */
}
}
if (empty($rawHost)) {
$rawHost = php_uname('n');
}
$hostname = preg_replace('/[^a-zA-Z0-9._-]/', '', $rawHost);
$siteName = ''; $siteName = '';
try { try {
@@ -0,0 +1,11 @@
<?php
/**
* @package MokoSuiteBackup
* @subpackage mod_mokosuitebackup_cpanel
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
@@ -8,7 +8,7 @@
--> -->
<extension type="module" client="administrator" method="upgrade"> <extension type="module" client="administrator" method="upgrade">
<name>mod_mokosuitebackup_cpanel</name> <name>mod_mokosuitebackup_cpanel</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-23</creationDate> <creationDate>2026-06-23</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -20,6 +20,7 @@
<namespace path="src">Joomla\Module\MokoSuiteBackupCpanel</namespace> <namespace path="src">Joomla\Module\MokoSuiteBackupCpanel</namespace>
<files> <files>
<filename module="mod_mokosuitebackup_cpanel">mod_mokosuitebackup_cpanel.php</filename>
<folder>language</folder> <folder>language</folder>
<folder>services</folder> <folder>services</folder>
<folder>src</folder> <folder>src</folder>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="actionlog" method="upgrade"> <extension type="plugin" group="actionlog" method="upgrade">
<name>Action Log - MokoSuiteBackup</name> <name>Action Log - MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="console" method="upgrade"> <extension type="plugin" group="console" method="upgrade">
<name>Console - MokoSuiteBackup</name> <name>Console - MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="content" method="upgrade"> <extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteBackup</name> <name>Content - MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-04</creationDate> <creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade"> <extension type="plugin" group="quickicon" method="upgrade">
<name>Quick Icon - MokoSuiteBackup</name> <name>Quick Icon - MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="system" method="upgrade"> <extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name> <name>System - MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="task" method="upgrade"> <extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name> <name>Task - MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
--> -->
<extension type="plugin" group="webservices" method="upgrade"> <extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name> <name>Web Services - MokoSuiteBackup</name>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade"> <extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name> <name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename> <packagename>mokosuitebackup</packagename>
<version>01.41.00</version> <version>01.43.00</version>
<creationDate>2026-06-02</creationDate> <creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>