Compare commits

...

39 Commits

Author SHA1 Message Date
Jonathan Miller 1ad277cd73 Merge branch 'dev' 2026-05-26 21:11:54 -05:00
Jonathan Miller 4624385501 feat(ci): update version branch on every stable release
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Add Step 12 to auto-release pipeline that recreates the version branch
from main after each stable release. Also mirrors the version branch
to GitHub alongside main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 21:11:32 -05:00
Jonathan Miller e084c7f4b4 Merge branch 'dev' 2026-05-26 21:07:38 -05:00
Jonathan Miller 9721728b45 Merge remote-tracking branch 'origin/dev' into dev
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
2026-05-26 21:07:04 -05:00
jmiller 38ed8eaeea Merge pull request #2 from mokoconsulting-tech/feat/appname-branding
feat(branding): replace hardcoded Gitea/MokoGitea with APP_NAME setting
2026-05-26 20:37:40 -05:00
Jonathan Miller 1608b5c4b9 feat(branding): replace hardcoded Gitea/MokoGitea with APP_NAME setting
Add runtime ${APP_NAME} placeholder substitution in locale strings so
all user-facing text reflects the configured APP_NAME from app.ini.
Replace 52 hardcoded locale strings, template literals, HTTP auth
realm headers, and Swagger API titles/descriptions with the
configurable value.

Closes #1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 20:19:20 -05:00
jmiller eb15990510 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:51:10 +00:00
jmiller 6654d7605d chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:51:04 +00:00
jmiller 411ba858f5 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:49:59 +00:00
jmiller a7e39fa992 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:49:53 +00:00
jmiller bd899bcbb1 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:48:46 +00:00
jmiller 7c014dc4da chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:48:40 +00:00
jmiller d0ca5eff28 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:37:09 +00:00
jmiller 4c4d2ac956 Merge pull request 'rc(v05.06.00): security backports, actions deadlock fix, dep bumps' (#228) from rc/05.06.00 into main
Universal: Auto Version Bump / Patch Bump (push) Successful in 6s
rc(v05.06.00): security backports, actions deadlock fix, dep bumps (#228)
2026-05-26 22:37:06 +00:00
jmiller 47ddd6a277 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:35:53 +00:00
jmiller e946d49bf3 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:35:47 +00:00
Jonathan Miller c3eb57f124 Merge remote-tracking branch 'origin/main' into rc/05.06.00
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 22s
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m0s
# Conflicts:
#	.mokogitea/workflows/update-server.yml
2026-05-26 17:35:19 -05:00
jmiller c0662ab118 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:25:31 +00:00
jmiller fce8389296 chore(ci): update auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:25:24 +00:00
jmiller eeaf9f036b chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:24:14 +00:00
jmiller 9957fe56ca chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 22:24:08 +00:00
jmiller dcc4a49cdb chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:13:35 +00:00
jmiller f15a0ed7e4 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 22:13:29 +00:00
jmiller 0e0ce94353 chore(ci): add auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:12:24 +00:00
jmiller 455f12e21b chore(ci): add auto-bump.yml from moko-platform [skip ci] 2026-05-26 22:12:18 +00:00
jmiller 360d0b1b1f Merge pull request 'fix(security): backport upstream v1.26.2 security fixes' (#226) from fix/225-security-backports into dev
fix(security): backport upstream v1.26.2 security fixes (#226)
2026-05-26 22:05:08 +00:00
jmiller e8ce4ae60b Merge pull request 'fix(actions): retry workflow insertion on database deadlock' (#221) from fix/220-actions-deadlock-retry into dev
fix(actions): retry workflow insertion on database deadlock (#221)
2026-05-26 22:04:54 +00:00
Jonathan Miller dd6fc4b69c fix(actions): retry workflow insertion on database deadlock
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 21s
When multiple workflows are triggered by a single event (e.g. a
pull_request with several matching workflow files), each InsertRun
transaction acquires an X-lock on the repository row via
UpdateRepoRunsNumbers and an index lock on action_run. Two concurrent
transactions can deadlock when each holds one lock and waits for the
other. InnoDB kills the lighter transaction, but handleWorkflows only
logged the error and silently dropped the workflow run — making it
appear as though pull_request events were never fired.

This was the root cause of API-created PRs appearing to not trigger
Actions workflows: the notification pipeline was correct, but the DB
insert was lost to an unretried deadlock.

The fix wraps PrepareRunAndInsert in a retry loop (up to 3 attempts
with exponential backoff) that detects deadlock errors across MySQL,
PostgreSQL, and SQLite. On deadlock, the rolled-back run fields are
reset before the next attempt.

Also adds db.IsErrDeadlock() for cross-engine deadlock detection and
unit tests for the same.

Closes #220

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:16:30 -05:00
jmiller 3b79d57d45 chore: sync .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-26 20:12:56 +00:00
jmiller 871cb495b1 chore: sync .mokogitea/workflows/pre-release.yml from moko-platform [skip ci] 2026-05-26 20:11:02 +00:00
jmiller d66ca8db83 chore(ci): add update-server.yml universal workflow [skip ci] 2026-05-26 19:56:49 +00:00
jmiller 7019f08f74 chore(ci): update pre-release.yml from moko-platform [skip ci] 2026-05-26 19:35:56 +00:00
jmiller b577f8f4f4 chore(ci): update auto-release.yml from moko-platform [skip ci] 2026-05-26 19:35:42 +00:00
jmiller 65456962b4 chore: add .mokogitea/manifest.xml
Authored-by: Moko Consulting
2026-05-26 19:19:06 +00:00
jmiller 020b160403 chore: add .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-26 19:04:20 +00:00
jmiller c633024a9c Merge pull request 'rc(v05.05.00): org 2FA, wiki slugs, http render fix' (#219) from rc/05.05.00 into main 2026-05-26 18:50:40 +00:00
jmiller 1e1441f8bd Merge pull request 'rc(v05.04.00): login notifications, help footer, login logo, checksum fix' (#210) from rc/05.04.00 into main 2026-05-26 17:47:13 +00:00
jmiller 3c55a3baca Merge pull request 'rc: footer help + login logo' (#206) from rc/05.03.08 into main 2026-05-26 16:12:57 +00:00
jmiller b7f9743ade Merge pull request 'rc: fix dashboard icon' (#203) from rc/05.03.06 into main 2026-05-26 04:39:15 +00:00
19 changed files with 526 additions and 629 deletions
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
<identity>
<name>MokoGitea</name>
<org>MokoConsulting</org>
<description>Moko fork of Gitea — adding project board REST API endpoints and custom enhancements</description>
<version>01.00.00</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
<platform>go</platform>
<standards-version>05.00.00</standards-version>
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/moko-platform</standards-source>
</governance>
<build>
<language>Go</language>
<package-type>application</package-type>
<entry-point>./</entry-point>
</build>
</moko-platform>
+84
View File
@@ -0,0 +1,84 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
name: "Universal: Auto Version Bump"
on:
push:
branches:
- dev
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions:
contents: write
jobs:
bump:
name: Version Bump
runs-on: release
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request')
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.GA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
if [ -d "/opt/moko-platform/cli" ]; then
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
fi
- name: Bump version
run: |
BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true
echo "$BUMP"
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true
[ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; }
# Propagate to platform manifests
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch dev 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Commit if anything changed
if git diff --quiet && git diff --cached --quiet; then
echo "No version changes to commit"
exit 0
fi
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push origin dev
echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY
+104 -346
View File
@@ -31,6 +31,15 @@ on:
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
@@ -47,8 +56,8 @@ jobs:
name: Promote Pre-Release to RC
runs-on: release
if: >-
github.event.action == 'opened' &&
github.event.pull_request.draft == true
(github.event.action == 'opened' && github.event.pull_request.draft == true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository
@@ -78,7 +87,7 @@ jobs:
--from auto --to release-candidate \
--token "${{ secrets.GA_TOKEN }}" \
--api-base "${API_BASE}" \
--branch "${{ github.event.pull_request.head.ref }}"
--branch "${{ github.event.pull_request.head.ref || 'dev' }}"
- name: Cascade lesser channels
continue-on-error: true
@@ -100,7 +109,8 @@ jobs:
name: Build & Release Pipeline
runs-on: release
if: >-
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository
@@ -170,16 +180,15 @@ jobs:
echo "::notice::No RC release — full build pipeline"
fi
- name: "Step 1b: Bump version"
- name: "Step 1b: Minor bump version"
id: bump
if: >-
steps.version.outputs.skip != 'true' &&
steps.rc.outputs.promote != 'true'
run: |
MOKO_API="/tmp/moko-platform-api/cli"
BUMP=$(php ${MOKO_API}/version_bump.php --path . --minor)
VERSION=$(echo "$BUMP" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
[ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
php ${MOKO_API}/version_bump.php --path . --minor 2>&1 || true
VERSION=$(php ${MOKO_API}/version_read.php --path .)
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Bumped to: ${VERSION}"
@@ -209,95 +218,8 @@ jobs:
steps.check.outputs.already_released != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
ERRORS=0
PLATFORM="${{ steps.platform.outputs.platform }}"
MANIFEST="${{ steps.platform.outputs.manifest }}"
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
echo "## Pre-Release Sanity Checks (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# -- Version drift check (must pass before release) --------
README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
if [ "$README_VER" != "$VERSION" ]; then
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Check CHANGELOG version matches
CL_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' CHANGELOG.md 2>/dev/null | head -1)
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
# Check composer.json version if present
if [ -f "composer.json" ]; then
COMP_VER=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' composer.json 2>/dev/null | head -1)
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
fi
# Common checks
if [ ! -f "LICENSE" ]; then
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
fi
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
else
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
fi
# -- Platform-specific checks --------
case "$PLATFORM" in
joomla)
if [ -n "$MANIFEST" ]; then
XML_VER=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null)
echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
else
echo "- No Joomla XML manifest (WaaS site)" >> $GITHUB_STEP_SUMMARY
fi ;;
dolibarr)
if [ -n "$MOD_FILE" ]; then
MOD_VER=$(sed -n "s/.*\\\$this->version = '\([^']*\)'.*/\1/p" "$MOD_FILE" 2>/dev/null | head -1)
if [ -n "$MOD_VER" ] && [ "$MOD_VER" != "$VERSION" ]; then
echo "- Module drift: \`${MOD_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Module version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
else
echo "- No mod*.class.php found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
if [ ! -f "update.txt" ]; then
echo "- Missing update.txt" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi ;;
*) echo "- Generic platform no manifest checks" >> $GITHUB_STEP_SUMMARY ;;
esac
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
else
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
fi
php /tmp/moko-platform-api/cli/release_validate.php \
--path . --version "$VERSION" --output-summary --github-output || true
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
# Always runs — every version change on main archives to version/XX.YY
@@ -337,25 +259,7 @@ jobs:
php /tmp/moko-platform-api/cli/badge_update.php --path . --version "${VERSION}" 2>/dev/null || true
php /tmp/moko-platform-api/cli/version_check.php --path . --fix 2>/dev/null || true
- name: "Step 5: Write update stream"
if: >-
steps.version.outputs.skip != 'true' &&
steps.platform.outputs.platform == 'joomla'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
# Fetch latest updates.xml from main so preserve logic has all channels
GA_TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
curl -sf -H "Authorization: token ${GA_TOKEN}" \
"${API}/contents/updates.xml?ref=main" 2>/dev/null | \
python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
> updates.xml 2>/dev/null || true
php /tmp/moko-platform-api/cli/updates_xml_build.php \
--path . --version "${VERSION}" --stability stable \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
--github-output
# Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum
- name: Commit release changes
if: >-
@@ -398,7 +302,7 @@ jobs:
steps.version.outputs.skip != 'true' &&
steps.rc.outputs.promote == 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_promote.php \
--from release-candidate --to stable \
@@ -415,250 +319,93 @@ jobs:
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
BRANCH="${{ steps.version.outputs.branch }}"
MAJOR="${{ steps.version.outputs.major }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_create.php \
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch main
echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
# Reuse metadata from Step 5 (single source of truth)
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
EXT_NAME="${{ steps.updates.outputs.ext_name }}"
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
# Fallbacks if Step 5 was skipped
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
[ -z "$EXT_NAME" ] && EXT_NAME="${GITEA_REPO}"
NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
# Build release name: "Pretty Name VERSION (type_element-VERSION)"
# Strip existing type prefix to prevent duplication
EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
RELEASE_NAME="${EXT_NAME} ${VERSION} (${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION})"
# Delete existing release if present (overwrite, not append)
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
if [ -n "$EXISTING_ID" ]; then
curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${EXISTING_ID}" 2>/dev/null || true
curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/tags/${RELEASE_TAG}" 2>/dev/null || true
echo "Deleted previous stable release (id: ${EXISTING_ID})"
fi
# Create fresh release
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases" \
-d "$(python3 -c "import json; print(json.dumps({
'tag_name': '${RELEASE_TAG}',
'name': '${RELEASE_NAME}',
'body': '''## ${VERSION} ($(date +%Y-%m-%d))\n${NOTES}''',
'target_commitish': '${BRANCH}'
}))")"
echo "Release created: ${RELEASE_NAME}" >> $GITHUB_STEP_SUMMARY
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
- name: "Step 8: Build package and update checksum"
# -- STEP 8: Build packages and upload to release ----------------------------
- name: "Step 8: Build package and upload"
id: package
if: >-
steps.version.outputs.skip != 'true' &&
steps.rc.outputs.promote != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
REPO="${{ github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_package.php \
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# All ZIPs upload to the major release tag (vXX)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -z "$RELEASE_ID" ]; then
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
exit 0
# -- STEP 5: Write update stream (after build so SHA-256 is available) -----
- name: "Step 5: Write update stream"
if: steps.version.outputs.skip != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
# Fetch latest updates.xml from main so preserve logic has all channels
GA_TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
curl -sf -H "Authorization: token ${GA_TOKEN}" \
"${API}/contents/updates.xml?ref=main" 2>/dev/null | \
python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
> updates.xml 2>/dev/null || true
SHA_FLAG=""
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
php /tmp/moko-platform-api/cli/updates_xml_build.php \
--path . --version "${VERSION}" --stability stable \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
${SHA_FLAG} --github-output
# Commit updates.xml if changed
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 remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add updates.xml
git commit -m "chore: update stable channel ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push origin HEAD 2>&1 || true
fi
# Find extension element name from manifest
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
[ -z "$MANIFEST" ] && exit 0
# Reuse element from Step 5, with same fallback chain
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
# ZIP name: type_folder_element-VERSION (e.g. plg_system_mokojgdpc-01.01.00.zip)
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
# For packages, prefer <packagename> over filename-derived element
if [ "$EXT_TYPE" = "package" ]; then
PKG_NAME=$(sed -n 's/.*<packagename>\([^<]*\)<\/packagename>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -n "$PKG_NAME" ] && EXT_ELEMENT="$PKG_NAME"
fi
# Strip existing type prefix to prevent duplication (e.g. pkg_mokowaas → mokowaas)
EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
# -- Build install packages from src/ ----------------------------
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/"; exit 0; }
# ZIP package (type-aware via moko-platform PHP API)
php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --output /tmp
# Match the expected ZIP_NAME for upload
BUILT_ZIP=$(ls /tmp/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip 2>/dev/null | head -1 || true)
if [ -n "$BUILT_ZIP" ] && [ "$BUILT_ZIP" != "/tmp/${ZIP_NAME}" ]; then
mv "$BUILT_ZIP" "/tmp/${ZIP_NAME}"
fi
# tar.gz package (flat source archive)
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" --exclude='.ftpignore' --exclude='sftp-config*' --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown")
TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown")
# -- Calculate SHA-256 for both ----------------------------------
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
# -- Get existing assets for cleanup --------------------------------
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
# -- Create per-file .sha256 checksum files -------------------------
echo "${SHA256_ZIP} ${ZIP_NAME}" > "/tmp/${ZIP_NAME}.sha256"
echo "${SHA256_TAR} ${TAR_NAME}" > "/tmp/${TAR_NAME}.sha256"
# -- Upload packages + checksums to release tag --------------------
for ASSET in "${ZIP_NAME}" "${TAR_NAME}" "${ZIP_NAME}.sha256" "${TAR_NAME}.sha256"; do
[ ! -f "/tmp/${ASSET}" ] && continue
# Delete existing asset with same name
ASSET_ID=$(echo "$ASSETS" | python3 -c "
import sys,json
assets = json.load(sys.stdin)
for a in assets:
if a['name'] == '${ASSET}':
print(a['id']); break
" 2>/dev/null || true)
[ -n "$ASSET_ID" ] && curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
# Upload
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${ASSET}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${ASSET}" > /dev/null 2>&1 || true
done
# updates.xml already handled by Step 5 (updates_xml_build.php with preserve logic)
echo "### Packages" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Package | Size | SHA-256 |" >> $GITHUB_STEP_SUMMARY
echo "|---------|------|---------|" >> $GITHUB_STEP_SUMMARY
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY
# -- STEP 8b: Update release description with changelog ----------------------
- name: "Step 8b: Update release body"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
MOKO_CLI="/tmp/moko-platform-api/cli"
php ${MOKO_CLI}/release_body_update.php \
php /tmp/moko-platform-api/cli/release_body_update.php \
--path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
--token "${{ secrets.GA_TOKEN }}" \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
2>/dev/null || {
# Fallback: simple body update if CLI not available
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\nChecksum files attached as \`*.sha256\` assets."
curl -sf -X PATCH -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases/${RELEASE_ID}" \
-d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
fi
}
2>&1 || true
echo "Release body updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
steps.version.outputs.stability == 'stable' &&
secrets.GH_TOKEN != ''
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
MAJOR="${{ steps.version.outputs.major }}"
BRANCH="${{ steps.version.outputs.branch }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
echo "$NOTES" > /tmp/release_notes.md
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".tag_name // empty" || true)
if [ -z "$EXISTING" ]; then
gh release create "$RELEASE_TAG" \
--repo "$GH_REPO" \
--title "v${MAJOR} (latest: ${VERSION})" \
--notes-file /tmp/release_notes.md \
--target "$BRANCH" || true
else
gh release edit "$RELEASE_TAG" \
--repo "$GH_REPO" \
--title "v${MAJOR} (latest: ${VERSION})" || true
fi
# Upload assets to GitHub mirror
for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do
if [ -f "$PKG" ]; then
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".id // empty")
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
fi
done
echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
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.GA_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_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"
@@ -676,6 +423,9 @@ jobs:
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"
git push github origin/main:refs/heads/version --force 2>/dev/null \
&& echo "version branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror version push failed"
# -- Clean up lesser pre-releases (cascade) ---------------------------------
# stable → deletes all | rc → beta,alpha,dev | beta → alpha,dev | alpha → dev
@@ -709,28 +459,36 @@ jobs:
echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Dolibarr: Reset dev version"
if: >-
steps.version.outputs.skip != 'true' &&
steps.platform.outputs.platform == 'dolibarr' &&
steps.platform.outputs.mod_file != ''
- name: "Step 12: Update 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.GA_TOKEN }}"
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
ENCODED_PATH=$(echo "$MOD_FILE" | sed 's|^\./||' | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))")
FILE_RESP=$(curl -sf -H "Authorization: token ${TOKEN}" "${API_BASE}/contents/${ENCODED_PATH}?ref=dev" 2>/dev/null || true)
FILE_SHA=$(echo "$FILE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
FILE_CONTENT=$(echo "$FILE_RESP" | python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin).get('content','')).decode())" 2>/dev/null || true)
if [ -n "$FILE_SHA" ] && [ -n "$FILE_CONTENT" ]; then
UPDATED=$(echo "$FILE_CONTENT" | sed "s/\$this->version = '[^']*'/\$this->version = 'development'/")
ENCODED=$(echo "$UPDATED" | base64 -w0)
curl -sf -X PUT -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/contents/${ENCODED_PATH}" \
-d "$(jq -n --arg content \"$ENCODED\" --arg sha \"$FILE_SHA\" --arg msg \"chore(version): reset dev version [skip ci]\" --arg branch \"dev\" '{content:$content,sha:$sha,message:$msg,branch:$branch}')" > /dev/null 2>&1 || true
fi
MAIN_SHA=$(git rev-parse HEAD)
# Try delete + recreate (handles protected branch edge cases)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/version" 2>/dev/null && echo "Deleted old version branch"
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"version","old_branch_name":"main"}' 2>/dev/null \
&& echo "Created version branch from main (${MAIN_SHA})" \
|| echo "WARNING: version branch update failed"
echo "Version branch updated to main (${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.GA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
+48 -210
View File
@@ -52,41 +52,24 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GA_TOKEN }}
- name: Setup tools
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Update moko-platform CLI tools if available; install PHP if missing
if command -v moko-platform-update &> /dev/null; then
moko-platform-update
elif [ -d "/opt/moko-platform" ]; then
cd /opt/moko-platform && git pull origin main --quiet 2>/dev/null || true
else
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
fi
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
/tmp/moko-platform-api
fi
# Set MOKO_CLI to whichever path exists
if [ -d "/opt/moko-platform/cli" ]; then
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
else
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
- name: Detect platform
id: platform
run: |
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1 | tr -d '[:space:]')
[ -z "$PLATFORM" ] && PLATFORM="generic"
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
@@ -100,16 +83,9 @@ jobs:
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Patch bump via CLI tool
php ${MOKO_CLI}/version_bump.php --path .
# 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"
TODAY=$(date +%Y-%m-%d)
# Update platform-specific manifest
PLATFORM="${{ steps.platform.outputs.platform }}"
MANIFEST="${{ steps.platform.outputs.manifest }}"
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
@@ -127,36 +103,16 @@ jobs:
git push origin HEAD 2>&1
}
# Auto-detect element (platform-aware)
EXT_ELEMENT=""
case "$PLATFORM" in
joomla)
if [ -n "$MANIFEST" ]; then
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
case "$EXT_ELEMENT" in
templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac
fi
else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
;;
dolibarr)
if [ -n "$MOD_FILE" ]; then
MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
;;
*)
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
;;
esac
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
@@ -164,168 +120,50 @@ jobs:
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Build package
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ ! -d "$SOURCE_DIR" ]; then
echo "::error::No src/ or htdocs/ directory"
exit 1
fi
MANIFEST="${{ steps.meta.outputs.manifest }}"
EXT_TYPE=""
if [ -n "$MANIFEST" ]; then
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
fi
EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
mkdir -p build/package
if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
echo "=== Building Joomla PACKAGE (multi-extension) ==="
for ext_dir in "${SOURCE_DIR}"/packages/*/; do
[ ! -d "$ext_dir" ] && continue
EXT_NAME=$(basename "$ext_dir")
echo " Packaging sub-extension: ${EXT_NAME}"
cd "$ext_dir"
zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES
cd "$OLDPWD"
done
for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
[ -f "$f" ] && cp "$f" build/package/
done
else
echo "=== Building standard extension ==="
rsync -a \
--exclude='sftp-config*' \
--exclude='.ftpignore' \
--exclude='*.ppk' \
--exclude='*.pem' \
--exclude='*.key' \
--exclude='.env*' \
--exclude='*.local' \
--exclude='.build-trigger' \
"${SOURCE_DIR}/" build/package/
fi
- name: Create ZIP
id: zip
run: |
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
cd build/package
zip -r "../${ZIP_NAME}" .
cd ..
SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
- name: Create or replace Gitea release
- name: Create release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
BRANCH=$(git branch --show-current)
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch dev --prerelease
BODY="## ${VERSION} ($(date +%Y-%m-%d))
**Channel:** ${STABILITY}
**SHA-256:** \`${SHA256}\`"
# Delete existing release
EXISTING_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
"${API}/releases/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$EXISTING_ID" ]; then
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/releases/${EXISTING_ID}" 2>/dev/null || true
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/tags/${TAG}" 2>/dev/null || true
fi
# Create release
RELEASE_ID=$(curl -sS -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API}/releases" \
-d "$(jq -n \
--arg tag "$TAG" \
--arg target "$BRANCH" \
--arg name "${EXT_ELEMENT} ${VERSION} (${STABILITY})" \
--arg body "$BODY" \
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: true}'
)" | jq -r '.id')
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
# Upload ZIP
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
--data-binary "@build/${ZIP_NAME}"
echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
- name: Build package and upload
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
- name: Update updates.xml
if: steps.platform.outputs.platform == 'joomla'
run: |
STABILITY="${{ steps.meta.outputs.stability }}"
VERSION="${{ steps.meta.outputs.version }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
TAG="${{ steps.meta.outputs.tag }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
if [ ! -f "updates.xml" ]; then
echo "No updates.xml -- skipping"
exit 0
fi
# Map stability to XML tag name
case "$STABILITY" in
development) XML_TAG="development" ;;
alpha) XML_TAG="alpha" ;;
beta) XML_TAG="beta" ;;
release-candidate) XML_TAG="rc" ;;
*) XML_TAG="$STABILITY" ;;
esac
SHA_FLAG=""
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${TAG}/${ZIP_NAME}"
# Use PHP to update the channel in updates.xml
php -r '
$xml_tag = $argv[1];
$version = $argv[2];
$sha256 = $argv[3];
$url = $argv[4];
$date = date("Y-m-d");
$content = file_get_contents("updates.xml");
$pattern = "/(<update>(?:(?!<\/update>).)*?<tag>" . preg_quote($xml_tag) . "<\/tag>.*?<\/update>)/s";
$content = preg_replace_callback($pattern, function($m) use ($version, $sha256, $url, $date) {
$block = $m[0];
$block = preg_replace("/<version>[^<]*<\/version>/", "<version>{$version}</version>", $block);
if (strpos($block, "<sha256>") !== false) {
$block = preg_replace("/<sha256>[^<]*<\/sha256>/", "<sha256>{$sha256}</sha256>", $block);
} else {
$block = str_replace("</downloads>", "</downloads>\n <sha256>{$sha256}</sha256>", $block);
}
$block = preg_replace("/(<downloadurl[^>]*>)[^<]*(<\/downloadurl>)/", "\${1}{$url}\${2}", $block);
return $block;
}, $content);
file_put_contents("updates.xml", $content);
echo "Updated {$xml_tag} channel: version={$version}\n";
' "$XML_TAG" "$VERSION" "$SHA256" "$DOWNLOAD_URL"
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
@@ -347,7 +185,7 @@ jobs:
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
echo "Syncing updates.xml -> ${BRANCH}"
git fetch origin "${BRANCH}" 2>/dev/null || continue
git checkout "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
@@ -374,7 +212,7 @@ jobs:
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+69 -6
View File
@@ -206,13 +206,76 @@ jobs:
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}"
# -- Build install packages and upload to release --------------------
php /tmp/moko-platform/cli/release_package.php \
--path . --version "${DISPLAY_VERSION}" --tag "${RELEASE_TAG}" \
--token "${{ secrets.GA_TOKEN }}" --api-base "${API_BASE}" \
--repo "${GITEA_REPO}" --output /tmp 2>&1 || true
# -- Build install packages (ZIP + tar.gz) --------------------
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ -d "$SOURCE_DIR" ]; then
EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
echo "Package built and uploaded for ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES
cd ..
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
--exclude='.ftpignore' --exclude='sftp-config*' \
--exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
# Ensure release exists on Gitea
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -z "$RELEASE_ID" ]; then
# Create release
RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases" \
-d "$(python3 -c "import json; print(json.dumps({
'tag_name': '${RELEASE_TAG}',
'name': '${RELEASE_TAG} (${DISPLAY_VERSION})',
'body': '${STABILITY} release',
'prerelease': True,
'target_commitish': 'main'
}))")" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
fi
if [ -n "$RELEASE_ID" ]; then
# Delete existing assets with same name before uploading
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do
ASSET_ID=$(echo "$ASSETS" | python3 -c "
import sys,json
assets = json.load(sys.stdin)
for a in assets:
if a['name'] == '${ASSET_FILE}':
print(a['id']); break
" 2>/dev/null || true)
if [ -n "$ASSET_ID" ]; then
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
fi
done
# Upload both formats
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${PACKAGE_NAME}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${TAR_NAME}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
fi
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
else
SHA256=""
fi
# -- Build the new entry (canonical format matching release.yml) --
NEW_ENTRY=""
+9 -4
View File
@@ -34,16 +34,19 @@ const (
swaggerSpecPath = "templates/swagger/v1_json.tmpl"
openapi3OutPath = "templates/swagger/v1_openapi3_json.tmpl"
appSubUrlVar = "{{.SwaggerAppSubUrl}}"
appVerVar = "{{.SwaggerAppVer}}"
appSubUrlVar = "{{.SwaggerAppSubUrl}}"
appVerVar = "{{.SwaggerAppVer}}"
appNameVar = "{{.SwaggerAppName}}"
appSubUrlPlaceholder = "GITEA_APP_SUB_URL_PLACEHOLDER"
appVerPlaceholder = "0.0.0-gitea-placeholder"
appSubUrlPlaceholder = "GITEA_APP_SUB_URL_PLACEHOLDER"
appVerPlaceholder = "0.0.0-gitea-placeholder"
appNamePlaceholder = "GiteaAppNamePlaceholder"
)
var (
appSubUrlRe = regexp.MustCompile(regexp.QuoteMeta(appSubUrlVar))
appVerRe = regexp.MustCompile(regexp.QuoteMeta(appVerVar))
appNameRe = regexp.MustCompile(regexp.QuoteMeta(appNameVar))
enumScanDirs = []string{
"modules/structs",
@@ -70,6 +73,7 @@ func main() {
cleaned := appSubUrlRe.ReplaceAll(data, []byte(appSubUrlPlaceholder))
cleaned = appVerRe.ReplaceAll(cleaned, []byte(appVerPlaceholder))
cleaned = appNameRe.ReplaceAll(cleaned, []byte(appNamePlaceholder))
oas3, err := openapi3gen.Convert(cleaned, astEnumMap)
if err != nil {
@@ -87,6 +91,7 @@ func main() {
result := strings.ReplaceAll(string(out), appSubUrlPlaceholder, appSubUrlVar)
result = strings.ReplaceAll(result, appVerPlaceholder, appVerVar)
result = strings.ReplaceAll(result, appNamePlaceholder, appNameVar)
result = strings.TrimSpace(result)
if err := os.WriteFile(openapi3OutPath, []byte(result), 0o644); err != nil {
+25
View File
@@ -5,6 +5,7 @@ package db
import (
"fmt"
"strings"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
)
@@ -72,3 +73,27 @@ func (err ErrNotExist) Error() string {
func (err ErrNotExist) Unwrap() error {
return util.ErrNotExist
}
// IsErrDeadlock checks whether err is a database deadlock.
// MySQL returns error 1213 (ER_LOCK_DEADLOCK / SQLSTATE 40001).
// PostgreSQL returns SQLSTATE 40P01 with "deadlock detected".
// SQLite returns SQLITE_BUSY (error 5) with "database is locked".
func IsErrDeadlock(err error) bool {
if err == nil {
return false
}
msg := err.Error()
// MySQL / MariaDB: "Error 1213 (40001): Deadlock found when trying to get lock"
if strings.Contains(msg, "Error 1213") || strings.Contains(msg, "40001") {
return true
}
// PostgreSQL: "deadlock detected"
if strings.Contains(msg, "deadlock detected") {
return true
}
// SQLite: "database is locked"
if strings.Contains(msg, "database is locked") {
return true
}
return false
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsErrDeadlock(t *testing.T) {
tests := []struct {
name string
err error
want bool
}{
{name: "nil", err: nil, want: false},
{name: "unrelated", err: errors.New("connection refused"), want: false},
{name: "mysql 1213", err: errors.New("Error 1213 (40001): Deadlock found when trying to get lock; try restarting transaction"), want: true},
{name: "mysql sqlstate", err: errors.New("SQLSTATE 40001: serialization failure"), want: true},
{name: "postgres", err: errors.New("pq: deadlock detected"), want: true},
{name: "sqlite", err: errors.New("database is locked"), want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, IsErrDeadlock(tt.err))
})
}
}
+17
View File
@@ -8,6 +8,8 @@ import (
"strings"
"testing"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"github.com/stretchr/testify/assert"
)
@@ -66,6 +68,21 @@ func TestLocaleStore(t *testing.T) {
assert.Equal(t, "&lt;no-such&gt;", string(res))
}
func TestLocaleAppNameSubstitution(t *testing.T) {
setting.AppName = "TestApp"
ls := NewLocaleStore()
assert.NoError(t, ls.AddLocaleByJSON("lang1", "Lang1", []byte(`{"greeting":"Welcome to ${APP_NAME}","plain":"No placeholder here"}`), nil))
lang1, _ := ls.Locale("lang1")
assert.Equal(t, "Welcome to TestApp", lang1.TrString("greeting"))
assert.Equal(t, "No placeholder here", lang1.TrString("plain"))
// Verify it responds to runtime AppName changes
setting.AppName = "ChangedApp"
assert.Equal(t, "Welcome to ChangedApp", lang1.TrString("greeting"))
}
func TestLocaleStoreMoreSource(t *testing.T) {
testData1 := []byte(`
{
+5
View File
@@ -9,9 +9,11 @@ import (
"html"
"html/template"
"slices"
"strings"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
)
// This file implements the static LocaleStore that will not watch for changes
@@ -142,6 +144,9 @@ func (l *locale) TrString(trKey string, trArgs ...any) string {
if format == "" { // still missing, use the key itself
format = html.EscapeString(trKey)
}
if strings.Contains(format, "${APP_NAME}") {
format = strings.ReplaceAll(format, "${APP_NAME}", setting.AppName)
}
msg, err := Format(format, trArgs...)
if err != nil {
log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err)
+52 -52
View File
@@ -25,7 +25,7 @@
"enable_javascript": "This website requires JavaScript.",
"toc": "Table of Contents",
"licenses": "Licenses",
"return_to_gitea": "Return to MokoGitea",
"return_to_gitea": "Return to ${APP_NAME}",
"more_items": "More items",
"username": "Username",
"email": "Email Address",
@@ -222,7 +222,7 @@
"filter.string.asc": "AZ",
"filter.string.desc": "ZA",
"error.occurred": "An error occurred",
"error.report_message": "If you believe that this is a MokoGitea bug, please search for issues on <a href=\"%s\" target=\"_blank\">GitHub</a> or open a new issue if necessary.",
"error.report_message": "If you believe that this is a ${APP_NAME} bug, please search for issues on <a href=\"%s\" target=\"_blank\">GitHub</a> or open a new issue if necessary.",
"error.not_found": "The target couldn't be found.",
"error.permission_denied": "Permission denied.",
"error.network_error": "Network error",
@@ -230,16 +230,16 @@
"startpage.install": "Easy to install",
"startpage.install_desc": "Simply <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[1]s\">run the binary</a> for your platform, ship it with <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[2]s\">Docker</a>, or get it <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[3]s\">packaged</a>.",
"startpage.platform": "Cross-platform",
"startpage.platform_desc": "MokoGitea runs anywhere <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Go</a> can compile for: Windows, macOS, Linux, ARM, etc. Choose the one you love!",
"startpage.platform_desc": "${APP_NAME} runs anywhere <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Go</a> can compile for: Windows, macOS, Linux, ARM, etc. Choose the one you love!",
"startpage.lightweight": "Lightweight",
"startpage.lightweight_desc": "MokoGitea has low minimal requirements and can run on an inexpensive Raspberry Pi. Save your machine energy!",
"startpage.lightweight_desc": "${APP_NAME} has low minimal requirements and can run on an inexpensive Raspberry Pi. Save your machine energy!",
"startpage.license": "Open Source",
"startpage.license_desc": "Go get <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[1]s\">%[2]s</a>! Join us by <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[3]s\">contributing</a> to make this project even better. Don't hesitate to contribute!",
"install.install": "Installation",
"install.installing_desc": "Installing now, please wait…",
"install.title": "Initial Configuration",
"install.docker_helper": "If you run MokoGitea inside Docker, please read the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">documentation</a> before changing any settings.",
"install.require_db_desc": "MokoGitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).",
"install.docker_helper": "If you run ${APP_NAME} inside Docker, please read the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">documentation</a> before changing any settings.",
"install.require_db_desc": "${APP_NAME} requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).",
"install.db_title": "Database Settings",
"install.db_type": "Database Type",
"install.host": "Host",
@@ -250,12 +250,12 @@
"install.db_schema_helper": "Leave blank for database default (\"public\").",
"install.ssl_mode": "SSL",
"install.path": "Path",
"install.sqlite_helper": "File path for the SQLite3 database.<br>Enter an absolute path if you run MokoGitea as a service.",
"install.reinstall_error": "You are trying to install into an existing MokoGitea database",
"install.reinstall_confirm_message": "Re-installing with an existing MokoGitea database can cause multiple problems. In most cases, you should use your existing \"app.ini\" to run MokoGitea. If you know what you are doing, confirm the following:",
"install.sqlite_helper": "File path for the SQLite3 database.<br>Enter an absolute path if you run ${APP_NAME} as a service.",
"install.reinstall_error": "You are trying to install into an existing ${APP_NAME} database",
"install.reinstall_confirm_message": "Re-installing with an existing ${APP_NAME} database can cause multiple problems. In most cases, you should use your existing \"app.ini\" to run ${APP_NAME}. If you know what you are doing, confirm the following:",
"install.reinstall_confirm_check_1": "The data encrypted by the SECRET_KEY in app.ini may be lost: users may not be able to log in with 2FA/OTP and mirrors may not function correctly. By checking this box, you confirm that the current app.ini file contains the correct SECRET_KEY.",
"install.reinstall_confirm_check_2": "The repositories and settings may need to be resynchronized. By checking this box, you confirm that you will resynchronize the hooks for the repositories and authorized_keys file manually. You confirm that you will ensure that repository and mirror settings are correct.",
"install.reinstall_confirm_check_3": "You confirm that you are absolutely sure that this MokoGitea is running with the correct app.ini location and that you are sure that you have to re-install. You confirm that you acknowledge the above risks.",
"install.reinstall_confirm_check_3": "You confirm that you are absolutely sure that this ${APP_NAME} is running with the correct app.ini location and that you are sure that you have to re-install. You confirm that you acknowledge the above risks.",
"install.err_empty_db_path": "The SQLite3 database path cannot be empty.",
"install.no_admin_and_disable_registration": "You cannot disable user self-registration without creating an administrator account.",
"install.err_empty_admin_password": "The administrator password cannot be empty.",
@@ -271,14 +271,14 @@
"install.lfs_path": "Git LFS Root Path",
"install.lfs_path_helper": "Files tracked by Git LFS will be stored in this directory. Leave empty to disable.",
"install.run_user": "Run As Username",
"install.run_user_helper": "The operating system username that MokoGitea runs as, it must have write access to the data paths. This value is auto-detected and cannot be changed here. To use a different user, restart MokoGitea under that account.",
"install.run_user_helper": "The operating system username that ${APP_NAME} runs as, it must have write access to the data paths. This value is auto-detected and cannot be changed here. To use a different user, restart ${APP_NAME} under that account.",
"install.domain": "Server Domain",
"install.domain_helper": "Domain or host address for the server.",
"install.ssh_port": "SSH Server Port",
"install.ssh_port_helper": "Port number your SSH server listens on. Leave empty to disable.",
"install.http_port": "MokoGitea HTTP Listen Port",
"install.http_port_helper": "Port number the MokoGitea web server will listen on.",
"install.app_url": "MokoGitea Base URL",
"install.http_port": "${APP_NAME} HTTP Listen Port",
"install.http_port_helper": "Port number the ${APP_NAME} web server will listen on.",
"install.app_url": "${APP_NAME} Base URL",
"install.app_url_helper": "Base address for HTTP(S) clone URLs and email notifications.",
"install.log_root_path": "Log Path",
"install.log_root_path_helper": "Log files will be written to this directory.",
@@ -288,7 +288,7 @@
"install.smtp_port": "SMTP Port",
"install.smtp_from": "Send Email As",
"install.smtp_from_invalid": "The \"Send Email As\" address is invalid",
"install.smtp_from_helper": "Email address MokoGitea will use. Enter a plain email address or use the \"Name\" <email@example.com> format.",
"install.smtp_from_helper": "Email address ${APP_NAME} will use. Enter a plain email address or use the \"Name\" <email@example.com> format.",
"install.mailer_user": "SMTP Username",
"install.mailer_password": "SMTP Password",
"install.register_confirm": "Require Email Confirmation to Register",
@@ -311,7 +311,7 @@
"install.admin_password": "Password",
"install.confirm_password": "Confirm Password",
"install.admin_email": "Email Address",
"install.install_btn_confirm": "Install MokoGitea",
"install.install_btn_confirm": "Install ${APP_NAME}",
"install.test_git_failed": "Could not test 'git' command: %v",
"install.invalid_db_setting": "The database settings are invalid: %v",
"install.invalid_db_table": "The database table \"%s\" is invalid: %v",
@@ -385,7 +385,7 @@
"auth.forgot_password_title": "Forgot Password",
"auth.forgot_password": "Forgot password?",
"auth.need_account": "Need an account?",
"auth.sign_up_tip": "You are registering the first account in the system, which has administrator privileges. Please carefully remember your username and password. If you forget the username or password, please refer to the MokoGitea documentation to recover the account.",
"auth.sign_up_tip": "You are registering the first account in the system, which has administrator privileges. Please carefully remember your username and password. If you forget the username or password, please refer to the ${APP_NAME} documentation to recover the account.",
"auth.sign_up_now": "Register now.",
"auth.sign_up_successful": "Account was successfully created. Welcome!",
"auth.confirmation_mail_sent_prompt_ex": "A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.",
@@ -409,7 +409,7 @@
"auth.reset_password_helper": "Recover Account",
"auth.reset_password_wrong_user": "You are signed in as %s, but the account recovery link is meant for %s",
"auth.password_too_short": "Password length cannot be less than %d characters.",
"auth.non_local_account": "Non-local users cannot update their password through the MokoGitea web interface.",
"auth.non_local_account": "Non-local users cannot update their password through the ${APP_NAME} web interface.",
"auth.verify": "Verify",
"auth.scratch_code": "Scratch code",
"auth.use_scratch_code": "Use a scratch code",
@@ -726,7 +726,7 @@
"settings.retype_new_password": "Confirm New Password",
"settings.password_incorrect": "The current password is incorrect.",
"settings.change_password_success": "Your password has been updated. Sign in using your new password from now on.",
"settings.password_change_disabled": "Non-local users cannot update their password through the MokoGitea web interface.",
"settings.password_change_disabled": "Non-local users cannot update their password through the ${APP_NAME} web interface.",
"settings.emails": "Email Addresses",
"settings.manage_emails": "Manage Email Addresses",
"settings.manage_themes": "Select default theme",
@@ -734,7 +734,7 @@
"settings.email_desc": "Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.",
"settings.theme_desc": "This will be your default theme across the site.",
"settings.theme_colorblindness_help": "Color blindness Theme Support",
"settings.theme_colorblindness_prompt": "MokoGitea only has a few themes with basic color blindness support, which only have a few colors defined. The work is still in progress. More improvements could be made by defining more colors in the theme CSS files.",
"settings.theme_colorblindness_prompt": "${APP_NAME} only has a few themes with basic color blindness support, which only have a few colors defined. The work is still in progress. More improvements could be made by defining more colors in the theme CSS files.",
"settings.primary": "Primary",
"settings.activated": "Activated",
"settings.requires_activation": "Requires activation",
@@ -843,7 +843,7 @@
"settings.unbind_success": "The social account has been removed successfully.",
"settings.manage_access_token": "Manage Access Tokens",
"settings.generate_new_token": "Generate New Token",
"settings.tokens_desc": "These tokens grant access to your account using the Gitea API.",
"settings.tokens_desc": "These tokens grant access to your account using the ${APP_NAME} API.",
"settings.token_name": "Token Name",
"settings.generate_token": "Generate Token",
"settings.generate_token_success": "Your new token has been generated. Copy it now as it will not be shown again.",
@@ -869,7 +869,7 @@
"settings.permissions_list": "Permissions:",
"settings.manage_oauth2_applications": "Manage OAuth2 Applications",
"settings.edit_oauth2_application": "Edit OAuth2 Application",
"settings.oauth2_applications_desc": "OAuth2 applications enable your third-party application to securely authenticate users at this MokoGitea instance.",
"settings.oauth2_applications_desc": "OAuth2 applications enable your third-party application to securely authenticate users at this ${APP_NAME} instance.",
"settings.remove_oauth2_application": "Remove OAuth2 Application",
"settings.remove_oauth2_application_desc": "Removing an OAuth2 application will revoke access to all signed access tokens. Continue?",
"settings.remove_oauth2_application_success": "The application has been deleted.",
@@ -890,9 +890,9 @@
"settings.oauth2_application_edit": "Edit",
"settings.oauth2_application_create_description": "OAuth2 applications give your third-party application access to user accounts on this instance.",
"settings.oauth2_application_remove_description": "Removing an OAuth2 application will prevent it from accessing authorized user accounts on this instance. Continue?",
"settings.oauth2_application_locked": "MokoGitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected behavior, these can neither be edited nor removed. Please refer to the OAuth2 documentation for more information.",
"settings.oauth2_application_locked": "${APP_NAME} pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected behavior, these can neither be edited nor removed. Please refer to the OAuth2 documentation for more information.",
"settings.authorized_oauth2_applications": "Authorized OAuth2 Applications",
"settings.authorized_oauth2_applications_description": "You have granted access to your personal MokoGitea account to these third-party applications. Please revoke access for applications you no longer need.",
"settings.authorized_oauth2_applications_description": "You have granted access to your personal ${APP_NAME} account to these third-party applications. Please revoke access for applications you no longer need.",
"settings.revoke_key": "Revoke",
"settings.revoke_oauth2_grant": "Revoke Access",
"settings.revoke_oauth2_grant_description": "Revoking access for this third-party application will prevent this application from accessing your data. Are you sure?",
@@ -923,11 +923,11 @@
"settings.webauthn_key_loss_warning": "If you lose your security keys, you will lose access to your account.",
"settings.webauthn_alternative_tip": "You may want to configure an additional authentication method.",
"settings.manage_account_links": "Manage Linked Accounts",
"settings.manage_account_links_desc": "These external accounts are linked to your MokoGitea account.",
"settings.account_links_not_available": "No external accounts are currently linked to your MokoGitea account.",
"settings.manage_account_links_desc": "These external accounts are linked to your ${APP_NAME} account.",
"settings.account_links_not_available": "No external accounts are currently linked to your ${APP_NAME} account.",
"settings.link_account": "Link Account",
"settings.remove_account_link": "Remove Linked Account",
"settings.remove_account_link_desc": "Removing a linked account will revoke its access to your MokoGitea account. Continue?",
"settings.remove_account_link_desc": "Removing a linked account will revoke its access to your ${APP_NAME} account. Continue?",
"settings.remove_account_link_success": "The linked account has been removed.",
"settings.hooks.desc": "Add webhooks which will be triggered for <strong>all repositories</strong> that you own.",
"settings.orgs_none": "You are not a member of any organizations.",
@@ -943,7 +943,7 @@
"settings.email_notifications.disable": "Disable Email Notifications",
"settings.email_notifications.submit": "Set Email Preference",
"settings.email_notifications.andyourown": "And Your Own Notifications",
"settings.email_notifications.actions.desc": "Notifications for workflow runs on repositories set up with <a target=\"_blank\" href=\"%s\">Gitea Actions</a>.",
"settings.email_notifications.actions.desc": "Notifications for workflow runs on repositories set up with <a target=\"_blank\" href=\"%s\">${APP_NAME} Actions</a>.",
"settings.email_notifications.actions.failure_only": "Only notify for failed workflow runs",
"settings.visibility": "User visibility",
"settings.visibility.public": "Public",
@@ -1125,7 +1125,7 @@
"repo.migrate.github.description": "Migrate data from github.com or other GitHub instances.",
"repo.migrate.git.description": "Migrate a repository only from any Git service.",
"repo.migrate.gitlab.description": "Migrate data from gitlab.com or other GitLab instances.",
"repo.migrate.gitea.description": "Migrate data from gitea.com or other Gitea instances.",
"repo.migrate.gitea.description": "Migrate data from other ${APP_NAME} instances.",
"repo.migrate.gogs.description": "Migrate data from notabug.org or other Gogs instances.",
"repo.migrate.onedev.description": "Migrate data from code.onedev.io or other OneDev instances.",
"repo.migrate.codebase.description": "Migrate data from codebasehq.com.",
@@ -1891,7 +1891,7 @@
"repo.pulls.cmd_instruction_checkout_title": "Checkout",
"repo.pulls.cmd_instruction_checkout_desc": "From your project repository, check out a new branch and test the changes.",
"repo.pulls.cmd_instruction_merge_title": "Merge",
"repo.pulls.cmd_instruction_merge_desc": "Merge the changes and update on MokoGitea.",
"repo.pulls.cmd_instruction_merge_desc": "Merge the changes and update on ${APP_NAME}.",
"repo.pulls.cmd_instruction_merge_warning": "Warning: This operation cannot merge pull request because \"autodetect manual merge\" is not enabled.",
"repo.pulls.clear_merge_message": "Clear merge message",
"repo.pulls.clear_merge_message_hint": "Clearing the merge message will only remove the commit message content and keep generated git trailers such as \"Co-Authored-By…\".",
@@ -2199,11 +2199,11 @@
"repo.settings.trust_model.collaborator.long": "Collaborator: Trust signatures by collaborators",
"repo.settings.trust_model.collaborator.desc": "Valid signatures by collaborators of this repository will be marked \"trusted\", whether they match the committer or not. Otherwise, valid signatures will be marked \"untrusted\" if the signature matches the committer and \"unmatched\" if not.",
"repo.settings.trust_model.committer": "Committer",
"repo.settings.trust_model.committer.long": "Committer: Trust signatures that match committers. This matches GitHub's behavior and will force commits signed by MokoGitea to have MokoGitea as the committer.",
"repo.settings.trust_model.committer.desc": "Valid signatures will only be marked \"trusted\" if they match the committer, otherwise they will be marked \"unmatched\". This forces MokoGitea to be the committer on signed commits, with the actual committer marked as Co-authored-by: and Co-committed-by: trailer in the commit. The default MokoGitea key must match a user in the database.",
"repo.settings.trust_model.committer.long": "Committer: Trust signatures that match committers. This matches GitHub's behavior and will force commits signed by ${APP_NAME} to have ${APP_NAME} as the committer.",
"repo.settings.trust_model.committer.desc": "Valid signatures will only be marked \"trusted\" if they match the committer, otherwise they will be marked \"unmatched\". This forces ${APP_NAME} to be the committer on signed commits, with the actual committer marked as Co-authored-by: and Co-committed-by: trailer in the commit. The default ${APP_NAME} key must match a user in the database.",
"repo.settings.trust_model.collaboratorcommitter": "Collaborator+Committer",
"repo.settings.trust_model.collaboratorcommitter.long": "Collaborator+Committer: Trust signatures by collaborators which match the committer",
"repo.settings.trust_model.collaboratorcommitter.desc": "Valid signatures by collaborators of this repository will be marked \"trusted\" if they match the committer. Otherwise, valid signatures will be marked \"untrusted\" if the signature matches the committer and \"unmatched\" otherwise. This will force MokoGitea to be marked as the committer on signed commits, with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default MokoGitea key must match a user in the database.",
"repo.settings.trust_model.collaboratorcommitter.desc": "Valid signatures by collaborators of this repository will be marked \"trusted\" if they match the committer. Otherwise, valid signatures will be marked \"untrusted\" if the signature matches the committer and \"unmatched\" otherwise. This will force ${APP_NAME} to be marked as the committer on signed commits, with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default ${APP_NAME} key must match a user in the database.",
"repo.settings.wiki_delete": "Delete Wiki Data",
"repo.settings.wiki_delete_desc": "Deleting repository wiki data is permanent and cannot be undone.",
"repo.settings.wiki_delete_notices_1": "- This will permanently delete and disable the repository wiki for %s.",
@@ -2240,7 +2240,7 @@
"repo.settings.remove_team_success": "The team's access to the repository has been removed.",
"repo.settings.add_webhook": "Add Webhook",
"repo.settings.add_webhook.invalid_channel_name": "Webhook channel name cannot be empty and cannot contain only a # character.",
"repo.settings.hooks_desc": "Webhooks automatically make HTTP POST requests to a server when certain MokoGitea events trigger. Read more in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks guide</a>.",
"repo.settings.hooks_desc": "Webhooks automatically make HTTP POST requests to a server when certain ${APP_NAME} events trigger. Read more in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks guide</a>.",
"repo.settings.webhook_deletion": "Remove Webhook",
"repo.settings.webhook_deletion_desc": "Removing a webhook deletes its settings and delivery history. Continue?",
"repo.settings.webhook_deletion_success": "The webhook has been removed.",
@@ -2258,7 +2258,7 @@
"repo.settings.githooks_desc": "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations.",
"repo.settings.githook_edit_desc": "If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.",
"repo.settings.update_githook": "Update Hook",
"repo.settings.add_webhook_desc": "MokoGitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks guide</a>.",
"repo.settings.add_webhook_desc": "${APP_NAME} will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks guide</a>.",
"repo.settings.payload_url": "Target URL",
"repo.settings.http_method": "HTTP Method",
"repo.settings.content_type": "POST Content Type",
@@ -2326,9 +2326,9 @@
"repo.settings.event_pull_request_merge": "Pull Request Merge",
"repo.settings.event_header_workflow": "Workflow Events",
"repo.settings.event_workflow_run": "Workflow Run",
"repo.settings.event_workflow_run_desc": "Gitea Actions Workflow run queued, waiting, in progress, or completed.",
"repo.settings.event_workflow_run_desc": "${APP_NAME} Actions Workflow run queued, waiting, in progress, or completed.",
"repo.settings.event_workflow_job": "Workflow Jobs",
"repo.settings.event_workflow_job_desc": "Gitea Actions Workflow job queued, waiting, in progress, or completed.",
"repo.settings.event_workflow_job_desc": "${APP_NAME} Actions Workflow job queued, waiting, in progress, or completed.",
"repo.settings.event_package": "Package",
"repo.settings.event_package_desc": "Package created or deleted in a repository.",
"repo.settings.branch_filter": "Branch filter",
@@ -2349,7 +2349,7 @@
"repo.settings.slack_domain": "Domain",
"repo.settings.slack_channel": "Channel",
"repo.settings.add_web_hook_desc": "Integrate <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">%s</a> into your repository.",
"repo.settings.web_hook_name_gitea": "MokoGitea",
"repo.settings.web_hook_name_gitea": "${APP_NAME}",
"repo.settings.web_hook_name_gogs": "Gogs",
"repo.settings.web_hook_name_slack": "Slack",
"repo.settings.web_hook_name_discord": "Discord",
@@ -2634,7 +2634,7 @@
"repo.release.delete_release": "Delete Release",
"repo.release.delete_tag": "Delete Tag",
"repo.release.deletion": "Delete Release",
"repo.release.deletion_desc": "Deleting a release only removes it from MokoGitea. It will not affect the Git tag, the contents of your repository or its history. Continue?",
"repo.release.deletion_desc": "Deleting a release only removes it from ${APP_NAME}. It will not affect the Git tag, the contents of your repository or its history. Continue?",
"repo.release.deletion_success": "The release has been deleted.",
"repo.release.deletion_tag_desc": "Will delete this tag from repository. Repository contents and history remain unchanged. Continue?",
"repo.release.deletion_tag_success": "The tag has been deleted.",
@@ -2913,7 +2913,7 @@
"admin.last_page": "Last",
"admin.total": "Total: %d",
"admin.settings": "Admin Settings",
"admin.dashboard.new_version_hint": "MokoGitea %s is now available, you are running %s. Check <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">the blog</a> for more details.",
"admin.dashboard.new_version_hint": "${APP_NAME} %s is now available, you are running %s. Check <a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">the blog</a> for more details.",
"admin.dashboard.statistic": "Summary",
"admin.dashboard.maintenance_operations": "Maintenance Operations",
"admin.dashboard.system_status": "System Status",
@@ -2949,8 +2949,8 @@
"admin.dashboard.deleted_branches_cleanup": "Clean up deleted branches",
"admin.dashboard.update_migration_poster_id": "Update migration poster IDs",
"admin.dashboard.git_gc_repos": "Garbage-collect all repositories",
"admin.dashboard.resync_all_sshkeys": "Update the '.ssh/authorized_keys' file with MokoGitea SSH keys",
"admin.dashboard.resync_all_sshprincipals": "Update the '.ssh/authorized_principals' file with MokoGitea SSH principals",
"admin.dashboard.resync_all_sshkeys": "Update the '.ssh/authorized_keys' file with ${APP_NAME} SSH keys",
"admin.dashboard.resync_all_sshprincipals": "Update the '.ssh/authorized_principals' file with ${APP_NAME} SSH principals",
"admin.dashboard.resync_all_hooks": "Resynchronize git hooks of all repositories (pre-receive, update, post-receive, proc-receive, ...)",
"admin.dashboard.reinit_missing_repos": "Reinitialize all missing Git repositories for which records exist",
"admin.dashboard.sync_external_users": "Synchronize external user data",
@@ -3030,7 +3030,7 @@
"admin.users.is_admin": "Is Administrator",
"admin.users.is_restricted": "Is Restricted",
"admin.users.allow_git_hook": "May Create Git Hooks",
"admin.users.allow_git_hook_tooltip": "Git Hooks are executed as the OS user running MokoGitea and will have the same level of host access. As a result, users with this special Git Hook privilege can access and modify all MokoGitea repositories as well as the database used by MokoGitea. Consequently they are also able to gain MokoGitea administrator privileges.",
"admin.users.allow_git_hook_tooltip": "Git Hooks are executed as the OS user running ${APP_NAME} and will have the same level of host access. As a result, users with this special Git Hook privilege can access and modify all ${APP_NAME} repositories as well as the database used by ${APP_NAME}. Consequently they are also able to gain ${APP_NAME} administrator privileges.",
"admin.users.allow_import_local": "May Import Local Repositories",
"admin.users.allow_create_organization": "May Create Organizations",
"admin.users.update_profile": "Update User Account",
@@ -3100,11 +3100,11 @@
"admin.packages.size": "Size",
"admin.packages.published": "Published",
"admin.defaulthooks": "Default Webhooks",
"admin.defaulthooks.desc": "Webhooks automatically make HTTP POST requests to a server when certain MokoGitea events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the <a target=\"_blank\" rel=\"noopener\" href=\"%s\">webhooks guide</a>.",
"admin.defaulthooks.desc": "Webhooks automatically make HTTP POST requests to a server when certain ${APP_NAME} events trigger. Webhooks defined here are defaults and will be copied into all new repositories. Read more in the <a target=\"_blank\" rel=\"noopener\" href=\"%s\">webhooks guide</a>.",
"admin.defaulthooks.add_webhook": "Add Default Webhook",
"admin.defaulthooks.update_webhook": "Update Default Webhook",
"admin.systemhooks": "System Webhooks",
"admin.systemhooks.desc": "Webhooks automatically make HTTP POST requests to a server when certain MokoGitea events trigger. Webhooks defined here will act on all repositories on the system, so please consider any performance implications this may have. Read more in the <a target=\"_blank\" rel=\"noopener\" href=\"%s\">webhooks guide</a>.",
"admin.systemhooks.desc": "Webhooks automatically make HTTP POST requests to a server when certain ${APP_NAME} events trigger. Webhooks defined here will act on all repositories on the system, so please consider any performance implications this may have. Read more in the <a target=\"_blank\" rel=\"noopener\" href=\"%s\">webhooks guide</a>.",
"admin.systemhooks.add_webhook": "Add System Webhook",
"admin.systemhooks.update_webhook": "Update System Webhook",
"admin.auths.auth_manage_panel": "Authentication Source Management",
@@ -3125,7 +3125,7 @@
"admin.auths.user_base": "User Search Base",
"admin.auths.user_dn": "User DN",
"admin.auths.attribute_username": "Username Attribute",
"admin.auths.attribute_username_placeholder": "Leave empty to use the username entered in MokoGitea.",
"admin.auths.attribute_username_placeholder": "Leave empty to use the username entered in ${APP_NAME}.",
"admin.auths.attribute_name": "First Name Attribute",
"admin.auths.attribute_surname": "Surname Attribute",
"admin.auths.attribute_mail": "Email Attribute",
@@ -3232,7 +3232,7 @@
"admin.auths.invalid_openIdConnectAutoDiscoveryURL": "Invalid Auto Discovery URL (this must be a valid URL starting with http:// or https://)",
"admin.config.server_config": "Server Configuration",
"admin.config.app_name": "Site Title",
"admin.config.app_ver": "MokoGitea Version",
"admin.config.app_ver": "${APP_NAME} Version",
"admin.config.custom_conf": "Configuration File Path",
"admin.config.custom_file_root_path": "Custom File Root Path",
"admin.config.disable_router_log": "Disable Router Log",
@@ -3272,7 +3272,7 @@
"admin.config.service_config": "Service Configuration",
"admin.config.register_email_confirm": "Require Email Confirmation to Register",
"admin.config.disable_register": "Disable Self-Registration",
"admin.config.allow_only_internal_registration": "Allow Registration Only Through MokoGitea itself",
"admin.config.allow_only_internal_registration": "Allow Registration Only Through ${APP_NAME} itself",
"admin.config.allow_only_external_registration": "Allow Registration Only Through External Services",
"admin.config.enable_openid_signup": "Enable OpenID Self-Registration",
"admin.config.enable_openid_signin": "Enable OpenID Sign-In",
@@ -3414,11 +3414,11 @@
"admin.self_check.no_problem_found": "No problem found yet.",
"admin.self_check.startup_warnings": "Startup warnings:",
"admin.self_check.database_collation_mismatch": "Expect database to use collation: %s",
"admin.self_check.database_collation_case_insensitive": "Database is using collation %s, which is a case-insensitive collation. Although MokoGitea could work with it, there might be some rare cases which don't work as expected.",
"admin.self_check.database_collation_case_insensitive": "Database is using collation %s, which is a case-insensitive collation. Although ${APP_NAME} could work with it, there might be some rare cases which don't work as expected.",
"admin.self_check.database_inconsistent_collation_columns": "Database is using collation %s, but these columns are using mismatched collations. This might cause some unexpected problems.",
"admin.self_check.database_fix_mysql": "For MySQL/MariaDB users, you could use the \"gitea doctor convert\" command to fix the collation problems, or you could also fix the problem manually with \"ALTER ... COLLATE ...\" SQL queries.",
"admin.self_check.database_fix_mssql": "For MSSQL users, you could only fix the problem manually with \"ALTER ... COLLATE ...\" SQL queries at the moment.",
"admin.self_check.location_origin_mismatch": "Current URL (%[1]s) doesn't match the URL seen by MokoGitea (%[2]s). If you are using a reverse proxy, please make sure the \"Host\" and \"X-Forwarded-Proto\" headers are set correctly.",
"admin.self_check.location_origin_mismatch": "Current URL (%[1]s) doesn't match the URL seen by ${APP_NAME} (%[2]s). If you are using a reverse proxy, please make sure the \"Host\" and \"X-Forwarded-Proto\" headers are set correctly.",
"action.create_repo": "created repository <a href=\"%s\">%s</a>",
"action.rename_repo": "renamed repository from <code>%[1]s</code> to <a href=\"%[2]s\">%[3]s</a>",
"action.commit_repo": "pushed to <a href=\"%[2]s\">%[3]s</a> at <a href=\"%[1]s\">%[4]s</a>",
@@ -3771,8 +3771,8 @@
"actions.runs.status_no_select": "All status",
"actions.runs.no_results": "No results matched.",
"actions.runs.no_workflows": "There are no workflows yet.",
"actions.runs.no_workflows.quick_start": "Don't know how to start with Gitea Actions? See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">the quick start guide</a>.",
"actions.runs.no_workflows.documentation": "For more information on Gitea Actions, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">the documentation</a>.",
"actions.runs.no_workflows.quick_start": "Don't know how to start with ${APP_NAME} Actions? See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">the quick start guide</a>.",
"actions.runs.no_workflows.documentation": "For more information on ${APP_NAME} Actions, see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">the documentation</a>.",
"actions.runs.no_runs": "The workflow has no runs yet.",
"actions.runs.empty_commit_message": "(empty commit message)",
"actions.runs.expire_log_message": "Logs have been purged because they were too old.",
+3 -3
View File
@@ -147,11 +147,11 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
if !ctx.IsSigned {
// TODO: support digit auth - which would be Authorization header with digit
if setting.OAuth2.Enabled {
// `Basic realm="Gitea"` tells the GCM to use builtin OAuth2 application: https://github.com/git-ecosystem/git-credential-manager/pull/1442
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
// `Basic realm="<AppName>"` tells the GCM to use builtin OAuth2 application: https://github.com/git-ecosystem/git-credential-manager/pull/1442
ctx.Resp.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, setting.AppName))
} else {
// If OAuth2 is disabled, then use another realm to avoid GCM OAuth2 attempt
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea (Basic Auth)"`)
ctx.Resp.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s (Basic Auth)"`, setting.AppName))
}
ctx.HTTPError(http.StatusUnauthorized)
return nil
+2
View File
@@ -13,6 +13,7 @@ import (
// SwaggerV1Json render swagger v1 json
func SwaggerV1Json(ctx *context.Context) {
ctx.Data["SwaggerAppVer"] = template.HTML(template.JSEscapeString(setting.AppVer))
ctx.Data["SwaggerAppName"] = template.HTML(template.JSEscapeString(setting.AppName))
ctx.Data["SwaggerAppSubUrl"] = setting.AppSubURL // it is JS-safe
ctx.JSONTemplate("swagger/v1_json")
}
@@ -20,6 +21,7 @@ func SwaggerV1Json(ctx *context.Context) {
// OpenAPI3Json render OpenAPI 3.0 json (auto-converted from Swagger 2.0)
func OpenAPI3Json(ctx *context.Context) {
ctx.Data["SwaggerAppVer"] = template.HTML(template.JSEscapeString(setting.AppVer))
ctx.Data["SwaggerAppName"] = template.HTML(template.JSEscapeString(setting.AppName))
ctx.Data["SwaggerAppSubUrl"] = setting.AppSubURL // it is JS-safe
ctx.JSONTemplate("swagger/v1_openapi3_json")
}
+50 -1
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"slices"
"strings"
"time"
actions_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/actions"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
@@ -344,7 +345,7 @@ func handleWorkflows(
run.NeedApproval = need
if err := PrepareRunAndInsert(ctx, dwf.Content, run, nil); err != nil {
if err := prepareRunAndInsertWithRetry(ctx, dwf.Content, run); err != nil {
log.Error("PrepareRunAndInsert: %v", err)
continue
}
@@ -352,6 +353,54 @@ func handleWorkflows(
return nil
}
// prepareRunAndInsertWithRetry wraps PrepareRunAndInsert with retries on
// database deadlocks. When multiple workflow runs are inserted for the same
// event (e.g. several workflows triggered by a single pull_request), each
// InsertRun transaction acquires an X-lock on the repository row (via
// UpdateRepoRunsNumbers) and an index lock on action_run. Two concurrent
// transactions can deadlock when each holds one lock and waits for the other.
// InnoDB resolves this by killing the lighter transaction, but handleWorkflows
// only logged the error and moved on — silently dropping the workflow run.
// Retrying the insert is safe because the rolled-back transaction left no
// partial state.
func prepareRunAndInsertWithRetry(ctx context.Context, content []byte, run *actions_model.ActionRun) error {
const maxRetries = 3
backoff := 50 * time.Millisecond
// Save original values that InsertRun mutates inside its transaction.
// On deadlock rollback these become stale and must be reset before retry.
origTitle := run.Title
var err error
for attempt := range maxRetries {
if err = PrepareRunAndInsert(ctx, content, run, nil); err == nil {
return nil
}
if !db.IsErrDeadlock(err) {
return err
}
log.Warn("PrepareRunAndInsert deadlock (attempt %d/%d) for workflow %s in repo %d, retrying: %v",
attempt+1, maxRetries, run.WorkflowID, run.RepoID, err)
// Reset fields that InsertRun sets inside the (now rolled-back) transaction
// so the next attempt starts clean.
run.ID = 0
run.Index = 0
run.Status = actions_model.StatusWaiting
run.Title = origTitle
run.ConcurrencyGroup = ""
run.ConcurrencyCancel = false
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
}
backoff *= 2
}
return fmt.Errorf("deadlock persisted after %d retries: %w", maxRetries, err)
}
func newNotifyInputFromIssue(issue *issues_model.Issue, event webhook_module.HookEventType) *notifyInput {
return newNotifyInput(issue.Repo, issue.Poster, event)
}
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="admin-setting-content">
{{if .NeedUpdate}}
<div class="ui positive message">
<div class="header">{{svg "octicon-info"}} MokoGitea Update Available</div>
<div class="header">{{svg "octicon-info"}} {{AppName}} Update Available</div>
<p>A new version <strong>{{.LatestVersion}}</strong> is available{{if .UpdateChannel}} ({{.UpdateChannel}} channel){{end}}.
{{if .ReleaseURL}}<a href="{{.ReleaseURL}}" target="_blank" rel="noopener noreferrer">View release notes</a>{{end}}</p>
{{if .DockerImage}}<p><code>docker pull {{.DockerImage}}</code></p>{{end}}
+1 -1
View File
@@ -1,7 +1,7 @@
<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">
<div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}">
{{if ShowFooterPoweredBy}}
<a target="_blank" href="https://git.mokoconsulting.tech/MokoConsulting/MokoGitea">{{ctx.Locale.Tr "powered_by" "MokoGitea"}}</a>
<a target="_blank" href="https://git.mokoconsulting.tech/MokoConsulting/MokoGitea">{{ctx.Locale.Tr "powered_by" AppName}}</a>
{{end}}
{{if (or .ShowFooterVersion .PageIsAdmin)}}
<span>
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
{{ctx.HeadMetaContentSecurityPolicy}}
<title>Gitea API</title>
<title>{{AppName}} API</title>
<link rel="stylesheet" href="{{ctx.CurrentWebTheme.PublicAssetURI}}">
{{/* HINT: SWAGGER-CSS-IMPORT: import swagger styles ahead to avoid UI flicker (e.g.: the swagger-back-link element) */}}
<link rel="stylesheet" href="{{AssetURI "css/swagger.css"}}">
+2 -2
View File
@@ -11,8 +11,8 @@
],
"swagger": "2.0",
"info": {
"description": "This documentation describes the Gitea API.",
"title": "Gitea API",
"description": "This documentation describes the {{.SwaggerAppName}} API.",
"title": "{{.SwaggerAppName}} API",
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/MIT"
+2 -2
View File
@@ -10588,12 +10588,12 @@
}
},
"info": {
"description": "This documentation describes the Gitea API.",
"description": "This documentation describes the {{.SwaggerAppName}} API.",
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/MIT"
},
"title": "Gitea API",
"title": "{{.SwaggerAppName}} API",
"version": "{{.SwaggerAppVer}}"
},
"openapi": "3.0.3",