Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bec7b70ff5 | |||
| 92b4cd61c2 | |||
| dc2647977c | |||
| 685d89acf9 | |||
| 8ceddefbdb | |||
| ea2666948e | |||
| 3aabd1b1f9 | |||
| 0328258529 | |||
| e6ff9a99f9 | |||
| 4138ab7d47 | |||
| d75e648970 | |||
| 06a382e82e | |||
| 0ff4b12f27 | |||
| 53f0472e4f | |||
| 021ddbb17a | |||
| 12f78e8feb | |||
| b72d419fb1 | |||
| 865e8b9bfa | |||
| 3a5ca580db | |||
| 93f186bd1d | |||
| 56988e810e | |||
| f8b7af30d9 | |||
| df06e11704 | |||
| 51517a5275 | |||
| 336338b541 | |||
| 8f537df6c5 | |||
| 7ac8c3d0a1 | |||
| 3465b4fa01 | |||
| 0b4e7575eb | |||
| 508185f7ad | |||
| c8ba0647d3 | |||
| d55da332cf | |||
| a04e237f17 | |||
| e7cc4c120f | |||
| aa54f3834e | |||
| 4d93f23037 | |||
| d7e2ffd02b | |||
| b9d81ca5c5 | |||
| 59c62dc687 | |||
| b14ffa083e | |||
| 2cc57bbbbc | |||
| 3cd7687c06 | |||
| c3b2643b0c | |||
| 0159e567e2 | |||
| f194b204b4 | |||
| f118f084ce | |||
| 2821c35326 | |||
| 5b02cf188e | |||
| 689173ecab | |||
| b2fe44fbc3 | |||
| 0e89ef9944 | |||
| 522dadecf0 | |||
| f1b9bb2f3d |
@@ -4,7 +4,7 @@
|
||||
<name>MokoGitea</name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>Moko fork of Gitea — adding project board REST API endpoints and custom enhancements</description>
|
||||
<version>05.10.00</version>
|
||||
<version>05.14.00</version>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# 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
|
||||
- rc
|
||||
- 'feature/**'
|
||||
- 'patch/**'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
if [ -d "/opt/moko-platform/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
php ${MOKO_CLI}/version_auto_bump.php \
|
||||
--path . --branch "${GITHUB_REF_NAME}" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
@@ -1,270 +0,0 @@
|
||||
# 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-tech/moko-platform
|
||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||
#
|
||||
# +========================================================================+
|
||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||
# | |
|
||||
# | Platform-specific: |
|
||||
# | joomla: XML manifest, updates.xml, type-prefixed packages |
|
||||
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||
# | generic: README-only, no update stream |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: "Universal: Build & Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, closed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'Action to perform'
|
||||
required: false
|
||||
type: choice
|
||||
default: release
|
||||
options:
|
||||
- release
|
||||
- promote-rc
|
||||
|
||||
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:
|
||||
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
|
||||
promote-rc:
|
||||
name: Promote to RC
|
||||
runs-on: release
|
||||
if: >-
|
||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
- name: Rename branch to rc
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/branch_rename.php \
|
||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||
--pr "${{ github.event.pull_request.number }}"
|
||||
|
||||
- name: Checkout rc and configure git
|
||||
run: |
|
||||
git fetch origin rc
|
||||
git checkout rc
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Publish RC release
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||
--path . --stability rc --bump minor --branch rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Branch renamed to rc, minor bump, RC + lesser stream releases built, updates.xml synced" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
||||
release:
|
||||
name: Build & Release Pipeline
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git for bot pushes
|
||||
run: |
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||
run: |
|
||||
# Ensure PHP + Composer are available
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
|
||||
- name: "Publish stable release"
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||
--path . --stability stable --bump minor --branch main \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||
- name: "Step 9: Mirror release to GitHub"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php /tmp/moko-platform-api/cli/release_mirror.php \
|
||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||
--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"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||
git fetch origin main --depth=1
|
||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||
&& 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'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
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}" \
|
||||
"${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}" \
|
||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||
|
||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/branches" \
|
||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||
|
||||
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Step 12: Create version branch from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
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}"
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
# -- Dolibarr post-release: Reset dev version -----------------------------
|
||||
- name: "Post-release: Reset dev version"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php /tmp/moko-platform-api/cli/version_reset_dev.php \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||
--branch dev --path . 2>&1 || true
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -1,10 +0,0 @@
|
||||
# DISABLED — auto-release Step 11 recreates dev from main after every release.
|
||||
# Cascade-dev is redundant and causes version conflicts when both main and dev
|
||||
# have different version numbers in templateDetails.xml / manifest.xml.
|
||||
name: "Cascade Main → Dev (DISABLED)"
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
noop:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "Cascade disabled — auto-release handles dev recreation"
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# VERSION: 05.10.00
|
||||
# VERSION: 05.14.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Pre-release channel'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- release-candidate
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
STABILITY="${{ inputs.stability || 'development' }}"
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Read current version (bump already handled by push workflow)
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||
|
||||
# Strip any existing suffix from version before applying stability
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||
|
||||
# Verify version consistency across all files
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Update VERSION variable with suffix
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
# Commit version bump
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Auto-detect element via manifest_element.php
|
||||
php ${MOKO_CLI}/manifest_element.php \
|
||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||
--repo "${GITEA_REPO}" --github-output
|
||||
|
||||
# Read back element outputs
|
||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||
fi
|
||||
|
||||
- name: "Sync updates.xml to all branches"
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
|
||||
for BRANCH in main dev; do
|
||||
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||
echo "Syncing updates.xml -> ${BRANCH}"
|
||||
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||
git checkout "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue
|
||||
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git add updates.xml
|
||||
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
|
||||
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
|
||||
fi
|
||||
git checkout "${CURRENT_BRANCH}" 2>/dev/null
|
||||
done
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,312 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/update-server.yml
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||
#
|
||||
# Thin wrapper around moko-platform CLI tools.
|
||||
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||
#
|
||||
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||
|
||||
name: "Update Server"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Stability tag'
|
||||
required: true
|
||||
default: 'development'
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- rc
|
||||
- stable
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-xml:
|
||||
name: Update Server
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform 2>/dev/null || true
|
||||
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
||||
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve stability and bump version
|
||||
id: meta
|
||||
run: |
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
|
||||
# Configure git for bot pushes
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
# Auto-bump patch version
|
||||
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
|
||||
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||
|
||||
# Strip any existing suffix before applying stability
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
# Determine stability from branch or manual input
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
elif [[ "$BRANCH" == rc/* ]]; then
|
||||
STABILITY="rc"
|
||||
elif [[ "$BRANCH" == beta/* ]]; then
|
||||
STABILITY="beta"
|
||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||
STABILITY="alpha"
|
||||
else
|
||||
STABILITY="development"
|
||||
fi
|
||||
|
||||
# Version suffix per stability stream
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
*) SUFFIX=""; TAG="stable" ;;
|
||||
esac
|
||||
|
||||
# Propagate version with stability suffix to all manifest files
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Re-read version (now includes suffix from version_set_platform)
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Commit version bump if changed
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Create release and upload package
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Create or update Gitea release
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||
|
||||
# Build package and upload
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push updates.xml
|
||||
git add updates.xml
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Sync updates.xml to main
|
||||
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||
python3 -c "
|
||||
import base64, json, urllib.request, sys
|
||||
with open('updates.xml', 'rb') as f:
|
||||
content = base64.b64encode(f.read()).decode()
|
||||
payload = json.dumps({
|
||||
'content': content,
|
||||
'sha': '${FILE_SHA}',
|
||||
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
|
||||
'branch': 'main'
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/contents/updates.xml',
|
||||
data=payload, method='PUT',
|
||||
headers={
|
||||
'Authorization': 'token ${GITEA_TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
try:
|
||||
urllib.request.urlopen(req)
|
||||
print('updates.xml synced to main')
|
||||
except Exception as e:
|
||||
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||
"
|
||||
fi
|
||||
|
||||
- name: SFTP deploy to dev server
|
||||
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||
env:
|
||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
run: |
|
||||
# Permission check: admin or maintain role required
|
||||
ACTOR="${{ github.actor }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||
case "$PERMISSION" in
|
||||
admin|maintain|write) ;;
|
||||
*)
|
||||
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||
|
||||
PORT="${DEV_PORT:-22}"
|
||||
REMOTE="${DEV_PATH%/}"
|
||||
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||
if [ -n "$DEV_KEY" ]; then
|
||||
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
fi
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
DISPLAY="${{ steps.meta.outputs.display_version }}"
|
||||
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package licenses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||
)
|
||||
|
||||
const (
|
||||
MasterPackageName = "Master (Internal)"
|
||||
MasterPackageDesc = "Auto-created master package with unlimited access to all channels."
|
||||
)
|
||||
|
||||
// EnsureMasterKey ensures that a master license package and key exist for the given owner.
|
||||
// Returns the master key's raw key string only if it was just created (empty string otherwise).
|
||||
func EnsureMasterKey(ctx context.Context, ownerID int64) (rawKey string, err error) {
|
||||
// Check if a master package already exists.
|
||||
pkgs, err := ListLicensePackages(ctx, ownerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var masterPkg *LicensePackage
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Name == MasterPackageName {
|
||||
masterPkg = pkg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Create master package if it doesn't exist.
|
||||
if masterPkg == nil {
|
||||
masterPkg = &LicensePackage{
|
||||
OwnerID: ownerID,
|
||||
Name: MasterPackageName,
|
||||
Description: MasterPackageDesc,
|
||||
DurationDays: 0, // lifetime
|
||||
MaxSites: 0, // unlimited
|
||||
RepoScope: "all",
|
||||
IsActive: true,
|
||||
}
|
||||
if err := CreateLicensePackage(ctx, masterPkg); err != nil {
|
||||
return "", fmt.Errorf("CreateLicensePackage: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a master key already exists for this package.
|
||||
keys, err := ListLicenseKeysByPackage(ctx, masterPkg.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
if key.IsInternal {
|
||||
return "", nil // already exists, don't return raw key
|
||||
}
|
||||
}
|
||||
|
||||
// Create the master key.
|
||||
masterKey := &LicenseKey{
|
||||
PackageID: masterPkg.ID,
|
||||
OwnerID: ownerID,
|
||||
IsInternal: true,
|
||||
IsActive: true,
|
||||
}
|
||||
rawKey, err = CreateLicenseKey(ctx, masterKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("CreateLicenseKey: %w", err)
|
||||
}
|
||||
|
||||
return rawKey, nil
|
||||
}
|
||||
|
||||
// GetMasterKey returns the master key for an owner, if it exists.
|
||||
func GetMasterKey(ctx context.Context, ownerID int64) (*LicenseKey, error) {
|
||||
key := new(LicenseKey)
|
||||
has, err := db.GetEngine(ctx).Where("owner_id = ? AND is_internal = ?", ownerID, true).Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
@@ -24,6 +24,8 @@ type UpdateStreamConfig struct {
|
||||
OwnerID int64 `xorm:"INDEX NOT NULL"` // org or user
|
||||
RepoID int64 `xorm:"INDEX NOT NULL DEFAULT 0"` // 0 = org-level default
|
||||
StreamMode string `xorm:"NOT NULL DEFAULT 'joomla'"` // joomla, custom
|
||||
Platform string `xorm:"NOT NULL DEFAULT 'joomla'"` // joomla, dolibarr, both
|
||||
RequireKey bool `xorm:"NOT NULL DEFAULT false"` // require license key for update feed
|
||||
// CustomStreams is a JSON array of stream definitions.
|
||||
// Each entry: {"name":"lts","suffix":"-lts","description":"Long-term support"}
|
||||
CustomStreams string `xorm:"TEXT"`
|
||||
|
||||
@@ -414,6 +414,8 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(334, "Add actions user whitelist to protected branches", v1_27.AddActionsUserWhitelistToProtectedBranch),
|
||||
newMigration(335, "Add license key tables for update server", v1_27.AddLicenseKeyTables),
|
||||
newMigration(336, "Add update stream config table", v1_27.AddUpdateStreamConfigTable),
|
||||
newMigration(337, "Add key_plain column to license_key", v1_27.AddKeyPlainToLicenseKey),
|
||||
newMigration(338, "Add platform and require_key to update_stream_config", v1_27.AddPlatformAndRequireKeyToStreamConfig),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package v1_27
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
type licenseKey337 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
KeyPlain string `xorm:""`
|
||||
}
|
||||
|
||||
func (licenseKey337) TableName() string {
|
||||
return "license_key"
|
||||
}
|
||||
|
||||
// AddKeyPlainToLicenseKey adds the key_plain column to license_key table.
|
||||
func AddKeyPlainToLicenseKey(x *xorm.Engine) error {
|
||||
return x.Sync(new(licenseKey337))
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package v1_27
|
||||
|
||||
import "xorm.io/xorm"
|
||||
|
||||
type updateStreamConfig338 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Platform string `xorm:"NOT NULL DEFAULT 'joomla'"`
|
||||
RequireKey bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
func (updateStreamConfig338) TableName() string {
|
||||
return "update_stream_config"
|
||||
}
|
||||
|
||||
// AddPlatformAndRequireKeyToStreamConfig adds platform and require_key
|
||||
// columns to update_stream_config.
|
||||
func AddPlatformAndRequireKeyToStreamConfig(x *xorm.Engine) error {
|
||||
return x.Sync(new(updateStreamConfig338))
|
||||
}
|
||||
@@ -2147,7 +2147,10 @@
|
||||
"repo.settings.unit_visibility": "Visibility",
|
||||
"repo.settings.unit_visibility_private": "Private (follow repo visibility)",
|
||||
"repo.settings.unit_visibility_public": "Public (anyone can read)",
|
||||
"repo.settings.unit_visibility_releases_help": "Update feeds (updates.xml, dolibarr.json) are always accessible regardless of this setting. Set to Public to also show the releases page to anonymous visitors.",
|
||||
"repo.settings.unit_visibility_releases_help": "Controls whether the releases page is visible to anonymous visitors.",
|
||||
"repo.settings.update_platform": "Update Server Platform",
|
||||
"repo.settings.update_platform_both": "Both (Joomla + Dolibarr)",
|
||||
"repo.settings.require_update_key": "Require license key for update feed access",
|
||||
"repo.settings.packages_desc": "Enable Repository Packages Registry",
|
||||
"repo.settings.projects_desc": "Enable Projects",
|
||||
"repo.settings.projects_mode_desc": "Projects Mode (which kinds of projects to show)",
|
||||
@@ -2636,13 +2639,20 @@
|
||||
"repo.licenses.description": "Description",
|
||||
"repo.licenses.max_sites": "Max Sites",
|
||||
"repo.licenses.channels_help": "Comma-separated channel names (e.g. stable,release-candidate). Leave empty for all channels.",
|
||||
"repo.licenses.create_package": "Create Package",
|
||||
"repo.licenses.create_package": "Create License Package",
|
||||
"repo.licenses.create_new_package": "Create New License Package",
|
||||
"repo.licenses.package_created": "License package created successfully.",
|
||||
"repo.licenses.generate_key": "Generate Key",
|
||||
"repo.licenses.key_created": "License Key Created",
|
||||
"repo.licenses.key_created_copy": "Copy this key now. It will not be shown again.",
|
||||
"repo.licenses.revoke": "Revoke",
|
||||
"repo.licenses.edit_package": "Edit License Package",
|
||||
"repo.licenses.delete_package": "Delete Package",
|
||||
"repo.licenses.package_updated": "License package updated.",
|
||||
"repo.licenses.package_deleted": "License package deleted.",
|
||||
"repo.licenses.key_revoked": "License key revoked.",
|
||||
"repo.licenses.master_key_created": "Master License Key Created",
|
||||
"repo.licenses.master_key_created_copy": "This is your organization master key with unlimited access to all update channels. Copy it now — it will not be shown again.",
|
||||
"repo.licenses.update_feeds": "Update Feed URLs",
|
||||
"repo.release.draft": "Draft",
|
||||
"repo.release.prerelease": "Pre-Release",
|
||||
@@ -2785,6 +2795,18 @@
|
||||
"org.form.create_org_not_allowed": "You are not allowed to create an organization.",
|
||||
"org.settings": "Settings",
|
||||
"org.settings.options": "Organization",
|
||||
"org.settings.update_streams": "Licenses & Update Streams",
|
||||
"org.settings.update_streams_desc": "Configure the default update streams for all repositories in this organization. Repos can override with their own settings.",
|
||||
"org.settings.stream_mode": "Stream Mode",
|
||||
"org.settings.stream_mode_joomla": "Standard Joomla streams (stable, release-candidate, beta, alpha, development)",
|
||||
"org.settings.stream_mode_custom": "Custom streams (define your own channels and tag patterns)",
|
||||
"org.settings.default_streams": "Active Streams",
|
||||
"org.settings.default_streams_joomla": "These are the currently active update streams. Release tags are matched to streams by their suffix.",
|
||||
"org.settings.stream_name": "Stream Name",
|
||||
"org.settings.stream_suffix": "Tag Suffix",
|
||||
"org.settings.custom_streams": "Custom Stream Definitions (JSON)",
|
||||
"org.settings.custom_streams_help": "JSON array of stream objects. Each needs: name, suffix, description. Example: [{\"name\":\"lts\",\"suffix\":\"-lts\",\"description\":\"Long-term support\"}]",
|
||||
"org.settings.update_streams_saved": "Update stream settings saved.",
|
||||
"org.settings.full_name": "Full Name",
|
||||
"org.settings.email": "Contact Email Address",
|
||||
"org.settings.website": "Website",
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||
)
|
||||
|
||||
const tplOrgLicenses templates.TplName = "org/licenses"
|
||||
|
||||
// LicensePackageDisplay is used in templates.
|
||||
type LicensePackageDisplay struct {
|
||||
*licenses.LicensePackage
|
||||
KeyCount int64
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
// Licenses shows the org-level license packages and keys.
|
||||
func Licenses(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.licenses")
|
||||
ctx.Data["IsLicensesPage"] = true
|
||||
|
||||
org := ctx.Org.Organization
|
||||
ownerID := org.ID
|
||||
|
||||
// Auto-create master key if org owner.
|
||||
if ctx.Org.IsOwner {
|
||||
newMasterKey, err := licenses.EnsureMasterKey(ctx, ownerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("EnsureMasterKey", err)
|
||||
return
|
||||
}
|
||||
if newMasterKey != "" {
|
||||
ctx.Data["NewMasterKey"] = newMasterKey
|
||||
}
|
||||
}
|
||||
|
||||
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("ListLicensePackages", err)
|
||||
return
|
||||
}
|
||||
|
||||
var display []LicensePackageDisplay
|
||||
for _, pkg := range pkgs {
|
||||
count, _ := licenses.CountKeysByPackage(ctx, pkg.ID)
|
||||
display = append(display, LicensePackageDisplay{
|
||||
LicensePackage: pkg,
|
||||
KeyCount: count,
|
||||
Created: time.Unix(int64(pkg.CreatedUnix), 0),
|
||||
})
|
||||
}
|
||||
ctx.Data["LicensePackages"] = display
|
||||
|
||||
keys, err := licenses.ListLicenseKeys(ctx, ownerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("ListLicenseKeys", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["LicenseKeys"] = keys
|
||||
ctx.Data["IsRepoAdmin"] = ctx.Org.IsOwner
|
||||
ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin()
|
||||
|
||||
ctx.HTML(http.StatusOK, tplOrgLicenses)
|
||||
}
|
||||
|
||||
// LicensesCreatePackage handles POST to create a new org-level license package.
|
||||
func LicensesCreatePackage(ctx *context.Context) {
|
||||
name := ctx.FormString("name")
|
||||
if name == "" {
|
||||
ctx.Flash.Error("Package name is required")
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||
return
|
||||
}
|
||||
|
||||
durationDays, _ := strconv.Atoi(ctx.FormString("duration_days"))
|
||||
maxSites, _ := strconv.Atoi(ctx.FormString("max_sites"))
|
||||
|
||||
pkg := &licenses.LicensePackage{
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
Name: name,
|
||||
Description: ctx.FormString("description"),
|
||||
DurationDays: durationDays,
|
||||
MaxSites: maxSites,
|
||||
AllowedChannels: ctx.FormString("allowed_channels"),
|
||||
RepoScope: "all",
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
|
||||
ctx.ServerError("CreateLicensePackage", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.package_created"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||
}
|
||||
|
||||
// LicensesGenerateKey handles POST to generate a key from an org package.
|
||||
func LicensesGenerateKey(ctx *context.Context) {
|
||||
packageID, _ := strconv.ParseInt(ctx.FormString("package_id"), 10, 64)
|
||||
if packageID == 0 {
|
||||
ctx.Flash.Error("Invalid package")
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||
return
|
||||
}
|
||||
|
||||
pkg, err := licenses.GetLicensePackageByID(ctx, packageID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLicensePackageByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
key := &licenses.LicenseKey{
|
||||
PackageID: packageID,
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if pkg.DurationDays > 0 {
|
||||
expires := time.Now().AddDate(0, 0, pkg.DurationDays)
|
||||
key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
|
||||
}
|
||||
|
||||
rawKey, err := licenses.CreateLicenseKey(ctx, key)
|
||||
if err != nil {
|
||||
ctx.ServerError("CreateLicenseKey", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Re-render with the new key shown.
|
||||
ctx.Data["Title"] = ctx.Tr("repo.licenses")
|
||||
ctx.Data["IsLicensesPage"] = true
|
||||
ctx.Data["IsRepoAdmin"] = ctx.Org.IsOwner
|
||||
ctx.Data["NewKeyCreated"] = rawKey
|
||||
|
||||
ownerID := ctx.Org.Organization.ID
|
||||
pkgs, _ := licenses.ListLicensePackages(ctx, ownerID)
|
||||
var display []LicensePackageDisplay
|
||||
for _, p := range pkgs {
|
||||
count, _ := licenses.CountKeysByPackage(ctx, p.ID)
|
||||
display = append(display, LicensePackageDisplay{
|
||||
LicensePackage: p,
|
||||
KeyCount: count,
|
||||
Created: time.Unix(int64(p.CreatedUnix), 0),
|
||||
})
|
||||
}
|
||||
ctx.Data["LicensePackages"] = display
|
||||
keys, _ := licenses.ListLicenseKeys(ctx, ownerID)
|
||||
ctx.Data["LicenseKeys"] = keys
|
||||
|
||||
ctx.HTML(http.StatusOK, tplOrgLicenses)
|
||||
}
|
||||
|
||||
// LicensesRevokeKey handles POST to revoke an org license key.
|
||||
func LicensesRevokeKey(ctx *context.Context) {
|
||||
keyID := ctx.PathParamInt64("id")
|
||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLicenseKeyByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
key.IsActive = false
|
||||
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
|
||||
ctx.ServerError("UpdateLicenseKey", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.key_revoked"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||
)
|
||||
|
||||
const tplSettingsUpdateStreams templates.TplName = "org/settings/update_streams"
|
||||
|
||||
// SettingsUpdateStreams shows the org-level update stream settings.
|
||||
func SettingsUpdateStreams(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("org.settings.update_streams")
|
||||
ctx.Data["PageIsOrgSettings"] = true
|
||||
ctx.Data["PageIsSettingsUpdateStreams"] = true
|
||||
|
||||
orgID := ctx.Org.Organization.ID
|
||||
|
||||
cfg, err := licenses.GetOrgConfig(ctx, orgID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOrgConfig", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["StreamConfig"] = cfg
|
||||
ctx.Data["EffectiveStreams"] = cfg.GetActiveStreams()
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSettingsUpdateStreams)
|
||||
}
|
||||
|
||||
// SettingsUpdateStreamsPost saves the org-level update stream settings.
|
||||
func SettingsUpdateStreamsPost(ctx *context.Context) {
|
||||
orgID := ctx.Org.Organization.ID
|
||||
|
||||
cfg := &licenses.UpdateStreamConfig{
|
||||
OwnerID: orgID,
|
||||
RepoID: 0,
|
||||
StreamMode: ctx.FormString("stream_mode"),
|
||||
CustomStreams: ctx.FormString("custom_streams"),
|
||||
}
|
||||
|
||||
if cfg.StreamMode == "" {
|
||||
cfg.StreamMode = "joomla"
|
||||
}
|
||||
|
||||
if err := licenses.SaveConfig(ctx, cfg); err != nil {
|
||||
ctx.ServerError("SaveConfig", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("org.settings.update_streams_saved"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/update-streams")
|
||||
}
|
||||
@@ -32,6 +32,18 @@ func Licenses(ctx *context.Context) {
|
||||
|
||||
ownerID := ctx.Repo.Repository.OwnerID
|
||||
|
||||
// Auto-create master package + key if admin and none exist.
|
||||
if ctx.Repo.Permission.IsAdmin() {
|
||||
newMasterKey, err := licenses.EnsureMasterKey(ctx, ownerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("EnsureMasterKey", err)
|
||||
return
|
||||
}
|
||||
if newMasterKey != "" {
|
||||
ctx.Data["NewMasterKey"] = newMasterKey
|
||||
}
|
||||
}
|
||||
|
||||
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
|
||||
if err != nil {
|
||||
ctx.ServerError("ListLicensePackages", err)
|
||||
@@ -167,3 +179,64 @@ func LicensesRevokeKey(ctx *context.Context) {
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.key_revoked"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||
}
|
||||
|
||||
const tplLicensesEditPackage templates.TplName = "repo/licenses_edit_package"
|
||||
|
||||
// LicensesEditPackage shows the edit form for a license package.
|
||||
func LicensesEditPackage(ctx *context.Context) {
|
||||
pkgID := ctx.PathParamInt64("id")
|
||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLicensePackageByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("repo.licenses.edit_package")
|
||||
ctx.Data["PageIsLicenses"] = true
|
||||
ctx.Data["IsLicensesPage"] = true
|
||||
ctx.Data["Package"] = pkg
|
||||
ctx.HTML(http.StatusOK, tplLicensesEditPackage)
|
||||
}
|
||||
|
||||
// LicensesEditPackagePost saves edits to a license package.
|
||||
func LicensesEditPackagePost(ctx *context.Context) {
|
||||
pkgID := ctx.PathParamInt64("id")
|
||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLicensePackageByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
pkg.Name = ctx.FormString("name")
|
||||
pkg.Description = ctx.FormString("description")
|
||||
durationDays, _ := strconv.Atoi(ctx.FormString("duration_days"))
|
||||
pkg.DurationDays = durationDays
|
||||
maxSites, _ := strconv.Atoi(ctx.FormString("max_sites"))
|
||||
pkg.MaxSites = maxSites
|
||||
pkg.AllowedChannels = ctx.FormString("allowed_channels")
|
||||
pkg.IsActive = ctx.FormString("is_active") == "on"
|
||||
|
||||
if err := licenses.UpdateLicensePackage(ctx, pkg); err != nil {
|
||||
ctx.ServerError("UpdateLicensePackage", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.package_updated"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||
}
|
||||
|
||||
// LicensesDeletePackage deletes a license package. Site admin only.
|
||||
func LicensesDeletePackage(ctx *context.Context) {
|
||||
if !ctx.IsUserSiteAdmin() {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
pkgID := ctx.PathParamInt64("id")
|
||||
if err := licenses.DeleteLicensePackage(ctx, pkgID); err != nil {
|
||||
ctx.ServerError("DeleteLicensePackage", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.licenses.package_deleted"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
|
||||
licenses_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm"
|
||||
repo_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||
unit_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
||||
@@ -100,6 +101,8 @@ func SettingsCtxData(ctx *context.Context) {
|
||||
|
||||
// Settings show a repository's settings page
|
||||
func Settings(ctx *context.Context) {
|
||||
repoCfg, _ := licenses_model.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
||||
ctx.Data["RepoUpdateConfig"] = repoCfg
|
||||
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
||||
}
|
||||
|
||||
@@ -672,6 +675,22 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Save update server platform and require-key settings.
|
||||
updatePlatform := form.UpdatePlatform
|
||||
if updatePlatform == "" {
|
||||
updatePlatform = "joomla"
|
||||
}
|
||||
updateCfg := &licenses_model.UpdateStreamConfig{
|
||||
OwnerID: repo.OwnerID,
|
||||
RepoID: repo.ID,
|
||||
Platform: updatePlatform,
|
||||
RequireKey: form.RequireUpdateKey,
|
||||
StreamMode: "joomla", // inherit org default
|
||||
}
|
||||
if err := licenses_model.SaveConfig(ctx, updateCfg); err != nil {
|
||||
log.Error("SaveConfig: %v", err)
|
||||
}
|
||||
|
||||
log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
|
||||
@@ -23,7 +23,13 @@ func validateUpdateKey(ctx *context.Context) (allowedChannels []string, ok bool)
|
||||
}
|
||||
|
||||
if rawKey == "" {
|
||||
// No key provided — allow public access (all channels).
|
||||
// Check if this repo requires a key for update feed access.
|
||||
repoCfg, _ := licenses.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
||||
if repoCfg != nil && repoCfg.RequireKey {
|
||||
// Key required but not provided — return empty.
|
||||
return nil, false
|
||||
}
|
||||
// No key required — allow public access (all channels).
|
||||
return nil, true
|
||||
}
|
||||
|
||||
|
||||
@@ -1057,6 +1057,11 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Get("", org.BlockedUsers)
|
||||
m.Post("", web.Bind(forms.BlockUserForm{}), org.BlockedUsersPost)
|
||||
})
|
||||
|
||||
m.Group("/update-streams", func() {
|
||||
m.Get("", org.SettingsUpdateStreams)
|
||||
m.Post("", org.SettingsUpdateStreamsPost)
|
||||
})
|
||||
}, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true))
|
||||
}, context.OrgAssignment(context.OrgAssignmentOptions{RequireOwner: true}))
|
||||
}, reqSignIn)
|
||||
@@ -1099,6 +1104,13 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
// at the moment, only editing "owner-level projects" need to "mention", maybe in the future we can relax the permission check
|
||||
m.Get("/mentions-in-owner", reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), org.GetMentionsInOwner)
|
||||
|
||||
m.Group("/licenses", func() {
|
||||
m.Get("", org.Licenses)
|
||||
m.Post("/packages", org.LicensesCreatePackage)
|
||||
m.Post("/keys/generate", org.LicensesGenerateKey)
|
||||
m.Post("/keys/{id}/revoke", org.LicensesRevokeKey)
|
||||
})
|
||||
|
||||
m.Get("/repositories", org.Repositories)
|
||||
m.Get("/heatmap", user.DashboardHeatmap)
|
||||
|
||||
@@ -1505,6 +1517,9 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
||||
m.Group("/{username}/{reponame}/licenses", func() {
|
||||
m.Get("", repo.Licenses)
|
||||
m.Post("/packages", repo.LicensesCreatePackage)
|
||||
m.Get("/packages/{id}/edit", repo.LicensesEditPackage)
|
||||
m.Post("/packages/{id}/edit", repo.LicensesEditPackagePost)
|
||||
m.Post("/packages/{id}/delete", repo.LicensesDeletePackage)
|
||||
m.Post("/keys/generate", repo.LicensesGenerateKey)
|
||||
m.Post("/keys/{id}/revoke", repo.LicensesRevokeKey)
|
||||
}, optSignIn, context.RepoAssignment)
|
||||
|
||||
@@ -613,6 +613,15 @@ func repoAssignmentPrepareTemplateData(ctx *Context, data *repoAssignmentPrepare
|
||||
ctx.Data["NumLicensePackages"] = numLicensePackages
|
||||
ctx.Data["EnableLicenses"] = numLicensePackages > 0
|
||||
ctx.Data["IsRepoAdmin"] = ctx.Repo.Permission.IsAdmin()
|
||||
ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin()
|
||||
|
||||
// Load repo update config for platform-aware UI.
|
||||
repoUpdateCfg, _ := licenses_model.GetRepoConfig(ctx, repo.ID)
|
||||
if repoUpdateCfg != nil {
|
||||
ctx.Data["RepoUpdatePlatform"] = repoUpdateCfg.Platform
|
||||
} else {
|
||||
ctx.Data["RepoUpdatePlatform"] = "joomla"
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = repo.Owner.Name + "/" + repo.Name
|
||||
ctx.Data["PageTitleCommon"] = repo.Name + " - " + setting.AppName
|
||||
|
||||
@@ -133,6 +133,8 @@ type RepoSettingForm struct {
|
||||
|
||||
EnableReleases bool
|
||||
ReleasesVisibility string
|
||||
UpdatePlatform string
|
||||
RequireUpdateKey bool
|
||||
|
||||
EnablePackages bool
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content organization">
|
||||
{{template "org/header" .}}
|
||||
<div class="ui container">
|
||||
|
||||
{{if .NewMasterKey}}
|
||||
<div class="ui info message">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.licenses.master_key_created"}}</div>
|
||||
<p>{{ctx.Locale.Tr "repo.licenses.master_key_created_copy"}}</p>
|
||||
<code class="tw-text-lg tw-select-all">{{.NewMasterKey}}</code>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .NewKeyCreated}}
|
||||
<div class="ui success message">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.licenses.key_created"}}</div>
|
||||
<p>{{ctx.Locale.Tr "repo.licenses.key_created_copy"}}</p>
|
||||
<code class="tw-text-lg tw-select-all">{{.NewKeyCreated}}</code>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<h4 class="ui top attached header">
|
||||
{{svg "octicon-key" 16}} {{ctx.Locale.Tr "repo.licenses.packages"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{if .IsRepoAdmin}}
|
||||
<details class="tw-mb-4">
|
||||
<summary class="ui primary button">{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.new_package"}}</summary>
|
||||
<div class="tw-mt-4">
|
||||
<form class="ui form" method="post" action="{{$.Org.HomeLink}}/-/licenses/packages">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="two fields">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
|
||||
<input name="name" required placeholder="e.g. Pro Annual, Basic Monthly">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
|
||||
<input name="description" placeholder="e.g. Annual pro subscription">
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
|
||||
<input name="duration_days" type="number" value="0" min="0">
|
||||
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
|
||||
<input name="max_sites" type="number" value="0" min="0">
|
||||
<p class="help">0 = unlimited</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
|
||||
<input name="allowed_channels" placeholder="stable,release-candidate">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
{{end}}
|
||||
{{if .LicensePackages}}
|
||||
<table class="ui table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.package_name"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.duration"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.channels"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.keys_issued"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.status"}}</th>
|
||||
{{if .IsRepoAdmin}}<th></th>{{end}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .LicensePackages}}
|
||||
<tr>
|
||||
<td><strong>{{.Name}}</strong>{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}</td>
|
||||
<td>{{if eq .DurationDays 0}}{{ctx.Locale.Tr "repo.licenses.lifetime"}}{{else}}{{.DurationDays}} {{ctx.Locale.Tr "repo.licenses.days"}}{{end}}</td>
|
||||
<td>{{if .AllowedChannels}}<code>{{.AllowedChannels}}</code>{{else}}{{ctx.Locale.Tr "repo.licenses.all_channels"}}{{end}}</td>
|
||||
<td>{{.KeyCount}}</td>
|
||||
<td>{{if .IsActive}}<span class="ui green label">{{ctx.Locale.Tr "repo.licenses.active"}}</span>{{else}}<span class="ui grey label">{{ctx.Locale.Tr "repo.licenses.inactive"}}</span>{{end}}</td>
|
||||
{{if $.IsRepoAdmin}}
|
||||
<td class="tw-text-right">
|
||||
<form method="post" action="{{$.Org.HomeLink}}/-/licenses/keys/generate" class="tw-inline">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" name="package_id" value="{{.ID}}">
|
||||
<button class="ui tiny primary button" type="submit">
|
||||
{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.generate_key"}}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<div class="empty-placeholder">
|
||||
{{svg "octicon-key" 48}}
|
||||
<h2>{{ctx.Locale.Tr "repo.licenses.none"}}</h2>
|
||||
<p>{{ctx.Locale.Tr "repo.licenses.none_desc"}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .LicenseKeys}}
|
||||
<h4 class="ui top attached header tw-mt-4">
|
||||
{{svg "octicon-lock" 16}} {{ctx.Locale.Tr "repo.licenses.issued_keys"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<table class="ui table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.key_prefix"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.licensee"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.expires"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.status"}}</th>
|
||||
{{if .IsRepoAdmin}}<th></th>{{end}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .LicenseKeys}}
|
||||
<tr>
|
||||
<td><code>{{.KeyPrefix}}</code>{{if .IsInternal}} <span class="ui tiny orange label">Master</span>{{end}}</td>
|
||||
<td>{{.LicenseeName}}{{if .LicenseeEmail}} <small>({{.LicenseeEmail}})</small>{{end}}</td>
|
||||
<td>{{if eq .ExpiresUnix 0}}{{ctx.Locale.Tr "repo.licenses.never"}}{{else}}{{DateUtils.TimeSince .ExpiresUnix}}{{end}}</td>
|
||||
<td>{{if .IsActive}}<span class="ui green label">{{ctx.Locale.Tr "repo.licenses.active"}}</span>{{else}}<span class="ui grey label">{{ctx.Locale.Tr "repo.licenses.inactive"}}</span>{{end}}</td>
|
||||
{{if $.IsRepoAdmin}}
|
||||
<td class="tw-text-right">
|
||||
<form method="post" action="{{$.Org.HomeLink}}/-/licenses/keys/{{.ID}}/revoke" class="tw-inline">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<button class="ui tiny red button" type="submit">
|
||||
{{svg "octicon-x" 14}}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
@@ -25,6 +25,11 @@
|
||||
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .IsOrganizationMember}}
|
||||
<a class="{{if .IsLicensesPage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/licenses">
|
||||
{{svg "octicon-key"}} {{ctx.Locale.Tr "repo.licenses"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if and .IsRepoIndexerEnabled .CanReadCode}}
|
||||
<a class="{{if .IsCodePage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/code">
|
||||
{{svg "octicon-code"}} {{ctx.Locale.Tr "org.code"}}
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
{{ctx.Locale.Tr "packages.title"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="{{if .PageIsSettingsUpdateStreams}}active {{end}}item" href="{{.OrgLink}}/settings/update-streams">
|
||||
{{ctx.Locale.Tr "org.settings.update_streams"}}
|
||||
</a>
|
||||
{{if .EnableActions}}
|
||||
<details class="item toggleable-item" {{if or .PageIsOrgSettingsActionsGeneral .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables}}open{{end}}>
|
||||
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{{template "org/settings/layout_head" (dict "pageClass" "organization settings")}}
|
||||
<div class="org-setting-content">
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "org.settings.update_streams"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "org.settings.update_streams_desc"}}</p>
|
||||
<form class="ui form" method="post" action="{{.OrgLink}}/settings/update-streams">
|
||||
{{.CsrfTokenHtml}}
|
||||
|
||||
<div class="grouped fields">
|
||||
<label>{{ctx.Locale.Tr "org.settings.stream_mode"}}</label>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input name="stream_mode" type="radio" value="joomla" {{if or (eq .StreamConfig.StreamMode "") (eq .StreamConfig.StreamMode "joomla")}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "org.settings.stream_mode_joomla"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input name="stream_mode" type="radio" value="custom" {{if eq .StreamConfig.StreamMode "custom"}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "org.settings.stream_mode_custom"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "org.settings.default_streams"}}</label>
|
||||
<p class="help">{{ctx.Locale.Tr "org.settings.default_streams_joomla"}}</p>
|
||||
<table class="ui small table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ctx.Locale.Tr "org.settings.stream_name"}}</th>
|
||||
<th>{{ctx.Locale.Tr "org.settings.stream_suffix"}}</th>
|
||||
<th>{{ctx.Locale.Tr "repo.licenses.description"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .EffectiveStreams}}
|
||||
<tr>
|
||||
<td><code>{{.Name}}</code></td>
|
||||
<td>{{if .Suffix}}<code>{{.Suffix}}</code>{{else}}<em>(no suffix)</em>{{end}}</td>
|
||||
<td>{{.Description}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "org.settings.custom_streams"}}</label>
|
||||
<textarea name="custom_streams" rows="6" placeholder='[{"name":"lts","suffix":"-lts","description":"Long-term support"}]'>{{.StreamConfig.CustomStreams}}</textarea>
|
||||
<p class="help">{{ctx.Locale.Tr "org.settings.custom_streams_help"}}</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{template "org/settings/layout_footer" .}}
|
||||
@@ -3,6 +3,14 @@
|
||||
{{template "repo/header" .}}
|
||||
<div class="ui container">
|
||||
|
||||
{{if .NewMasterKey}}
|
||||
<div class="ui info message">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.licenses.master_key_created"}}</div>
|
||||
<p>{{ctx.Locale.Tr "repo.licenses.master_key_created_copy"}}</p>
|
||||
<code class="tw-text-lg tw-select-all">{{.NewMasterKey}}</code>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .NewKeyCreated}}
|
||||
<div class="ui success message">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.licenses.key_created"}}</div>
|
||||
@@ -12,50 +20,10 @@
|
||||
{{end}}
|
||||
|
||||
{{/* ── License Packages ── */}}
|
||||
<h4 class="ui top attached header tw-flex tw-justify-between tw-items-center">
|
||||
<span>{{svg "octicon-key" 16}} {{ctx.Locale.Tr "repo.licenses.packages"}}</span>
|
||||
{{if .IsRepoAdmin}}
|
||||
<button class="ui small primary button" onclick="document.getElementById('new-package-form').classList.toggle('tw-hidden')">
|
||||
{{ctx.Locale.Tr "repo.licenses.new_package"}}
|
||||
</button>
|
||||
{{end}}
|
||||
<h4 class="ui top attached header">
|
||||
{{svg "octicon-key" 16}} {{ctx.Locale.Tr "repo.licenses.packages"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{if .IsRepoAdmin}}
|
||||
<div id="new-package-form" class="tw-hidden tw-mb-4">
|
||||
<form class="ui form" method="post" action="{{.RepoLink}}/licenses/packages">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="two fields">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
|
||||
<input name="name" required placeholder="Pro Annual">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
|
||||
<input name="description" placeholder="Annual pro subscription">
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
|
||||
<input name="duration_days" type="number" value="0" min="0" placeholder="0 = lifetime">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
|
||||
<input name="max_sites" type="number" value="0" min="0" placeholder="0 = unlimited">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
|
||||
<input name="allowed_channels" placeholder='stable,release-candidate'>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
|
||||
</form>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .LicensePackages}}
|
||||
<table class="ui table">
|
||||
<thead>
|
||||
@@ -77,14 +45,25 @@
|
||||
<td>{{.KeyCount}}</td>
|
||||
<td>{{if .IsActive}}<span class="ui green label">{{ctx.Locale.Tr "repo.licenses.active"}}</span>{{else}}<span class="ui grey label">{{ctx.Locale.Tr "repo.licenses.inactive"}}</span>{{end}}</td>
|
||||
{{if $.IsRepoAdmin}}
|
||||
<td class="tw-text-right">
|
||||
<td class="tw-text-right tw-flex tw-gap-1 tw-justify-end">
|
||||
<form method="post" action="{{$.RepoLink}}/licenses/keys/generate" class="tw-inline">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" name="package_id" value="{{.ID}}">
|
||||
<button class="ui tiny primary button" type="submit" title="{{ctx.Locale.Tr "repo.licenses.generate_key"}}">
|
||||
{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.generate_key"}}
|
||||
{{svg "octicon-plus" 14}}
|
||||
</button>
|
||||
</form>
|
||||
<a class="ui tiny button" href="{{$.RepoLink}}/licenses/packages/{{.ID}}/edit" title="{{ctx.Locale.Tr "repo.licenses.edit_package"}}">
|
||||
{{svg "octicon-pencil" 14}}
|
||||
</a>
|
||||
{{if $.IsSiteAdmin}}
|
||||
<form method="post" action="{{$.RepoLink}}/licenses/packages/{{.ID}}/delete" class="tw-inline" onsubmit="return confirm('Delete this package? This action cannot be undone.')">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<button class="ui tiny red button" type="submit" title="{{ctx.Locale.Tr "repo.licenses.delete_package"}}">
|
||||
{{svg "octicon-trash" 14}}
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
@@ -100,6 +79,48 @@
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{/* ── Create New License Package ── */}}
|
||||
{{if .IsRepoAdmin}}
|
||||
<div class="tw-mt-4">
|
||||
<details>
|
||||
<summary class="ui primary button">{{svg "octicon-plus" 14}} {{ctx.Locale.Tr "repo.licenses.new_package"}}</summary>
|
||||
<div class="ui segment tw-mt-2">
|
||||
<form class="ui form" method="post" action="{{.RepoLink}}/licenses/packages">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="two fields">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
|
||||
<input name="name" required placeholder="e.g. Pro Annual, Basic Monthly">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
|
||||
<input name="description" placeholder="e.g. Annual pro subscription with all channels">
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
|
||||
<input name="duration_days" type="number" value="0" min="0">
|
||||
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
|
||||
<input name="max_sites" type="number" value="0" min="0">
|
||||
<p class="help">0 = unlimited</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
|
||||
<input name="allowed_channels" placeholder="stable,release-candidate">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.licenses.create_package"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ── Issued Keys ── */}}
|
||||
{{if .LicenseKeys}}
|
||||
<h4 class="ui top attached header tw-mt-4">
|
||||
@@ -140,7 +161,7 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{/* ── Update Feed Info ── */}}
|
||||
{{/* ── Update Feed URLs ── */}}
|
||||
<h4 class="ui top attached header tw-mt-4">
|
||||
{{svg "octicon-rss" 16}} {{ctx.Locale.Tr "repo.licenses.update_feeds"}}
|
||||
</h4>
|
||||
@@ -148,14 +169,14 @@
|
||||
<div class="field">
|
||||
<label>Joomla updates.xml</label>
|
||||
<div class="ui action input tw-w-full">
|
||||
<input type="text" readonly value="{{AppSubUrl}}{{.RepoLink}}/updates.xml" onclick="this.select()">
|
||||
<input type="text" readonly value="{{.Repository.HTMLURL ctx}}/updates.xml" onclick="this.select()">
|
||||
<button class="ui button" onclick="navigator.clipboard.writeText(this.previousElementSibling.value)">{{svg "octicon-copy" 14}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field tw-mt-2">
|
||||
<label>Dolibarr JSON</label>
|
||||
<div class="ui action input tw-w-full">
|
||||
<input type="text" readonly value="{{AppSubUrl}}{{.RepoLink}}/updates/dolibarr.json" onclick="this.select()">
|
||||
<input type="text" readonly value="{{.Repository.HTMLURL ctx}}/updates/dolibarr.json" onclick="this.select()">
|
||||
<button class="ui button" onclick="navigator.clipboard.writeText(this.previousElementSibling.value)">{{svg "octicon-copy" 14}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content repository">
|
||||
{{template "repo/header" .}}
|
||||
<div class="ui container">
|
||||
<h4 class="ui top attached header">
|
||||
{{svg "octicon-pencil" 16}} {{ctx.Locale.Tr "repo.licenses.edit_package"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form" method="post" action="{{.RepoLink}}/licenses/packages/{{.Package.ID}}/edit">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="two fields">
|
||||
<div class="required field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.package_name"}}</label>
|
||||
<input name="name" required value="{{.Package.Name}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.description"}}</label>
|
||||
<input name="description" value="{{.Package.Description}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.duration"}} ({{ctx.Locale.Tr "repo.licenses.days"}})</label>
|
||||
<input name="duration_days" type="number" value="{{.Package.DurationDays}}" min="0">
|
||||
<p class="help">0 = {{ctx.Locale.Tr "repo.licenses.lifetime"}}</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.max_sites"}}</label>
|
||||
<input name="max_sites" type="number" value="{{.Package.MaxSites}}" min="0">
|
||||
<p class="help">0 = unlimited</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.channels"}}</label>
|
||||
<input name="allowed_channels" value="{{.Package.AllowedChannels}}">
|
||||
<p class="help">{{ctx.Locale.Tr "repo.licenses.channels_help"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input name="is_active" type="checkbox" {{if .Package.IsActive}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "repo.licenses.active"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field tw-mt-4">
|
||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "save"}}</button>
|
||||
<a class="ui button" href="{{.RepoLink}}/licenses">{{ctx.Locale.Tr "cancel"}}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
@@ -17,9 +17,16 @@
|
||||
</a>
|
||||
{{end}}
|
||||
{{if not .PageIsTagList}}
|
||||
<a class="ui small button" href="{{.RepoLink}}/updates.xml" target="_blank">
|
||||
{{svg "octicon-download" 16}} {{ctx.Locale.Tr "repo.release.update_feed"}}
|
||||
</a>
|
||||
{{if or (eq .RepoUpdatePlatform "joomla") (eq .RepoUpdatePlatform "both") (eq .RepoUpdatePlatform "")}}
|
||||
<a class="ui small button" href="{{.RepoLink}}/updates.xml" target="_blank">
|
||||
{{svg "octicon-download" 16}} Joomla XML
|
||||
</a>
|
||||
{{end}}
|
||||
{{if or (eq .RepoUpdatePlatform "dolibarr") (eq .RepoUpdatePlatform "both")}}
|
||||
<a class="ui small button" href="{{.RepoLink}}/updates/dolibarr.json" target="_blank">
|
||||
{{svg "octicon-download" 16}} Dolibarr JSON
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if and (not .PageIsTagList) .CanCreateRelease}}
|
||||
<a class="ui small primary button" href="{{$.RepoLink}}/releases/new{{if .PageIsSingleTag}}?tag={{.SingleReleaseTagName}}{{end}}">
|
||||
|
||||
@@ -514,6 +514,20 @@
|
||||
</select>
|
||||
<p class="help">{{ctx.Locale.Tr "repo.settings.unit_visibility_releases_help"}}</p>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.update_platform"}}</label>
|
||||
<select name="update_platform" class="ui dropdown">
|
||||
<option value="joomla" {{if or (not .RepoUpdateConfig) (eq .RepoUpdateConfig.Platform "joomla") (eq .RepoUpdateConfig.Platform "")}}selected{{end}}>Joomla (updates.xml)</option>
|
||||
<option value="dolibarr" {{if and .RepoUpdateConfig (eq .RepoUpdateConfig.Platform "dolibarr")}}selected{{end}}>Dolibarr (JSON)</option>
|
||||
<option value="both" {{if and .RepoUpdateConfig (eq .RepoUpdateConfig.Platform "both")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.update_platform_both"}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<input name="require_update_key" type="checkbox" {{if and .RepoUpdateConfig .RepoUpdateConfig.RequireKey}}checked{{end}}>
|
||||
<label>{{ctx.Locale.Tr "repo.settings.require_update_key"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{$isPackagesEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypePackages}}
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 05.10.00
|
||||
VERSION: 05.14.00
|
||||
-->
|
||||
|
||||
<updates>
|
||||
@@ -87,13 +87,13 @@
|
||||
<element>mokogitea</element>
|
||||
<type>application</type>
|
||||
<client>site</client>
|
||||
<version>05.10.00</version>
|
||||
<version>05.14.00</version>
|
||||
<creationDate>2026-05-31</creationDate>
|
||||
<infourl title='MokoGitea'>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/stable/mokogitea-05.10.00.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/releases/download/stable/mokogitea-05.14.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>d7f614631c076b5b0c87e9be0adc7d0fb65261029cfcfc25530dfeac7e3976b4</sha256>
|
||||
<sha256>bec4bf5a1a841f8e72d9826451004db5d8afc70144231dfedc7fb01a6695955c</sha256>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
|
||||
Reference in New Issue
Block a user