Compare commits

...

285 Commits

Author SHA1 Message Date
Jonathan Miller 79cc30e9a8 fix: use ctx.APIError instead of ctx.Error in licensing endpoints
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 10s
PR RC Release / Build RC Release (pull_request) Failing after 1m13s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m19s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 2m0s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 40s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m1s
APIContext doesn't have an Error method — use APIError(status, msg)
which is the correct 2-arg pattern for Gitea API error responses.
2026-06-20 21:47:02 -05:00
Jonathan Miller e3949077b0 Merge remote-tracking branch 'origin/main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: PR Check / Secret Scan (pull_request) Successful in 43s
PR RC Release / Build RC Release (pull_request) Failing after 41s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 42s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 44s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 1m35s
# Conflicts:
#	.mokogitea/manifest.xml
#	.mokogitea/workflows/issue-branch.yml
#	CHANGELOG.md
2026-06-20 21:35:55 -05:00
jmiller e469b4a857 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-21 01:28:46 +00:00
jmiller acae63f727 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-06-21 01:28:38 +00:00
jmiller e71ab8415f chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-21 01:28:29 +00:00
jmiller 03ce66a4f4 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-21 01:28:22 +00:00
jmiller deafaeca65 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-21 01:28:16 +00:00
jmiller 5e74c22609 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-21 01:28:10 +00:00
gitea-actions[bot] 03f881c746 chore(version): pre-release bump to 06.18.12-dev [skip ci] 2026-06-21 01:15:16 +00:00
Jonathan Miller 3a405033ae feat: add product tier admin UI with CRUD and license counts (#627)
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m35s
Admin page at /-/admin/license-tiers for managing product tiers:
- Tier list with key, name, repos, max domains, license count, sort order
- Create new tier form with repo input
- Delete tier (blocked if active licenses exist)
- Nav item added to admin sidebar
2026-06-20 20:14:24 -05:00
gitea-actions[bot] 034795951f chore(version): pre-release bump to 06.18.11-dev [skip ci] 2026-06-21 01:04:49 +00:00
Jonathan Miller 1d1b867df5 feat: add license management API — admin CRUD, user self-service, tier management (#624)
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m22s
Admin: POST/GET/PATCH/DELETE /api/v1/licensing/licenses (reqSiteAdmin)
User: GET /api/v1/licensing/my/licenses, manage domains (reqToken)
Tiers: GET/POST/PATCH/DELETE /api/v1/licensing/tiers (reqSiteAdmin)

Includes pagination, entitlement/activation detail in GET, tier change
triggers entitlement rebuild, delete-tier blocked if active licenses exist.
2026-06-20 20:04:15 -05:00
gitea-actions[bot] 63b599f62c chore(version): pre-release bump to 06.18.10-dev [skip ci] 2026-06-21 01:00:07 +00:00
Jonathan Miller 5bd449017c feat: add signed download endpoint with ed25519 tokens (#622)
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m20s
GET /api/v1/licensing/download/{product}/{version}.zip?token=XXX&expires=YYY&dlid=ZZZ

ed25519 keypair auto-generated on first use, stored in Gitea data dir.
Update XML endpoint now generates signed URLs with 5-minute TTL.
Download verifies signature + expiry + DLID + entitlement before serving
the release ZIP attachment. Downloads logged to audit trail.
2026-06-20 19:59:37 -05:00
gitea-actions[bot] fe3de3fbff chore(version): pre-release bump to 06.18.09-dev [skip ci] 2026-06-21 00:52:30 +00:00
Jonathan Miller 3e909df6d4 feat: add license validation API — public validate + authenticated status (#623)
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m16s
GET /api/v1/licensing/validate?dlid=XXX&product=YYY&domain=ZZZ (public)
GET /api/v1/licensing/{dlid}/status (authenticated, reqToken)

Public endpoint returns valid/invalid with reason codes for Joomla plugin
and external integration use. Authenticated endpoint returns full license
detail with entitlement list and domain usage for admin dashboards.
2026-06-20 19:51:50 -05:00
jmiller add37d07dc chore: delete manifest.xml (no longer used)
Deploy MokoGitea / deploy (push) Successful in 3m45s
2026-06-21 00:19:46 +00:00
jmiller 778ec0652a Merge pull request 'feat: DLID licensing system — tables, models, update endpoint' (#659) from rc into main
Deploy MokoGitea / deploy (push) Successful in 5m38s
2026-06-21 00:17:54 +00:00
gitea-actions[bot] e677c2e0bd chore(version): pre-release bump to 06.20.00-rc [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m44s
2026-06-21 00:16:41 +00:00
Jonathan Miller 8d03a4afbc Merge remote-tracking branch 'origin/main' into rc
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 15s
Universal: PR Check / Secret Scan (pull_request) Successful in 40s
PR RC Release / Build RC Release (pull_request) Failing after 37s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 4m32s
# Conflicts:
#	.mokogitea/manifest.xml
#	.mokogitea/workflows/issue-branch.yml
#	CHANGELOG.md
2026-06-20 19:15:33 -05:00
Jonathan Miller 5f81837fc2 chore: final CI trigger [skip bump]
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
PR RC Release / Build RC Release (pull_request) Failing after 45s
Universal: PR Check / Secret Scan (pull_request) Successful in 45s
2026-06-20 19:09:06 -05:00
gitea-actions[bot] cc740ca8c5 chore(version): pre-release bump to 06.27.00-rc [skip ci] 2026-06-21 00:05:52 +00:00
Jonathan Miller 407cd2e15d fix: remove Build RC Package and Report Issues jobs from pr-check (redundant, causes stuck queue)
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 37s
PR RC Release / Build RC Release (pull_request) Failing after 37s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m37s
2026-06-20 19:04:59 -05:00
Jonathan Miller 4b6779da3b chore: retrigger after queue cleanup [skip bump]
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
PR RC Release / Build RC Release (pull_request) Failing after 50s
Universal: PR Check / Secret Scan (pull_request) Successful in 50s
2026-06-20 18:57:44 -05:00
jmiller 498a51833c chore: sync pr-check.yml from Template-Go [skip ci] 2026-06-20 23:46:24 +00:00
jmiller 7fc4ffcb14 chore: sync gitleaks.yml from Template-Go [skip ci] 2026-06-20 23:46:18 +00:00
jmiller 22662af75a chore: sync ci-generic.yml from Template-Go [skip ci] 2026-06-20 23:46:13 +00:00
Jonathan Miller 3002f02b2d fix: add [skip bump] guard to pre-release to prevent CI cycle
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 40s
PR RC Release / Build RC Release (pull_request) Failing after 39s
2026-06-20 18:33:53 -05:00
gitea-actions[bot] cb67713ee6 chore(version): pre-release bump to 06.26.00-rc [skip ci] 2026-06-20 23:33:03 +00:00
Jonathan Miller 1d7792ee0f chore: trigger PR checks [skip bump]
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Universal: PR Check / Secret Scan (pull_request) Successful in 42s
PR RC Release / Build RC Release (pull_request) Failing after 43s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m15s
2026-06-20 18:32:13 -05:00
gitea-actions[bot] 306cc3be92 chore(version): pre-release bump to 06.25.00-rc [skip ci] 2026-06-20 23:29:00 +00:00
Jonathan Miller 5b78a059ed chore: retrigger checks after runner restart [skip bump]
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Universal: PR Check / Secret Scan (pull_request) Successful in 33s
PR RC Release / Build RC Release (pull_request) Failing after 40s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m44s
2026-06-20 18:28:14 -05:00
gitea-actions[bot] c3d4fdea65 chore(version): pre-release bump to 06.24.00-rc [skip ci] 2026-06-20 23:20:28 +00:00
Jonathan Miller 8a3d16fbd6 chore: retrigger CI [skip bump]
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 7s
PR RC Release / Build RC Release (pull_request) Failing after 50s
Universal: PR Check / Secret Scan (pull_request) Successful in 50s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m29s
2026-06-20 18:19:46 -05:00
gitea-actions[bot] fc97bb9499 chore(version): pre-release bump to 06.23.00-rc [skip ci] 2026-06-20 23:12:02 +00:00
Jonathan Miller b28fce2328 refactor: merge 3 PR workflows into pr-check, reduce CI noise
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 35s
PR RC Release / Build RC Release (pull_request) Failing after 36s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m30s
- Delete pr-branch-check.yml (redundant with pr-check Branch Policy job)
- Move gitleaks PR scan into pr-check.yml as Secret Scan job
- Remove pull_request trigger from ci-generic.yml (irrelevant for Go repo)
- Remove pull_request trigger from gitleaks.yml (keep schedule + dispatch)

Reduces PR-triggered workflows from 7 to 4.
2026-06-20 18:11:14 -05:00
gitea-actions[bot] 77e5c8a5ef chore(version): pre-release bump to 06.22.00-rc [skip ci] 2026-06-20 23:04:39 +00:00
Jonathan Miller c1841a32ce chore: trigger PR checks [skip bump]
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 40s
PR RC Release / Build RC Release (pull_request) Failing after 53s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 56s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m36s
2026-06-20 18:03:44 -05:00
gitea-actions[bot] a83b78c7cd chore(version): pre-release bump to 06.21.00-rc [skip ci] 2026-06-20 22:52:38 +00:00
Jonathan Miller ae8b110e89 chore: add changelog entries for licensing system and profile aliases
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
PR RC Release / Build RC Release (pull_request) Failing after 53s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m5s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m30s
2026-06-20 17:51:27 -05:00
gitea-actions[bot] 612736c0f0 chore(version): pre-release bump to 06.20.00-rc [skip ci] 2026-06-20 22:22:52 +00:00
gitea-actions[bot] 30bb5e33e2 chore(version): pre-release bump to 06.18.08-dev [skip ci] 2026-06-20 22:20:41 +00:00
gitea-actions[bot] 88e5def821 chore(release): build 06.19.00-rc [skip ci] 2026-06-20 22:19:00 +00:00
Jonathan Miller 6f6a8b0f2c fix: remove invalid SortType field from FindReleasesOptions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 37s
PR RC Release / Build RC Release (pull_request) Failing after 1m30s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m31s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m47s
2026-06-20 17:18:52 -05:00
gitea-actions[bot] efb1fe1f70 chore(version): pre-release bump to 06.18.07-dev [skip ci] 2026-06-20 22:16:44 +00:00
Jonathan Miller 212b8c9e6d feat: add DLID-gated update XML endpoint for Joomla updater (#621)
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Generic: Project CI / Lint & Validate (pull_request) Successful in 41s
PR RC Release / Build RC Release (pull_request) Failing after 1m13s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m23s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 3m6s
Universal: Build & Release / Promote to RC (pull_request) Successful in 23s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
GET /api/v1/licensing/updates/{product}.xml?dlid=XXX&domain=YYY

Validates DLID format (CRC32), checks license status and entitlement,
auto-activates domain, returns Joomla-compatible update XML from the
entitled repo's stable release. Invalid/expired returns empty <updates/>.
2026-06-20 17:15:24 -05:00
gitea-actions[bot] dada56899c chore(version): pre-release bump to 06.18.06-dev [skip ci] 2026-06-20 21:30:12 +00:00
Jonathan Miller 7d5eff194d feat: add license CRUD, audit logging, status transitions (#618)
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m46s
- RevokeLicense, SuspendLicense, ReactivateLicense convenience methods
- LicenseAuditLog table for tracking status and tier changes
- UpdateLicenseTier now logs old→new tier before rebuilding entitlements
- SetLicenseStatus logs old→new status transitions
2026-06-20 16:29:30 -05:00
jmiller ab45b87b66 chore: sync repo-health.yml from Template-Go [skip ci] 2026-06-20 20:52:59 +00:00
jmiller 615a1d42b4 chore: sync pre-release.yml from Template-Go [skip ci] 2026-06-20 20:52:47 +00:00
jmiller 422313018b chore: sync pr-check.yml from Template-Go [skip ci] 2026-06-20 20:52:36 +00:00
jmiller 1a98c60b7d chore: sync issue-branch.yml from Template-Go [skip ci] 2026-06-20 20:52:24 +00:00
jmiller c32852285e chore: sync deploy-manual.yml from Template-Go [skip ci] 2026-06-20 20:52:14 +00:00
gitea-actions[bot] 77da746d61 chore(version): pre-release bump to 06.18.05-dev [skip ci] 2026-06-20 20:52:08 +00:00
jmiller 071899e09e chore: sync cleanup.yml from Template-Go [skip ci] 2026-06-20 20:52:04 +00:00
jmiller 23cf618bf7 chore: sync branch-cleanup.yml from Template-Go [skip ci] 2026-06-20 20:51:53 +00:00
jmiller 0177288ab1 chore: sync auto-release.yml from Template-Go [skip ci] 2026-06-20 20:51:44 +00:00
jmiller deb04a13d1 chore: sync auto-bump.yml from Template-Go [skip ci] 2026-06-20 20:51:34 +00:00
Jonathan Miller 86bbad9fa1 feat: add licensing tables — license, entitlement, activation, product_tier (#617)
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 3m18s
Migration v359 creates 4 tables for the consumer DLID licensing system:
- license: DLID-based licenses with tier, status, expiry
- license_entitlement: per-license product/repo access mapping
- license_activation: domain tracking with limit enforcement
- product_tier: 13 seeded tiers from base to enterprise

Models in models/licensing/ with CRUD, DLID generation (CRC32 checksum),
entitlement rebuild on tier change, and domain activation with upsert.
2026-06-20 15:51:04 -05:00
gitea-actions[bot] 3ddfc7a504 chore(release): build 06.19.00 [skip ci] 2026-06-20 18:08:18 +00:00
gitea-actions[bot] 40e79ac6df chore(version): pre-release bump to 06.18.04-dev [skip ci] 2026-06-20 18:06:41 +00:00
jmiller b77bfe47ef Merge pull request 'feat: support .mokogitea, .profile, and .github as org/user profile repo names' (#657) from dev into main
Deploy MokoGitea / deploy (push) Successful in 4m31s
2026-06-20 18:05:58 +00:00
Jonathan Miller 4277b32867 Merge branch 'dev' of https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork into dev
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m54s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m39s
Branch Policy Check / Verify merge target (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 2m1s
PR RC Release / Build RC Release (pull_request) Failing after 1m59s
# Conflicts:
#	.mokogitea/manifest.xml
#	.mokogitea/workflows/issue-branch.yml
2026-06-20 13:05:42 -05:00
Jonathan Miller adb13241ad Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	.mokogitea/manifest.xml
#	.mokogitea/workflows/issue-branch.yml
2026-06-20 13:04:01 -05:00
gitea-actions[bot] 606504c2d2 chore(version): pre-release bump to 06.18.03-dev [skip ci] 2026-06-20 18:01:21 +00:00
Jonathan Miller b98c46e17c fix: stale error message, add fallback to wiki commit lookup
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m17s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 45s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 50s
PR RC Release / Build RC Release (pull_request) Failing after 51s
- Fix log message in FindOwnerProfileReadme (was "GetRepositoryByName", now "GetDoerRepoPermission")
- Add profile repo fallback to findOrgWikiCommit in wiki.go
- Extract ProfileRepoFallbacks helper for reuse across packages

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-20 13:00:33 -05:00
gitea-actions[bot] 9ab646409f chore(version): pre-release bump to 06.18.02-dev [skip ci] 2026-06-20 17:56:32 +00:00
Jonathan Miller 65fdbdafd3 feat: support .mokogitea, .profile, and .github as org/user profile repo names (#651)
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m35s
Primary profile repo name is now .mokogitea with fallback chain:
.mokogitea > .profile > .github (and their -private variants).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-20 12:55:20 -05:00
gitea-actions[bot] 5613f58a3d chore(release): build 06.19.00 [skip ci] 2026-06-20 16:43:23 +00:00
jmiller fc8032d4e7 Merge pull request 'fix: MySQL 8.0 compat for migration v358 DROP COLUMN' (#656) from fix/650-display-name-computed into main
Deploy MokoGitea / deploy (push) Successful in 4m27s
2026-06-20 16:39:53 +00:00
Jonathan Miller 51b1063924 fix: use information_schema check instead of DROP COLUMN IF EXISTS
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Failing after 1s
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
PR RC Release / Build RC Release (pull_request) Failing after 45s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 58s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m31s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m53s
MySQL 8.0.x does not support IF EXISTS on ALTER TABLE DROP COLUMN.
Query information_schema.COLUMNS first to check column existence.
2026-06-20 11:39:08 -05:00
gitea-actions[bot] 916949517f chore(release): build 06.19.00 [skip ci] 2026-06-20 16:02:47 +00:00
jmiller 88af53cc51 Merge pull request 'fix: remove missed DisplayName reference in manifest_sync.go' (#654) from fix/650-display-name-computed into main
Deploy MokoGitea / deploy (push) Failing after 38m24s
2026-06-20 15:58:21 +00:00
Jonathan Miller b97e36cf49 fix: remove DisplayName from manifest_sync.go struct literal
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Failing after 1s
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 45s
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 54s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m20s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 3m15s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 35m27s
2026-06-20 10:57:57 -05:00
gitea-actions[bot] cfc8f45724 chore(release): build 06.19.00 [skip ci] 2026-06-20 15:51:40 +00:00
jmiller 0ee02c8fbf Merge pull request 'feat: remove display_name, compute from extension_type + name (#650)' (#653) from fix/650-display-name-computed into main
Deploy MokoGitea / deploy (push) Failing after 4m1s
2026-06-20 15:47:13 +00:00
Jonathan Miller 08e441d7c8 fix: rename package_type to extension_type in metadata template
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Failing after 1s
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 49s
PR RC Release / Build RC Release (pull_request) Failing after 51s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 3m3s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m59s
The Go handler reads extension_type but the template was still sending
package_type, causing every save to silently clear the field.
2026-06-20 10:45:55 -05:00
Jonathan Miller 2b6684536e feat: remove display_name, compute from extension_type + name (#650)
- Remove DisplayName field from RepoMetadata and UpdateStreamConfig
- Add DerivedDisplayName() method: "{Type} - {Name}" (e.g. "Package - MokoSuiteBackup")
- API returns computed display_name in GET, ignores it on PUT
- Update server feeds use DerivedDisplayName() instead of stored value
- Remove display_name from web forms (repo licensing, org update streams)
- License settings API computes display_name from repo metadata
- Migration v358: drop display_name columns from both tables
2026-06-20 10:45:54 -05:00
gitea-actions[bot] 717fd314e2 chore(release): build 06.19.00 [skip ci] 2026-06-19 07:11:36 +00:00
gitea-actions[bot] 32ea85a6dd chore(version): pre-release bump to 06.18.01-dev [skip ci] 2026-06-19 07:09:17 +00:00
jmiller 5b18dc537d Merge pull request 'Release: PackageType → ExtensionType rename + deploy fixes' (#648) from dev into main
Deploy MokoGitea / deploy (push) Successful in 6m26s
2026-06-19 07:04:24 +00:00
Jonathan Miller c8bf77630d Merge remote-tracking branch 'origin/main' into dev
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 34s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m30s
PR RC Release / Build RC Release (pull_request) Failing after 1m29s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m15s
Universal: Auto Version Bump / Version Bump (push) Successful in 4s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 26s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
# Conflicts:
#	.mokogitea/manifest.xml
#	.mokogitea/workflows/issue-branch.yml
2026-06-19 02:03:39 -05:00
gitea-actions[bot] f50decfe77 chore(version): pre-release bump to 06.17.03-dev [skip ci] 2026-06-19 07:03:07 +00:00
jmiller 7ab27b6a14 Merge pull request 'refactor: rename PackageType → ExtensionType (#646)' (#647) from fix/646-extension-type into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
PR RC Release / Build RC Release (pull_request) Failing after 53s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m8s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m24s
Universal: Build & Release / Promote to RC (pull_request) Successful in 2m36s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-19 07:02:26 +00:00
gitea-actions[bot] e33a05268c chore(version): pre-release bump to 06.17.02-dev [skip ci]
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
2026-06-19 07:01:17 +00:00
Jonathan Miller 7ebe6d54be refactor: rename PackageType → ExtensionType (#646)
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m58s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Rename package_type column and Go field to extension_type across
model, API, update server, manifest sync, settings UI, and changelog.

SQL migration renames the column. XML tag <package-type> in
.mokogitea/manifest.xml is unchanged (backward compat).

Fixes #646
2026-06-19 02:00:04 -05:00
gitea-actions[bot] 77f3ac9e96 chore(release): build 06.18.00 [skip ci] 2026-06-19 06:21:22 +00:00
gitea-actions[bot] 7b6c0d2c08 chore(version): pre-release bump to 06.17.01-dev [skip ci] 2026-06-19 06:17:24 +00:00
jmiller 5a085c7ace Merge pull request 'fix: rename custom fields handlers, clean metadata API, remove Version field' (#645) from dev into main
Deploy MokoGitea / deploy (push) Successful in 5m3s
2026-06-19 06:17:16 +00:00
Jonathan Miller 45caf41499 fix: rename custom fields handlers, clean metadata API naming
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 33s
PR RC Release / Build RC Release (pull_request) Failing after 47s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 53s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m39s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m21s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-19 01:14:11 -05:00
Jonathan Miller 1ec0a70c0c fix: rename API handlers to avoid collision with custom_fields GetRepoMetadata
Deploy MokoGitea / deploy (push) Successful in 4m5s
2026-06-18 21:08:29 -05:00
Jonathan Miller fb98aac7d2 fix: remove Version field from metadata.go saveMetadata()
Deploy MokoGitea / deploy (push) Failing after 5m7s
2026-06-18 20:06:20 -05:00
Jonathan Miller 9b94c16226 fix: rename GetRepoManifest → GetRepoMetadata in changelog_xml.go
Deploy MokoGitea / deploy (push) Failing after 3m47s
2026-06-18 19:32:19 -05:00
Jonathan Miller 4b92fa9245 fix: deploy workflow pulls from renamed repo MokoGitea-Fork (was MokoGitea-APP)
Deploy MokoGitea / deploy (push) Failing after 3m24s
2026-06-18 19:00:03 -05:00
Jonathan Miller 82458a6289 fix: remove Version field references from manifest_sync.go
Deploy MokoGitea / deploy (push) Failing after 3m28s
Version field was removed from RepoMetadata struct during the
Manifest→Metadata rename but references remained in manifest_sync.go,
causing Go compilation to fail with "unknown field Version".
2026-06-18 16:43:56 -05:00
gitea-actions[bot] 309d848db5 chore(release): build 06.17.00 [skip ci] 2026-06-18 21:07:39 +00:00
jmiller 757b4672d4 Merge pull request 'fix: lowercase Joomla element names, rename Manifest → Metadata (#635)' (#644) from fix/635-joomla-element-name into main
Deploy MokoGitea / deploy (push) Failing after 3m22s
2026-06-18 21:05:39 +00:00
Jonathan Miller 3c9e3ffa13 fix: lowercase Joomla element names, rename Manifest → Metadata (#635)
Branch Policy Check / Verify merge target (pull_request) Failing after 1s
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 37s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 36s
PR RC Release / Build RC Release (pull_request) Failing after 36s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m49s
Universal: Build & Release / Promote to RC (pull_request) Failing after 5s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
AutoElementName() was using m.Name without lowercasing or cleaning,
producing elements like pkg_MokoSuiteBackup instead of
pkg_mokosuitebackup. Added cleanJoomlaElement() to replicate
Joomla's InputFilter::clean('cmd') behavior.

Removed incorrect "plugin": "plg_" — Joomla plugins use bare names.

Renamed RepoManifest → RepoMetadata across all non-migration Go
files. DB table remains repo_manifest for backward compatibility.
Migration files unchanged to preserve migration chain.

API endpoints /manifest and /metadata both continue to work
(backward compat route preserved in api.go).

Fixes #635
2026-06-18 16:03:54 -05:00
gitea-actions[bot] 3690f31b77 chore(release): build 06.17.00 [skip ci] 2026-06-18 17:05:18 +00:00
jmiller be70d65e6c Merge pull request 'merge: dev into main - remove version from manifest' (#642) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m34s
2026-06-18 17:04:34 +00:00
gitea-actions[bot] 3bfcc9f79a chore(version): pre-release bump to 06.16.01-dev [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m29s
2026-06-18 17:02:03 +00:00
Jonathan Miller e427bee210 merge: resolve version conflicts, keep main release version
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Generic: Project CI / Lint & Validate (pull_request) Successful in 38s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 56s
PR RC Release / Build RC Release (pull_request) Failing after 56s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m55s
Universal: Build & Release / Promote to RC (pull_request) Successful in 18s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-18 12:00:41 -05:00
gitea-actions[bot] 3af6265802 chore(release): build 06.16.00 [skip ci] 2026-06-18 16:58:44 +00:00
jmiller c456258540 Merge pull request 'fix: remove version from manifest first-class fields' (#641) from fix into main
Deploy MokoGitea / deploy (push) Failing after 3m33s
2026-06-18 16:57:55 +00:00
Jonathan Miller 91e50fe4bf fix: remove version from manifest first-class fields
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m35s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 39s
PR RC Release / Build RC Release (pull_request) Failing after 49s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m24s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Version is not a required metadata field — it's derived from git tags
and releases, not stored in the manifest. Remove from RepoManifest
model and the manifest API request/response struct.

VersionPrefix, StandardsVersion, and TargetVersion are kept as they
serve different purposes (tag parsing, standards compliance, platform
targeting).
2026-06-18 11:01:23 -05:00
gitea-actions[bot] 9b766c3dee chore(release): build 06.15.00 [skip ci] 2026-06-18 15:37:13 +00:00
gitea-actions[bot] 7ca3593f74 chore(version): pre-release bump to 06.14.03-dev [skip ci] 2026-06-18 15:33:59 +00:00
jmiller bf5b759398 Merge pull request 'merge: dev into main — CI workflow triggers + version bump' (#640) from dev into main
Deploy MokoGitea / deploy (push) Successful in 5m12s
2026-06-18 15:31:59 +00:00
Jonathan Miller d877bbbe0c merge: resolve auto-bump version conflicts
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m2s
PR RC Release / Build RC Release (pull_request) Failing after 1m30s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m50s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m36s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-18 10:31:45 -05:00
Jonathan Miller 5e401c659b merge: resolve version conflicts, keep main release version 2026-06-18 10:30:44 -05:00
gitea-actions[bot] 7d0b3f042c chore(version): pre-release bump to 06.14.02-dev [skip ci] 2026-06-18 15:28:34 +00:00
Jonathan Miller e0c492c7df fix(ci): run CI and repo-health on PR to main, not on push
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m19s
CI and repo-health were triggering on push to main, running alongside
(or after) the deploy workflow instead of gating the merge. Move both
to pull_request triggers so they run as PR checks before merge.

- ci-generic: remove push trigger, keep pull_request to main/dev/rc
- repo-health: remove bare push trigger, scope pull_request to main
- deploy-mokogitea stays on push:main (fires only after merge)
2026-06-18 10:27:50 -05:00
gitea-actions[bot] 5fec351426 chore(release): build 06.15.00 [skip ci] 2026-06-18 15:23:45 +00:00
gitea-actions[bot] 7f09fa0b98 chore(version): pre-release bump to 06.14.01-dev [skip ci] 2026-06-18 15:21:24 +00:00
jmiller 1b0b62db38 Merge pull request 'merge: dev into main' (#639) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 38s
Deploy MokoGitea / deploy (push) Successful in 4m58s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-18 15:20:31 +00:00
Jonathan Miller 0d9d526203 merge: resolve version conflicts, keep main release version (06.14.00)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 45s
Generic: Project CI / Lint & Validate (push) Successful in 50s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m21s
PR RC Release / Build RC Release (pull_request) Failing after 45s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m47s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m31s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-18 10:19:56 -05:00
gitea-actions[bot] b321273306 chore(version): pre-release bump to 06.13.04-dev [skip ci] 2026-06-18 15:06:43 +00:00
jmiller 8adccbcb40 Merge pull request 'fix: Joomla update server — element names, platform gating, domain race' (#638) from fix into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 24s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m59s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-18 15:06:01 +00:00
Jonathan Miller 08f6454dd2 fix: address PR review findings — error handling, type safety, comments
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 28s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Review fixes:
- Propagate updateDomainRestriction error in grace-period path instead
  of silently discarding it (was same bug class as the TOCTOU fix)
- Propagate IsDomainKnownForKey error inside transaction — discarding
  it defeated the atomicity guarantee
- Wrap updateDomainRestriction error with context message
- Use boolean flags for changelog manifest fallback instead of fragile
  sentinel comparison against strings.ToLower(repo.Name)
- Type-assert ctx.Data["RepoUpdatePlatform"] to string instead of
  comparing interface{} values
- Use log.Warn instead of log.Error for manifest fallback (intentional
  degradation, not a failure)
- Clarify comments: doc comment scope, hyphen removal wording
2026-06-18 09:51:33 -05:00
Jonathan Miller a83d2ee3bd fix: changelog element mismatch, platform gating, domain race condition
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m12s
PR RC Release / Build RC Release (pull_request) Failing after 1m10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m1s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Three fixes for the Joomla update server system:

1. changelog_xml.go: Resolve element name from manifest first (same
   priority as updates.xml) so changelog.xml and updates.xml emit
   matching <element> values. Previously only checked the config table.

2. updateserver.go: Only serve Joomla XML when platform is joomla,
   both, or unset. Previously only blocked dolibarr, meaning WordPress/
   PrestaShop/Drupal/WHMCS repos incorrectly served Joomla XML.

3. license_key.go: Wrap domain auto-association in db.WithTx to prevent
   TOCTOU race where concurrent requests from different domains could
   exceed MaxSites. Also removes a duplicate site-limit check that was
   unreachable dead code.
2026-06-18 09:14:13 -05:00
Jonathan Miller 73a1320d72 fix: derive Joomla element name with correct lowercase + type prefix
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
Generic: Repo Health / Site Health (push) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 41s
PR RC Release / Build RC Release (pull_request) Failing after 34s
Universal: Auto Version Bump / Version Bump (push) Successful in 4s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m46s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
AutoElementName() was using the manifest Name field verbatim, producing
element names like "pkg_MokoSuiteBackup" instead of "pkg_mokosuitebackup".
Joomla's updater matches by element+type+client_id in #__extensions, so
the case mismatch made updates invisible.

Changes:
- Lowercase name and strip hyphens in AutoElementName()
- Remove incorrect "plg_" prefix for plugins (Joomla plugins have no
  element prefix; the folder column determines the plugin group)

Fixes #635
2026-06-18 08:33:35 -05:00
gitea-actions[bot] 4694fbf719 chore(release): build 06.14.00 [skip ci] 2026-06-14 20:04:31 +00:00
gitea-actions[bot] 0a0d7b704d chore(version): pre-release bump to 06.13.03-dev [skip ci] 2026-06-14 20:02:18 +00:00
jmiller 3cc68ec310 Merge pull request 'fix: correct <client> tag per extension type in Joomla update feed (#611)' (#634) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 32s
Deploy MokoGitea / deploy (push) Failing after 4m7s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
fix: correct client tag per extension type in Joomla update feed (#611)
2026-06-14 20:01:47 +00:00
Jonathan Miller 96a51574e2 merge: resolve issue-branch.yml conflict
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Project CI / Lint & Validate (push) Successful in 31s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 38s
PR RC Release / Build RC Release (pull_request) Failing after 1m43s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m46s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m20s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m37s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-14 15:01:04 -05:00
Jonathan Miller 93f20b9671 merge: resolve conflicts, keep client_id fix 2026-06-14 15:00:16 -05:00
gitea-actions[bot] d419033384 chore(version): pre-release bump to 06.13.02-dev [skip ci] 2026-06-14 19:55:02 +00:00
Jonathan Miller c453310834 fix: set correct <client> per extension type in Joomla update feed (#611)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Generic: Project CI / Lint & Validate (push) Successful in 31s
Generic: Project CI / Lint & Validate (pull_request) Successful in 28s
PR RC Release / Build RC Release (pull_request) Failing after 1m11s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m14s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m6s
Universal: Build & Release / Promote to RC (pull_request) Successful in 17s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-14 14:53:53 -05:00
gitea-actions[bot] f329466d1c chore(release): build 06.15.00 [skip ci] 2026-06-12 03:21:49 +00:00
jmiller 05a89339e1 Merge pull request 'chore: update changelog with 06.15.00 features' (#614) from fix/changelog-update into main
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Project CI / Lint & Validate (push) Successful in 41s
Deploy MokoGitea / deploy (push) Failing after 6m22s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-12 03:19:12 +00:00
Jonathan Miller 75e640dd17 chore: update changelog with 06.15.00 features (#597, #598)
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 3s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 18s
Generic: Repo Health / Site Health (push) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 3s
Generic: Repo Health / Access control (push) Successful in 3s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m1s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 1m30s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m41s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m54s
Universal: Auto Version Bump / Version Bump (push) Successful in 13s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-11 22:18:44 -05:00
gitea-actions[bot] 09f17439ec chore(release): build 06.15.00 [skip ci] 2026-06-12 03:00:57 +00:00
jmiller 9d45a767e7 Merge pull request 'feat(issues): make status_id, priority_id, type_id required on create (#598)' (#613) from feature/598-required-issue-metadata into main 2026-06-12 02:59:37 +00:00
jmiller ece24c6d38 Merge pull request 'feat(custom-fields): add required flag UI and API validation (#597)' (#612) from feature/597-required-custom-fields into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 47s
Deploy MokoGitea / deploy (push) Failing after 6m52s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-12 02:59:10 +00:00
Jonathan Miller e0d4f5fd15 fix: handle SetIssueStatusID/PriorityID/TypeID errors on create
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 50s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m36s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m36s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Explicit caller-provided values now return 500 on failure instead of
silently discarding the error. Default auto-assignment still uses
best-effort (log and continue) since it's a fallback.
2026-06-11 21:57:39 -05:00
Jonathan Miller c67e7373fb fix: move required custom field validation before issue creation
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 51s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m22s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m19s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Validation now runs before NewIssue() to prevent orphaned issues
when a required field is missing. Reuses the defs query for both
validation and saving.
2026-06-11 21:56:14 -05:00
Jonathan Miller cbaca15cda feat(issues): make status_id, priority_id, type_id required on create (#598)
Branch Policy Check / Verify merge target (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Failing after 6s
Universal: PR Check / Validate PR (pull_request) Failing after 19s
Generic: Project CI / Lint & Validate (pull_request) Successful in 42s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 4s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 3m21s
PR RC Release / Build RC Release (pull_request) Failing after 2m55s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- Change CreateIssueOption fields from *int64 (optional) to int64
- API auto-assigns org defaults when value is 0
- MCP gitea_issue_create now requires status_id, priority_id, type_id
  (pass 0 for org default)
- Keep optional on gitea_issue_update (partial updates)

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 21:49:37 -05:00
Jonathan Miller 245b5a8e6a feat(custom-fields): add required flag UI and API validation (#597)
Branch Policy Check / Verify merge target (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Failing after 4s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 15s
Generic: Repo Health / Access control (pull_request) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m0s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 1m30s
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 2m37s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- Add required checkbox to org custom field settings form
- Show red asterisk (*) indicator on required fields in the list
- Validate required custom fields on API issue create (return 422)
- Add locale strings for required field UI

The Required column already exists in the DB (migration v343) and is
already validated in the web form. This adds the missing org settings
UI checkbox and API-side validation.

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 21:46:24 -05:00
Jonathan Miller d61680d5b3 fix: use string client values per Joomla update spec (#611)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 46s
Deploy MokoGitea / deploy (push) Failing after 3m15s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-12 00:39:07 +00:00
Jonathan Miller c2687a2595 fix: use string client values (site/administrator) per Joomla update spec
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Branch Policy Check / Verify merge target (pull_request) Failing after 1s
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 27s
PR RC Release / Build RC Release (pull_request) Failing after 1m28s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 55s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Joomla's update server documentation specifies <client> should be
"site" or "administrator" strings, not numeric 0/1. Joomla's XML
parser handles the string-to-integer mapping internally.

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 19:17:04 -05:00
gitea-actions[bot] 7161b9cdee chore(release): build 06.14.00 [skip ci] 2026-06-11 23:23:49 +00:00
jmiller 553abbb9d0 Merge pull request 'fix: deploy workflow clones wrong repo and runs swapoff' (#609) from fix/606-607-wiki-api-bugs into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 3s
Generic: Project CI / Lint & Validate (push) Successful in 35s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-11 23:23:02 +00:00
Jonathan Miller d66d96adaf fix: deploy workflow clones wrong repo and runs swapoff
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Failing after 1s
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 22s
PR RC Release / Build RC Release (pull_request) Failing after 21s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m20s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Remove swapoff -a that crashes MySQL during deploys
- Fix source repo URL from MokoGitea to MokoGitea-APP
- Add git remote set-url to correct existing clones on server
- Update CHANGELOG with all session fixes

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 18:13:55 -05:00
Jonathan Miller a8b3f45522 fix: remove swapoff from deploy workflow, update changelog and MCP metadata fields
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- Remove `swapoff -a && swapon -a` from deploy-mokogitea.yml that caused
  MySQL crashes during deploys by forcing swap contents into already-full RAM
- Add element_name, display_name, description, license_name, language
  fields to MCP gitea_metadata_update tool
- Update CHANGELOG with all fixes from this session

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 17:53:58 -05:00
gitea-actions[bot] a0ec6bc6b4 chore(release): build 06.14.00 [skip ci] 2026-06-11 22:18:30 +00:00
jmiller 5893a8b9dd Merge pull request 'fix: wiki API sub-page support and content response (#606, #607)' (#608) from fix/606-607-wiki-api-bugs into main 2026-06-11 22:16:46 +00:00
Jonathan Miller 0f1002f3c9 fix: address review findings for wiki API
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 3s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 42s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 44s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m30s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Log warning when wiki blob exceeds max size (was silent empty return)
- Fix stale WebPathFromRequest test to match folder-based wiki behavior
2026-06-11 17:03:21 -05:00
Jonathan Miller 3a7b07590f fix: wiki API sub-page support and content response (#606, #607)
Branch Policy Check / Verify merge target (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Failing after 4s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Failing after 12s
Generic: Project CI / Lint & Validate (pull_request) Successful in 44s
Universal: Build & Release / Promote to RC (pull_request) Successful in 44s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 57s
PR RC Release / Build RC Release (pull_request) Failing after 54s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- Change wiki API routes from {pageName} to wildcard (*) to support
  pages with path separators (e.g. mcp/fleet-overview)
- Use ListEntriesRecursiveFast() in ListWikiPages to include pages
  in subdirectories, not just root-level files
- Add error logging in wikiContentsByEntry for diagnosing empty
  content_base64 responses
- Fix pagination to count only regular files, not directories

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 16:56:10 -05:00
gitea-actions[bot] 1ab5b8a70d chore(release): build 06.14.00 [skip ci] 2026-06-11 21:39:24 +00:00
jmiller ea77b253a9 Merge pull request 'fix: update server feed generation bugs (#601)' (#605) from fix/601-update-feed-bugs into main 2026-06-11 21:37:35 +00:00
Jonathan Miller 878cac5d99 fix: address review findings for update feed bugs
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 4s
Branch Policy Check / Verify merge target (pull_request) Failing after 4s
Universal: PR Check / Branch Policy (pull_request) Failing after 4s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 16s
Generic: Project CI / Lint & Validate (pull_request) Successful in 48s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 5s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m16s
PR RC Release / Build RC Release (pull_request) Failing after 1m13s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m20s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Extend versionRegex to capture pre-release suffixes in fallback path
- Anchor versionHasChannelSuffix with regex to avoid false positives
- Log LoadAttributes errors instead of silently skipping releases
2026-06-11 16:36:32 -05:00
Jonathan Miller 7dfb11070d fix: update server feed generation bugs (#601)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: Build & Release / Promote to RC (pull_request) Failing after 14s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Generic: Project CI / Lint & Validate (pull_request) Successful in 51s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 59s
PR RC Release / Build RC Release (pull_request) Failing after 1m27s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Change default targetplatform from (5|6)\.* to 6\..* for Joomla 6 compat
- Use FullElementName() to auto-construct element from PackageType + Name
- Change <client> from string (site/administrator) to numeric (0/1)
- Preserve pre-release version suffix number (e.g. -rc2 not just -rc)

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 16:28:00 -05:00
gitea-actions[bot] 24a33fdd4d chore(release): build 06.14.00 [skip ci] 2026-06-11 21:16:28 +00:00
jmiller 9b0eaf937a Merge pull request 'fix(ui): open raw file links in new tab (#581)' (#600) from fix/581-raw-button-new-tab into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Project CI / Lint & Validate (push) Successful in 1m1s
Deploy MokoGitea / deploy (push) Failing after 16m27s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-11 21:14:44 +00:00
jmiller 2c394870de Merge pull request 'fix: return 404 for update feeds when update server is disabled (#589)' (#599) from fix/589-feed-disabled-check into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 45s
Deploy MokoGitea / deploy (push) Failing after 18m28s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-11 21:12:36 +00:00
Jonathan Miller c1604e96cf fix(security): add rel="noopener noreferrer" to target="_blank" links
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 3s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 37s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 39s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 41s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 16m46s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Prevents reverse tabnabbing on raw file links opened in new tabs.
2026-06-11 16:01:07 -05:00
gitea-actions[bot] 041adc50e5 chore(version): pre-release bump to 06.13.01-dev [skip ci] 2026-06-11 20:33:20 +00:00
jmiller cd305a2332 ci(pre-release): sync universal v05 workflow with chore/** branch trigger
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 43s
Universal: Auto Version Bump / Version Bump (push) Failing after 6s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m16s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-11 20:31:40 +00:00
Jonathan Miller 998f971246 fix(ui): open raw file links in new tab (#581)
Branch Policy Check / Verify merge target (pull_request) Failing after 2s
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Failing after 13s
Universal: Build & Release / Promote to RC (pull_request) Successful in 41s
PR RC Release / Build RC Release (pull_request) Failing after 44s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 45s
Generic: Project CI / Lint & Validate (pull_request) Successful in 47s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Raw and View Raw links now open in a new tab so the user stays on
the file view page. Updated in view_file, blame, filetoolarge, and
LFS file templates.

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 15:30:34 -05:00
Jonathan Miller 7f3785e7de fix: return 404 for update feeds when update server is disabled (#589)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Failing after 1s
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
PR RC Release / Build RC Release (pull_request) Failing after 1m3s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 1m6s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 33s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
The RepoAssignmentPublicFeed middleware did not check LicensingEnabled,
so feed endpoints responded with valid data even when the feature was
disabled. Now checks the effective config (repo → org cascade) and
returns 404 when neither level has LicensingEnabled=true.

Co-Authored-By: Moko Consulting <hello@mokoconsulting.tech>
2026-06-11 15:26:52 -05:00
Jonathan Miller 7ed335e6c3 feat(api): add /metadata API route alongside /manifest for backward compat
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 24s
Deploy MokoGitea / deploy (push) Successful in 4m8s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-10 04:33:58 -05:00
Jonathan Miller 4ed6e0175d feat(settings): SPDX license dropdown with auto-name, clean up metadata
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 42s
Deploy MokoGitea / deploy (push) Successful in 5m50s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- License field is now a dropdown with common SPDX identifiers
- License name auto-derived from SPDX (spdxToName map)
- Preserve hidden fields (element_name, display_name, maintainer, etc.)
  when saving from the simplified UI
- Remove unused updateserver_model import from metadata handler
2026-06-10 04:08:12 -05:00
Jonathan Miller a86350eebb fix(settings): remove auto-derived fields from metadata UI
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 35s
Deploy MokoGitea / deploy (push) Successful in 4m22s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Remove fields that are auto-derived at runtime:
- Standards source/version (internal)
- Maintainer/maintainer URL (from org profile)
- Display name (auto: ExtensionType - RepoName)
- Element name full (auto: prefix_reponame_lowercase)
- Language (auto-detected by Gitea)

Rename "Package Type" to "Extension Type".
Build section only shown for Joomla platform.
2026-06-10 00:48:13 -05:00
Jonathan Miller 97ea4fc4d0 feat(migration): migrate update server metadata fields to repo manifest
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Project CI / Lint & Validate (push) Successful in 27s
Deploy MokoGitea / deploy (push) Successful in 3m27s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Migration 355: copies extension metadata from update_stream_config to
repo_manifest where manifest fields are empty. Fields migrated:
display_name, extension_name→element_name, extension_type→package_type,
target_version, maintainer, maintainer_url, info_url, php_minimum, platform.
Only fills empty manifest fields (won't overwrite existing data).
2026-06-09 23:38:14 -05:00
Jonathan Miller faef50ec4d fix(settings): update server page shows only visibility and gating
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 4s
Generic: Project CI / Lint & Validate (push) Successful in 38s
Deploy MokoGitea / deploy (push) Successful in 3m56s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-09 23:36:21 -05:00
Jonathan Miller 9b9e5ae964 feat(settings): separate update server page from metadata
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 30s
Deploy MokoGitea / deploy (push) Successful in 3m26s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- /settings/metadata: project identity + custom fields
- /settings/updateserver: enable, platform, visibility, gating, keys
- Update server nav link shown when LicensingEnabled
- Old /settings/licensing and /settings/manifest redirect
2026-06-09 23:34:26 -05:00
Jonathan Miller 82a48d69cc chore: remove old manifest files (consolidated into metadata)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 22s
Generic: Project CI / Lint & Validate (push) Successful in 31s
Deploy MokoGitea / deploy (push) Failing after 13m42s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-09 23:26:19 -05:00
Jonathan Miller 4e5b7c7e65 chore: remove .gitea directory — workflows live in .mokogitea
The .gitea/workflows/deploy.yml was added by mistake. All workflows
and issue templates live in .mokogitea/ per MokoGitea convention.
2026-06-09 23:26:00 -05:00
Jonathan Miller e1ca5cdfc4 feat(settings): consolidate manifest + custom fields into metadata page
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 33s
Deploy MokoGitea / deploy (push) Failing after 3m31s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- Merge project identity (manifest), update server config, and custom
  fields into single /settings/metadata page
- Three sections: Project Identity, Update Server, Custom Fields
- Old /settings/manifest and /settings/licensing redirect to /metadata
- Single nav link replaces two separate entries
2026-06-09 23:24:35 -05:00
Jonathan Miller 704d9d10be feat(issues): status/priority/type dropdowns on new issue form (#598)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 28s
Deploy MokoGitea / deploy (push) Successful in 4m0s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- Add type, priority, status dropdowns to new issue sidebar
- Pre-select org defaults (is_default for type/priority, first open status)
- Auto-assign defaults on API create when not provided
- Required indicators (*) on dropdowns and custom fields
- Validate required custom fields on submit (#597)
- Add feed_visibility to update server settings on manifest page
2026-06-09 23:19:19 -05:00
Jonathan Miller 549e890cd0 refactor: rename models/licenses to models/updateserver
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 40s
Deploy MokoGitea / deploy (push) Successful in 5m29s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Rename the licensing/update server model package to better reflect
its purpose. All imports updated from licenses_model to updateserver_model.
License key management UI files kept (only imports updated).
2026-06-09 20:29:03 -05:00
Jonathan Miller 857c51e030 feat(settings): consolidate update server settings into manifest page
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 33s
Deploy MokoGitea / deploy (push) Successful in 4m32s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
- Move licensing/update server form into manifest settings page
- Remove separate licensing nav link (redirect old URL for compat)
- Extension metadata fields already in manifest section (no duplication)
- Closes #582
2026-06-09 19:35:47 -05:00
Jonathan Miller d8c4f1efaf fix(ci): use git.mokoconsulting.tech instead of code.mokoconsulting.tech
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 31s
Deploy MokoGitea / deploy (push) Successful in 4m27s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-09 19:15:10 -05:00
Jonathan Miller d9cdaacb77 fix(ci): rename all GA_TOKEN/GITEA_TOKEN refs to MOKOGITEA_TOKEN
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 49s
Deploy MokoGitea / deploy (push) Failing after 4m26s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-09 18:38:48 -05:00
Jonathan Miller 4a4873c733 fix(ci): use MOKOGITEA_TOKEN instead of deleted GITEA_TOKEN
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 32s
Deploy MokoGitea / deploy (push) Failing after 4m35s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-09 18:28:38 -05:00
Jonathan Miller 633980bd05 ci: add auto-deploy workflow on push to main
Builds Docker image from Dockerfile.rootless, deploys to git server,
runs health check with automatic rollback on failure. Replaces manual
docker build/restart process.

Runs on the release runner with Docker-in-Docker access.
2026-06-09 18:17:37 -05:00
Jonathan Miller 4bd6be7935 fix(ci): use single SSH session for deploy, add wiki debug logging
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Project CI / Lint & Validate (push) Successful in 32s
Deploy MokoGitea / deploy (push) Failing after 5m0s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Consolidate all SSH commands into one session via heredoc to avoid
connection refused errors from SSH rate limiting on repeated connects.
Add error logging to OrgWikiRepoExists to debug missing wiki tab.
2026-06-09 16:35:04 -05:00
Jonathan Miller cefff4878c chore: remove upstream Gitea CI workflows
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 42s
Deploy MokoGitea / deploy (push) Failing after 58s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
These workflows target upstream Gitea infrastructure (Docker Hub,
GHCR, AWS S3, namespace runners) that MokoGitea doesn't use.
MokoGitea deploy will use its own workflow.
2026-06-09 16:23:36 -05:00
Jonathan Miller 47ac97d284 debug: add error logging to OrgWikiRepoExists
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Project CI / Lint & Validate (push) Successful in 43s
Deploy MokoGitea / deploy (push) Failing after 45s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-09 16:16:51 -05:00
Jonathan Miller be6f3091af fix: switch org wiki tab to .profile wiki sidecars (#595)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 35s
Deploy MokoGitea / deploy (push) Failing after 1m16s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Fixes #594
2026-06-09 15:05:05 -05:00
Jonathan Miller aeb36e4312 feat: use manifest API as source of truth for update feed metadata (#592)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Replace custom field + config table cascade with manifest-only read
for extension identity fields (element_name, package_type, display_name,
target_version, php_minimum, description). Config table retained only
for licensing fields (download_gating, key_prefix, support_url fallback).

Fix client field: package/component/library/file → administrator.

Remove issues_model import (custom field lookups removed).
2026-06-09 14:32:39 -05:00
Jonathan Miller 2135f4c37c fix(ci): use echo instead of printf for deploy key write
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 38s
Deploy MokoGitea / deploy (push) Failing after 42s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-09 14:20:20 -05:00
Jonathan Miller d7bc3c3879 fix(ci): rewrite deploy workflow to fix YAML expression parsing
Generic: Project CI / Lint & Validate (push) Waiting to run
Generic: Project CI / Tests (push) Blocked by required conditions
Deploy MokoGitea / deploy (push) Waiting to run
Generic: Repo Health / Access control (push) Waiting to run
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Waiting to run
Generic: Repo Health / Report Issues (push) Blocked by required conditions
- Escape Go template {{ }} in docker inspect with Gitea expression syntax
- Remove Python heredoc updates.xml step (had unescapable { in f-strings)
- Remove maintenance mode steps
- Use $VAR instead of ${VAR} to avoid brace conflicts
2026-06-09 12:43:27 -05:00
Jonathan Miller e7e2c5f7a2 fix(ci): remove maintenance mode steps blocking deploy
Generic: Project CI / Tests (push) Blocked by required conditions
deploy-mokogitea.yml / deploy (push) Waiting to run
Generic: Repo Health / Access control (push) Waiting to run
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Waiting to run
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 28s
2026-06-09 12:35:06 -05:00
Jonathan Miller 45a0338fc3 fix(ci): URL-encode JSON braces in maintenance mode data
Generic: Project CI / Tests (push) Blocked by required conditions
deploy-mokogitea.yml / deploy (push) Waiting to run
Generic: Repo Health / Access control (push) Waiting to run
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Waiting to run
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 28s
2026-06-09 12:31:59 -05:00
Jonathan Miller ce5f3570fb fix(ci): move JSON braces to env vars to avoid YAML parse error
Generic: Project CI / Tests (push) Blocked by required conditions
deploy-mokogitea.yml / deploy (push) Waiting to run
Generic: Repo Health / Access control (push) Waiting to run
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Waiting to run
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 27s
2026-06-09 12:28:57 -05:00
Jonathan Miller 5acf10f766 fix(ci): escape braces in maintenance mode curl data
Generic: Project CI / Lint & Validate (push) Waiting to run
Generic: Project CI / Tests (push) Blocked by required conditions
deploy-mokogitea.yml / deploy (push) Waiting to run
Generic: Repo Health / Access control (push) Waiting to run
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Waiting to run
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Gitea Actions expression parser chokes on literal { in run blocks.
Use double-quoted strings with escaped quotes instead.
2026-06-09 12:22:12 -05:00
Jonathan Miller e7fd70e0f2 fix(ci): fix YAML parse error in deploy key step
Generic: Project CI / Tests (push) Blocked by required conditions
deploy-mokogitea.yml / deploy (push) Waiting to run
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 35s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Replace heredoc (KEYEOF) with printf - heredoc terminator
was indented which broke YAML parsing and blocked all runners.
2026-06-09 11:54:42 -05:00
Jonathan Miller 34b1ef6638 fix(ci): prevent deploy key leak in logs
Generic: Project CI / Tests (push) Blocked by required conditions
deploy-mokogitea.yml / deploy (push) Waiting to run
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Lint & Validate (push) Successful in 29s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Move SSH key write to separate step to avoid env: logging
the private key in CI output. Key has been rotated.
2026-06-09 11:07:03 -05:00
Jonathan Miller ce05f9f3c6 fix(ci): add docker login before registry push, remove MCP submodule
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 40s
Deploy MokoGitea / deploy (push) Failing after 45s
- Add docker login with GITEA_TOKEN before pushing to container registry
  (fixes unauthorized: reqPackageAccess deploy failure)
- Remove mcp-mokogitea-api submodule reference (now standalone repo)
2026-06-09 10:50:35 -05:00
gitea-actions[bot] 6f9d7ca03a chore(release): build 06.14.00 [skip ci] 2026-06-09 15:25:21 +00:00
jmiller 1c7d43df38 Merge pull request 'feat: issue metadata API + org wiki tab' (#590) from chore/mcp-cleanup into main
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 30s
Deploy MokoGitea / deploy (push) Failing after 1m12s
2026-06-09 15:24:17 +00:00
Jonathan Miller b2b31f6c7b fix(settings): validate wiki mode and URL scheme
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 27s
PR RC Release / Build RC Release (pull_request) Failing after 47s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 48s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m58s
- Reject wiki_mode values other than "" or "external"
- Validate wiki_url is http/https to prevent javascript: URI XSS
- Resolve CHANGELOG merge conflict
2026-06-09 10:22:59 -05:00
jmiller 5ba1d0b2e5 Merge pull request 'feat: issue metadata API — first-class status, priority, type fields' (#591) from dev into main
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 25s
Generic: Project CI / Lint & Validate (push) Successful in 26s
2026-06-09 15:22:13 +00:00
Jonathan Miller 1caf26453f feat: issue metadata API + org wiki tab with internal/external mode
Generic: Project CI / Tests (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 41s
Generic: Project CI / Lint & Validate (pull_request) Successful in 43s
Generic: Project CI / Lint & Validate (push) Successful in 45s
PR RC Release / Build RC Release (pull_request) Failing after 39s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 31s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Issue Status/Priority/Type API:
- Expose status_id, priority_id, type_id (with resolved names) on Issue API struct
- New endpoints: GET /orgs/{org}/issue-statuses, /issue-priorities, /issue-types
- CreateIssue and EditIssue handlers accept status_id, priority_id, type_id
- MCP tools: 5 new tools + updated create/update with metadata params

Org Wiki Tab:
- Convention repos: wiki (public) and wiki-private (members-only)
- Inline wiki rendering with markdown pipeline, sidebar, footer, page list
- Public/private view dropdown (same UX as org profile README selector)
- External wiki mode: link to outside URL from wiki tab
- Wiki mode setting in org settings (internal vs external with URL field)
- Migration 354: add wiki_mode and wiki_url to user table
2026-06-09 10:20:54 -05:00
Jonathan Miller 1e90900f69 chore: update mcp-mokogitea-api submodule with CI fixes 2026-06-08 05:53:13 -05:00
Jonathan Miller b762c94a25 feat: issue metadata API + org wiki tab with internal/external mode
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Build & Release / Promote to RC (pull_request) Failing after 6s
Generic: Project CI / Lint & Validate (pull_request) Successful in 27s
PR RC Release / Build RC Release (pull_request) Failing after 36s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 37s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Issue Status/Priority/Type API:
- Expose status_id, priority_id, type_id (with resolved names) on Issue API struct
- New endpoints: GET /orgs/{org}/issue-statuses, /issue-priorities, /issue-types
- CreateIssue and EditIssue handlers accept status_id, priority_id, type_id
- MCP tools: 5 new tools + updated create/update with metadata params

Org Wiki Tab:
- Convention repos: wiki (public) and wiki-private (members-only)
- Inline wiki rendering with markdown pipeline, sidebar, footer, page list
- Public/private view dropdown (same UX as org profile README selector)
- External wiki mode: link to outside URL from wiki tab
- Wiki mode setting in org settings (internal vs external with URL field)
- Migration 354: add wiki_mode and wiki_url to user table
2026-06-08 05:21:45 -05:00
Jonathan Miller 6070f7dbd4 chore: update mcp-mokogitea-api submodule with CI fixes
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 14:47:16 -05:00
jmiller 082c550bc4 Merge pull request 'release: remove duplicate MCP, update submodule with manifest tools' (#588) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Publish MCP to npm / publish (push) Failing after 10s
Generic: Project CI / Lint & Validate (push) Successful in 25s
Deploy MokoGitea / deploy (push) Failing after 4m13s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 19:34:01 +00:00
jmiller b1da31420f Merge pull request 'chore: remove duplicate .mokogitea/mcp, update mcp submodule with manifest tools' (#587) from chore/mcp-cleanup into dev
Generic: Project CI / Lint & Validate (push) Successful in 29s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 28s
Universal: Build & Release / Promote to RC (pull_request) Successful in 35s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 34s
PR RC Release / Build RC Release (pull_request) Failing after 34s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 32s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 19:31:27 +00:00
Jonathan Miller 30d7ddc375 chore: remove duplicate .mokogitea/mcp, update mcp-mokogitea-api submodule
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 30s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Remove .mokogitea/mcp/ which was a duplicate of the mcp-mokogitea-api submodule
- Update submodule to include manifest get/update tools with all distribution fields
2026-06-07 14:30:35 -05:00
jmiller 8fa56271de Merge pull request 'release: template duplicate fix' (#586) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Project CI / Lint & Validate (push) Successful in 26s
Deploy MokoGitea / deploy (push) Failing after 3m21s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 18:55:43 +00:00
jmiller 8535928a04 Merge pull request 'fix(manifest): remove duplicate element name block in template' (#585) from fix/manifest-template-duplicate into dev
Generic: Project CI / Lint & Validate (push) Successful in 26s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Successful in 32s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 43s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 32s
PR RC Release / Build RC Release (pull_request) Failing after 53s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 18:53:26 +00:00
Jonathan Miller d0853b874f fix(manifest): remove duplicate element name block in template
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 28s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-07 13:47:07 -05:00
gitea-actions[bot] 0fe1d769ea chore(release): build 06.13.00 [skip ci] 2026-06-07 18:39:00 +00:00
jmiller 18372c84a7 Merge pull request 'release: manifest distribution fields + update server fix' (#584) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 27s
Deploy MokoGitea / deploy (push) Failing after 3m42s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 18:38:10 +00:00
Jonathan Miller c26ad626bd feat(manifest): add distribution metadata fields (phase 1 consolidation)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Failing after 11s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 30s
PR RC Release / Build RC Release (pull_request) Failing after 29s
Generic: Project CI / Lint & Validate (pull_request) Successful in 37s
Generic: Project CI / Lint & Validate (push) Successful in 1m11s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m14s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Add DisplayName, Maintainer, MaintainerURL, InfoURL, TargetVersion, and
PHPMinimum to RepoManifest. These fields were previously only in
UpdateStreamConfig and are now in the manifest as the single source of
truth. Distribution section shown conditionally for joomla/wordpress/
dolibarr platforms. Closes #582 phase 1.
2026-06-07 13:36:58 -05:00
gitea-actions[bot] 7a5c2d146f chore(version): pre-release bump to 06.12.03-dev [skip ci] 2026-06-07 13:36:57 -05:00
jmiller d85ee80a4e Merge pull request 'release: fix update server disable bug' (#579) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 35s
Generic: Project CI / Lint & Validate (push) Successful in 39s
Generic: Project CI / Tests (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 18:14:57 +00:00
jmiller f48ca157a3 Merge pull request 'fix: allow disabling update server once enabled' (#577) from fix/update-server-disable into dev
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 36s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 39s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 3m10s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 18:14:14 +00:00
Jonathan Miller 2eb2ed67bf fix(licensing): allow disabling update server by deleting repo-level config
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
When the update server checkbox is unchecked, delete the repo-level
config override instead of saving it with LicensingEnabled=false. This
allows the org-level config to take effect properly and fixes the issue
where the update server could not be turned off once enabled.
2026-06-07 13:12:24 -05:00
jmiller 3ae1265fb6 chore: sync security-audit.yml from Template-Go [skip ci] 2026-06-07 17:58:04 +00:00
jmiller 6a23c3e90d chore: sync repo-health.yml from Template-Go [skip ci] 2026-06-07 17:57:57 +00:00
jmiller 5f327f9da7 chore: sync pr-check.yml from Template-Go [skip ci] 2026-06-07 17:57:50 +00:00
jmiller 2ba1a6795f chore: sync notify.yml from Template-Go [skip ci] 2026-06-07 17:57:44 +00:00
jmiller 15e1149eb5 chore: sync issue-branch.yml from Template-Go [skip ci] 2026-06-07 17:57:37 +00:00
jmiller ed91aa3392 chore: sync gitleaks.yml from Template-Go [skip ci] 2026-06-07 17:57:30 +00:00
jmiller 8b4ea10e02 chore: sync deploy-manual.yml from Template-Go [skip ci] 2026-06-07 17:57:23 +00:00
jmiller ad4bac1162 chore: sync cleanup.yml from Template-Go [skip ci] 2026-06-07 17:57:16 +00:00
jmiller 983ce46278 chore: sync ci-generic.yml from Template-Go [skip ci] 2026-06-07 17:57:09 +00:00
jmiller 7130c79317 chore: sync cascade-dev.yml from Template-Go [skip ci] 2026-06-07 17:57:02 +00:00
jmiller c3f1a5ab40 chore: sync branch-cleanup.yml from Template-Go [skip ci] 2026-06-07 17:56:56 +00:00
jmiller 0b2646880e chore: sync auto-release.yml from Template-Go [skip ci] 2026-06-07 17:56:49 +00:00
jmiller 49274afa40 chore: sync auto-bump.yml from Template-Go [skip ci] 2026-06-07 17:56:42 +00:00
jmiller f6578969e2 Merge pull request 'release: template fix for manifest settings' (#576) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 22s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 17:52:35 +00:00
gitea-actions[bot] 1ca4996307 chore(version): pre-release bump to 06.12.02-dev [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
2026-06-07 17:52:15 +00:00
jmiller f86527994b Merge pull request 'fix(manifest): fix template end mismatch in Joomla settings block' (#575) from fix/manifest-template-end into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 59s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 17:51:45 +00:00
Jonathan Miller c016c603b4 fix(manifest): fix template end mismatch in Joomla settings block
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-07 12:51:20 -05:00
jmiller 51a10782e6 ci: add platform_detect.php to pre-release workflow [skip ci] 2026-06-07 17:40:04 +00:00
gitea-actions[bot] 3cd6146fa0 chore(version): pre-release bump to 06.12.01-dev [skip ci] 2026-06-07 17:37:50 +00:00
jmiller 9a51bf23d4 Merge pull request 'release: manifest sync + element name + workflow rename' (#574) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 31s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 17:37:37 +00:00
jmiller 902ee39e90 Merge pull request 'fix(manifest): sync new fields + rename moko-platform everywhere' (#573) from feat/manifest-element-name into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 23s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m8s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-07 17:37:09 +00:00
Jonathan Miller aae7b65329 fix(manifest): sync version_prefix + element_name, rename moko-platform everywhere
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- manifest_sync.go: add VersionPrefix and ElementName to XML parse and DB sync
- deploy workflow: use manifest API for version prefix instead of hardcoded sed
- pre-release workflow: rename moko-platform paths to mokoplatform
- All workflow headers: rename moko-platform references
- manifest.xml: update root element to mokoplatform, add version-prefix field
- Server: symlink /opt/mokoplatform -> /opt/moko-platform for compatibility
2026-06-07 12:36:43 -05:00
gitea-actions[bot] 35f9cd2882 chore(version): pre-release bump to 05.51.03-dev [skip ci] 2026-06-07 17:26:15 +00:00
jmiller b8ad5398a3 Merge pull request 'feat(manifest): element name, version prefix, platform/language dropdowns' (#572) from feat/manifest-element-name into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m48s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 17:25:07 +00:00
Jonathan Miller 09aa8d8201 feat(manifest): add version prefix + fix platform dropdown values
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Add version_prefix field for fork versioning (e.g., v1.26.1-moko.)
- Change platform dropdown from programming languages to deployment
  targets (Joomla, Dolibarr, WordPress, Generic, etc.) so template
  repos can be mapped to platforms
- Available in settings UI and REST API
2026-06-07 12:24:07 -05:00
gitea-actions[bot] b6e88e4baf chore(version): pre-release bump to 05.51.02-dev [skip ci] 2026-06-07 17:13:58 +00:00
jmiller ff9e7183d6 Merge pull request 'release: manifest version prefix + platform/language dropdowns' (#571) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 37s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 17:13:43 +00:00
jmiller d274aabb4f Merge pull request 'feat(manifest): version prefix + platform dropdown fix' (#570) from feat/manifest-version-prefix into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 1m11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m32s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
2026-06-07 17:13:15 +00:00
Jonathan Miller 33ebcd7726 feat(manifest): add version prefix + fix platform dropdown values
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- Add version_prefix field for fork versioning (e.g., v1.26.1-moko.)
- Change platform dropdown from programming languages to deployment
  targets (Joomla, Dolibarr, WordPress, Generic, etc.) so template
  repos can be mapped to platforms
- Available in settings UI and REST API
2026-06-07 12:11:24 -05:00
jmiller 3361ce9f90 fix: correct auto-release.yml with patch detection and --bump none for fix branches [skip ci] 2026-06-07 17:01:10 +00:00
gitea-actions[bot] bcfae6d370 chore(version): pre-release bump to 05.51.01-dev [skip ci] 2026-06-07 16:58:28 +00:00
jmiller c44766106c Merge pull request 'release: v1.26.1-moko.06.12 - rename + changelog + Joomla fix' (#569) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 4m9s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 16:57:58 +00:00
Jonathan Miller 6bb6e2ffd8 chore: rename moko-platform to MokoPlatform + changelog for v1.26.1-moko.06.12
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 31s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m28s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Rename moko-platform references across locale, changelog, and README.
Add changelog entry for dependency scanner, CDN, and Joomla fix.
Closes #548
2026-06-07 11:57:26 -05:00
jmiller 48c9759639 ci: trigger pre-release builds on fix/patch/hotfix/bugfix branches [skip ci] 2026-06-07 16:56:17 +00:00
jmiller 90fb6169d0 feat: auto-detect version bump level from PR source branch [skip ci] 2026-06-07 16:50:22 +00:00
gitea-actions[bot] 5f6d25ff7b chore(release): build 05.51.00 [skip ci] 2026-06-07 16:40:35 +00:00
jmiller 9adcac546f Merge pull request 'release: dependency scanner + CDN release delivery' (#566) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 1m41s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 16:39:23 +00:00
jmiller b6b4d6f525 Merge pull request 'fix(licensing): hide require-key option for Joomla update servers' (#567) from fix/hide-joomla-require-key into dev
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 3m23s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 16:39:09 +00:00
Jonathan Miller 74279c55e3 fix(licensing): hide require-key option for Joomla update servers
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m25s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Joomla's update system does not support license key authentication,
so hide the "Require license key for update feeds" checkbox when
the platform is set to Joomla or Joomla+Dolibarr.
2026-06-07 11:38:05 -05:00
jmiller 78803e60df Merge pull request 'feat(cdn): built-in CDN for release asset delivery' (#565) from feat/cdn-release-delivery into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Successful in 27s
PR RC Release / Build RC Release (pull_request) Failing after 29s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 16:12:41 +00:00
Jonathan Miller 37d59e7b59 feat(cdn): built-in CDN for release asset delivery (#561)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 2m55s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
Add CDN system that serves release assets via a dedicated hostname
(e.g., cdn.mokoconsulting.tech) with per-asset public/private toggles,
IP/referrer allowlists, and aggressive caching headers.

- Host-based routing intercepts CDN domain before auth middleware
- Per-attachment cdn_public flag controls CDN visibility
- Releases in an update stream are excluded from CDN (update server takes precedence)
- CORS, ETag, Cache-Control headers for downstream CDN compatibility
- IP/CIDR and referrer domain allowlists for abuse prevention
2026-06-07 11:07:30 -05:00
jmiller 931d685593 ci: auto pre-release on push to dev/alpha/beta/rc branches [skip ci] 2026-06-07 15:26:05 +00:00
Jonathan Miller 9121f1b36a Merge dev: final version
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 3m49s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 02:21:16 +00:00
Jonathan Miller 4b07ccc578 Merge dev: manifest desc dedup fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Deploy MokoGitea / deploy (push) Failing after 39s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 02:08:43 +00:00
Jonathan Miller 4d42205cc8 Merge dev: wiki version update
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 32s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 01:57:22 +00:00
Jonathan Miller 3a492c5bd5 Merge dev: type settings, MCP SSE, npm workflow
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 29s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-07 00:53:54 +00:00
Jonathan Miller c947ebcb49 Merge dev: error pages login
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 3m47s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 23:26:52 +00:00
Jonathan Miller eef6292832 Merge dev: 403 OAuth login fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 28s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 23:16:50 +00:00
Jonathan Miller 27aeb19dda Merge dev: changelog + MCP update
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 4s
Deploy MokoGitea / deploy (push) Failing after 3m41s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 22:52:07 +00:00
Jonathan Miller d1b2fca784 Merge dev: wiki updates
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 3m53s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 22:27:01 +00:00
Jonathan Miller 971c5fc7a7 Merge dev: first-class type field + list badges
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 27s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 22:13:49 +00:00
Jonathan Miller cd36065464 Merge dev: dashboard badge fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 43s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 21:56:37 +00:00
Jonathan Miller 0debc72356 Merge dev: security tab
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 30s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 21:36:45 +00:00
Jonathan Miller 1ef6ef5fd4 Merge dev: security scanning platform (#508)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 2m58s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 21:25:52 +00:00
Jonathan Miller 62a44a3668 Merge dev: wiki folder fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 30s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 20:52:27 +00:00
Jonathan Miller 3c456dfe85 Merge dev: wiki dir fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 33s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 20:35:33 +00:00
Jonathan Miller 7b75ce9564 Merge dev: wiki type fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 30s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 20:29:05 +00:00
Jonathan Miller 3abd239397 Merge dev: wiki display fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 31s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 20:25:49 +00:00
Jonathan Miller 1e69927cec Merge dev: wiki slash fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 25s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 20:17:00 +00:00
Jonathan Miller c71e622e11 Merge dev: wiki folder navigation
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 37s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 20:10:33 +00:00
Jonathan Miller 2ba5e42113 Merge dev: wiki updates
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 28s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 19:50:43 +00:00
Jonathan Miller 7240deb822 Merge dev: closed issue permissions fix
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 25s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 19:21:58 +00:00
gitea-actions[bot] 727cff9eb8 chore(release): build 05.50.00 [skip ci] 2026-06-06 19:14:38 +00:00
jmiller 4c715d8424 Merge pull request 'release: v1.26.1-moko.06.07.02' (#529) from rc/v1.26.1-moko.06.07.02 into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 13m45s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 19:13:55 +00:00
gitea-actions[bot] c0acdd1f58 chore(release): build 05.49.00 [skip ci] 2026-06-06 18:43:15 +00:00
jmiller c73109e2e6 Merge pull request 'release: v1.26.1-moko.06.07.01' (#527) from rc/v1.26.1-moko.06.07.01 into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 33s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 18:42:28 +00:00
jmiller 3a159b7da6 Merge pull request 'release: v1.26.1-moko.06.07' (#525) from rc/v1.26.1-moko.06.07 into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 33s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 17:56:51 +00:00
jmiller 7c8b20b779 Merge pull request 'release: v1.26.1-moko.06.06.02' (#522) from rc/v1.26.1-moko.06.06.02 into main
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 17:41:13 +00:00
jmiller 4e8af85178 Merge pull request 'release: v1.26.1-moko.06.06.01' (#520) from rc/v1.26.1-moko.06.06.01 into main
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 17:30:28 +00:00
jmiller aa1a67c4cb Merge pull request 'release: v1.26.1-moko.06.06' (#518) from rc/v1.26.1-moko.06.06 into main
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 17:08:02 +00:00
Jonathan Miller 5642057c80 chore: resolve manifest conflict (use RC version)
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 16:25:53 +00:00
Jonathan Miller 4dd27ccdb8 Merge release v1.26.1-moko.06.05.01 (#515) 2026-06-06 16:24:36 +00:00
gitea-actions[bot] 71a7ab04e5 chore(release): build 05.48.00 [skip ci] 2026-06-06 14:50:05 +00:00
jmiller d6dc7533ff Merge pull request 'release: v1.26.1-moko.06.05' (#511) from rc/v1.26.1-moko.06.05 into main
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 14:49:11 +00:00
154 changed files with 6486 additions and 7333 deletions
-73
View File
@@ -1,73 +0,0 @@
# Populates the go module, build, and golangci-lint caches under the default
# branch's cache scope so that PR runs have a warm fallback to restore from.
#
# GitHub Actions caches are scoped per ref: a PR run can only write to its own
# branch's scope, but can read from the base branch's scope as a fallback.
# PRs therefore cannot seed main's scope themselves. Running the same cache
# steps on push-to-main is the only opportunity to populate that fallback
# scope so fresh PR branches start with a useful cache on first run.
# A PR job's exact key lives in its own PR-scope (empty on first run, filled
# by later runs of the same PR); on miss, actions/cache's restore-keys fall
# back to prefix matches against entries this seeder saves in main's scope.
name: cache-seeder
on:
push:
branches:
- main
paths:
- "go.sum"
- ".golangci.yml"
- ".github/actions/go-cache/action.yml"
- ".github/workflows/cache-seeder.yml"
concurrency:
group: cache-seeder
cancel-in-progress: true
permissions:
contents: read
jobs:
gobuild:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: seed
- run: make deps-backend
- run: TAGS="bindata" make backend
- run: TAGS="bindata gogit" GOEXPERIMENT="" make backend
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { job: lint-backend, tags: "bindata", target: "lint-backend" }
- { job: lint-go-windows, tags: "bindata", target: "lint-go-windows" }
- { job: lint-go-gogit, tags: "bindata gogit", target: "lint-go" }
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: ${{ matrix.job }}
lint-cache: "true"
- run: make deps-backend deps-tools
- run: make ${{ matrix.target }}
env:
TAGS: ${{ matrix.tags }}
-31
View File
@@ -1,31 +0,0 @@
name: cron-licenses
on:
# schedule:
# - cron: "7 0 * * 1" # every Monday at 00:07 UTC
workflow_dispatch:
jobs:
cron-licenses:
runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea'
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
- run: make generate-gitignore
timeout-minutes: 40
- name: push translations to repo
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
with:
author_email: "teabot@gitea.io"
author_name: GiteaBot
branch: main
commit: true
commit_message: "[skip ci] Updated licenses and gitignores"
remote: "git@github.com:go-gitea/gitea.git"
ssh_key: ${{ secrets.DEPLOY_KEY }}
-32
View File
@@ -1,32 +0,0 @@
name: cron-renovate
on:
schedule:
- cron: "23 * * * *" # hourly at :23
workflow_dispatch:
concurrency:
group: cron-renovate
env:
RENOVATE_VERSION: 43.141.5 # renovate: datasource=docker depName=ghcr.io/renovatebot/renovate
permissions:
contents: read
jobs:
cron-renovate:
runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea' # prevent running on forks
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: renovatebot/github-action@f66d8679fcfcfa051abde6e7a623007173bf5164 # v46.1.12
with:
renovate-version: ${{ env.RENOVATE_VERSION }}
configurationFile: renovate.json5
token: ${{ secrets.RENOVATE_TOKEN }}
env:
RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks.
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg nolyfill)$"]'
RENOVATE_REPOSITORIES: '["go-gitea/gitea"]'
-40
View File
@@ -1,40 +0,0 @@
name: cron-translations
on:
schedule:
- cron: "7 0 * * *" # every day at 00:07 UTC
workflow_dispatch:
jobs:
crowdin-pull:
runs-on: ubuntu-latest
if: github.repository == 'go-gitea/gitea'
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2
with:
upload_sources: true
upload_translations: false
download_sources: false
download_translations: true
push_translations: false
push_sources: false
create_pull_request: false
config: crowdin.yml
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }}
- name: update locales
run: ./build/update-locales.sh
- name: push translations to repo
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
with:
author_email: "teabot@gitea.io"
author_name: GiteaBot
branch: main
commit: true
commit_message: "[skip ci] Updated translations via Crowdin"
remote: "git@github.com:go-gitea/gitea.git"
ssh_key: ${{ secrets.DEPLOY_KEY }}
-125
View File
@@ -1,125 +0,0 @@
name: files-changed
on:
workflow_call:
outputs:
backend:
value: ${{ jobs.detect.outputs.backend }}
frontend:
value: ${{ jobs.detect.outputs.frontend }}
docs:
value: ${{ jobs.detect.outputs.docs }}
actions:
value: ${{ jobs.detect.outputs.actions }}
templates:
value: ${{ jobs.detect.outputs.templates }}
docker:
value: ${{ jobs.detect.outputs.docker }}
dockerfile:
value: ${{ jobs.detect.outputs.dockerfile }}
swagger:
value: ${{ jobs.detect.outputs.swagger }}
yaml:
value: ${{ jobs.detect.outputs.yaml }}
json:
value: ${{ jobs.detect.outputs.json }}
e2e:
value: ${{ jobs.detect.outputs.e2e }}
permissions:
contents: read
jobs:
detect:
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
backend: ${{ steps.changes.outputs.backend }}
frontend: ${{ steps.changes.outputs.frontend }}
docs: ${{ steps.changes.outputs.docs }}
actions: ${{ steps.changes.outputs.actions }}
templates: ${{ steps.changes.outputs.templates }}
docker: ${{ steps.changes.outputs.docker }}
dockerfile: ${{ steps.changes.outputs.dockerfile }}
swagger: ${{ steps.changes.outputs.swagger }}
yaml: ${{ steps.changes.outputs.yaml }}
json: ${{ steps.changes.outputs.json }}
e2e: ${{ steps.changes.outputs.e2e }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
backend:
- "**/*.go"
- "templates/**/*.tmpl"
- "assets/emoji.json"
- "go.mod"
- "go.sum"
- "Makefile"
- ".golangci.yml"
- ".editorconfig"
- "options/locale/locale_en-US.json"
frontend:
- "*.js"
- "*.ts"
- "web_src/**"
- "tools/*.js"
- "tools/*.ts"
- "assets/emoji.json"
- "package.json"
- "pnpm-lock.yaml"
- "Makefile"
- ".eslintrc.cjs"
- ".npmrc"
docs:
- "**/*.md"
- ".markdownlint.yaml"
- "package.json"
- "pnpm-lock.yaml"
actions:
- ".github/workflows/*"
- "Makefile"
templates:
- "tools/lint-templates-*.js"
- "templates/**/*.tmpl"
- "pyproject.toml"
- "uv.lock"
docker:
- ".github/workflows/pull-docker-dryrun.yml"
- "Dockerfile"
- "Dockerfile.rootless"
- "docker/**"
- "Makefile"
dockerfile:
- "Dockerfile"
- "Dockerfile.rootless"
swagger:
- "templates/swagger/v1_json.tmpl"
- "templates/swagger/v1_input.json"
- "Makefile"
- "package.json"
- "pnpm-lock.yaml"
- ".spectral.yaml"
yaml:
- "**/*.yml"
- "**/*.yaml"
- ".yamllint.yaml"
- "pyproject.toml"
json:
- "**/*.json"
e2e:
- "tests/e2e/**"
- "tools/test-e2e.sh"
- "playwright.config.ts"
-178
View File
@@ -1,178 +0,0 @@
name: compliance
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
files-changed:
uses: ./.github/workflows/files-changed.yml
lint-backend:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: lint-backend
lint-cache: "true"
- run: make deps-backend deps-tools
- run: make lint-backend
env:
TAGS: bindata
lint-on-demand:
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make lint-spell
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
run: uv python install 3.14 && make deps-py lint-templates lint-yaml
- if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.swagger == 'true' || needs.files-changed.outputs.json == 'true'
run: make deps-frontend lint-md lint-swagger lint-json
- if: needs.files-changed.outputs.actions == 'true'
run: make lint-actions
lint-go-windows:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: lint-go-windows
lint-cache: "true"
- run: make deps-backend deps-tools
- run: make lint-go-windows
env:
TAGS: bindata
GOOS: windows
GOARCH: amd64
lint-go-gogit:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: lint-go-gogit
lint-cache: "true"
- run: make deps-backend deps-tools
- run: make lint-go
env:
TAGS: bindata gogit
checks-backend:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: checks-backend
build-cache: "false"
- run: make deps-backend deps-tools
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
frontend:
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend
- run: make lint-frontend
- run: make checks-frontend
- run: make test-frontend
- run: make frontend
backend:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: compliance-backend
- run: make deps-backend generate-go
# no frontend build here as backend should be able to build, even without any frontend files
# CGO is not used when cross-compile, so these steps also test if the code is compatible with CGO disabled
- name: build-backend-arm64
run: go build -o gitea_linux_arm64
env:
GOOS: linux
GOARCH: arm64
TAGS: bindata gogit
- name: build-backend-windows
run: go build -o gitea_windows
env:
GOOS: windows
GOARCH: amd64
TAGS: bindata gogit
- name: build-backend-386
run: go build -o gitea_linux_386
env:
GOOS: linux
GOARCH: 386
-262
View File
@@ -1,262 +0,0 @@
name: db-tests
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
files-changed:
uses: ./.github/workflows/files-changed.yml
test-pgsql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
services:
pgsql:
image: postgres:14
env:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
ldap:
image: gitea/test-openldap:latest
ports:
- "389:389"
- "636:636"
minio:
# as github actions doesn't support "entrypoint", we need to use a non-official image
# that has a custom entrypoint set to "minio server /data"
image: bitnamilegacy/minio:2023.12.23
env:
MINIO_ROOT_USER: 123456
MINIO_ROOT_PASSWORD: 12345678
ports:
- "9000:9000"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: pgsql
- name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
- run: make deps-backend
- run: make backend
env:
TAGS: bindata
- name: run migration tests
run: GITEA_TEST_DATABASE=pgsql make test-migration
- name: run tests
run: GITEA_TEST_DATABASE=pgsql make test-integration
timeout-minutes: 50
env:
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
GOTEST_FLAGS: -race -timeout=40m
TAGS: bindata gogit
TEST_LDAP: 1
test-sqlite:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: sqlite
- run: make deps-backend
- run: make backend
env:
TAGS: bindata gogit
GOEXPERIMENT:
- name: run migration tests
run: GITEA_TEST_DATABASE=sqlite make test-migration
env:
TAGS: bindata gogit
- name: run tests
run: GITEA_TEST_DATABASE=sqlite make test-integration
timeout-minutes: 50
env:
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
GOTEST_FLAGS: -timeout=40m
TAGS: bindata gogit
GOEXPERIMENT:
test-unit:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
env:
discovery.type: single-node
xpack.security.enabled: false
ports:
- "9200:9200"
meilisearch:
image: getmeili/meilisearch:v1
env:
MEILI_ENV: development # disable auth
ports:
- "7700:7700"
redis:
image: redis
options: >- # wait until redis has started
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
ports:
- 6379:6379
minio:
image: bitnamilegacy/minio:2021.12.29
env:
MINIO_ACCESS_KEY: 123456
MINIO_SECRET_KEY: 12345678
ports:
- "9000:9000"
devstoreaccount1.azurite.local: # https://github.com/Azure/Azurite/issues/1583
image: mcr.microsoft.com/azure-storage/azurite:latest
ports:
- 10000:10000
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: unit
build-cache-rotate: "true"
- name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
- run: make deps-backend
- run: make backend
env:
TAGS: bindata
- name: unit-tests
run: make test-backend test-check
env:
GOTEST_FLAGS: -race -timeout=20m
TAGS: bindata
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
- name: unit-tests-gogit
run: make test-backend test-check
env:
GOTEST_FLAGS: -race -timeout=20m
TAGS: bindata gogit
GOEXPERIMENT:
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
test-mysql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
services:
mysql:
# the bitnami mysql image has more options than the official one, it's easier to customize
image: bitnamilegacy/mysql:8.4
env:
ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: testgitea
ports:
- "3306:3306"
options: >-
--mount type=tmpfs,destination=/bitnami/mysql/data
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
env:
discovery.type: single-node
xpack.security.enabled: false
ports:
- "9200:9200"
smtpimap:
image: tabascoterrier/docker-imap-devel:latest
ports:
- "25:25"
- "143:143"
- "587:587"
- "993:993"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: mysql
- name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
- run: make deps-backend
- run: make backend
env:
TAGS: bindata
- name: run migration tests
run: GITEA_TEST_DATABASE=mysql make test-migration
- name: run tests
run: GITEA_TEST_DATABASE=mysql make test-integration
env:
TAGS: bindata
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
test-mssql:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed
runs-on: ubuntu-latest
services:
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest
env:
ACCEPT_EULA: Y
MSSQL_PID: Standard
SA_PASSWORD: MwantsaSecurePassword1
ports:
- "1433:1433"
devstoreaccount1.azurite.local: # https://github.com/Azure/Azurite/issues/1583
image: mcr.microsoft.com/azure-storage/azurite:latest
ports:
- 10000:10000
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: mssql
- name: Add hosts to /etc/hosts
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
- run: make deps-backend
- run: make backend
env:
TAGS: bindata
- run: GITEA_TEST_DATABASE=mssql make test-migration
- name: run tests
run: GITEA_TEST_DATABASE=mssql make test-integration
timeout-minutes: 50
env:
TAGS: bindata
-47
View File
@@ -1,47 +0,0 @@
name: docker-dryrun
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
files-changed:
uses: ./.github/workflows/files-changed.yml
# QEMU-based build is slow (40-50 minutes), so run arm64 and riscv64 when dockerfile changes.
# Run amd64 when any docker-related files change, which is fast (4 minutes).
container-amd64:
if: needs.files-changed.outputs.docker == 'true'
needs: [files-changed]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/docker-dryrun
with:
platform: linux/amd64
container-arm64:
if: needs.files-changed.outputs.dockerfile == 'true'
needs: [files-changed]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/docker-dryrun
with:
platform: linux/arm64
container-riscv64:
if: needs.files-changed.outputs.dockerfile == 'true'
needs: [files-changed]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/docker-dryrun
with:
platform: linux/riscv64
-50
View File
@@ -1,50 +0,0 @@
name: e2e-tests
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
files-changed:
uses: ./.github/workflows/files-changed.yml
test-e2e:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.e2e == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
cache: false
- uses: ./.github/actions/go-cache
with:
cache-name: e2e
build-cache: "false"
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend
- run: make frontend
- run: make deps-backend
- run: make backend
env:
TAGS: bindata
- run: make playwright
- run: make test-e2e
timeout-minutes: 10
env:
TAGS: bindata
FORCE_COLOR: 1
GITEA_TEST_E2E_DEBUG: 1
-20
View File
@@ -1,20 +0,0 @@
name: labeler
on:
pull_request_target:
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
labeler:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
with:
sync-labels: true
-28
View File
@@ -1,28 +0,0 @@
name: pr-title
on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
lint-pr-title:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- run: make lint-pr-title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
-135
View File
@@ -1,135 +0,0 @@
name: release-nightly
on:
push:
branches: [main, release/v*]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
nightly-binary:
runs-on: namespace-profile-gitea-release-binary
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend
# xgo build
- run: make release
env:
TAGS: bindata
- name: import gpg key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
- name: sign binaries
run: |
for f in dist/release/*; do
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
done
# clean branch name to get the folder name in S3
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- name: configure aws
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: upload binaries to s3
run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
nightly-container:
runs-on: namespace-profile-gitea-release-docker
permissions:
contents: read
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
tags: |
type=raw,value=${{ steps.clean_name.outputs.branch }}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
id: meta_rootless
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
suffix=-rootless
tags: |
type=raw,value=${{ steps.clean_name.outputs.branch }}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build regular docker image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
- name: build rootless docker image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta_rootless.outputs.tags }}
annotations: ${{ steps.meta_rootless.outputs.annotations }}
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless,mode=max
-141
View File
@@ -1,141 +0,0 @@
name: release-tag-rc
on:
push:
tags:
- "v1*-rc*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
binary:
runs-on: namespace-profile-gitea-release-binary
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend
# xgo build
- run: make release
env:
TAGS: bindata
- name: import gpg key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
- name: sign binaries
run: |
for f in dist/release/*; do
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
done
# clean branch name to get the folder name in S3
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: upload binaries to s3
run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
- name: Install GH CLI
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
with:
gh-cli-version: 2.39.1
- name: create github release
run: |
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
container:
runs-on: namespace-profile-gitea-release-docker
permissions:
contents: read
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
flavor: |
latest=false
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
id: meta_rootless
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
latest=false
suffix=-rootless
# 1.2.3-rc0
tags: |
type=semver,pattern={{version}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build regular container image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: build rootless container image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta_rootless.outputs.tags }}
annotations: ${{ steps.meta_rootless.outputs.annotations }}
-153
View File
@@ -1,153 +0,0 @@
name: release-tag-version
on:
push:
tags:
- "v1.*"
- "!v1*-rc*"
- "!v1*-dev"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
binary:
runs-on: namespace-profile-gitea-release-binary
permissions:
contents: read
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
check-latest: true
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: make deps-frontend deps-backend
# xgo build
- run: make release
env:
TAGS: bindata
- name: import gpg key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
- name: sign binaries
run: |
for f in dist/release/*; do
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
done
# clean branch name to get the folder name in S3
- name: Get cleaned branch name
id: clean_name
run: |
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
echo "Cleaned name is ${REF_NAME}"
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
- name: configure aws
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: upload binaries to s3
run: |
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
- name: Install GH CLI
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
with:
gh-cli-version: 2.39.1
- name: create github release
run: |
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
container:
runs-on: namespace-profile-gitea-release-docker
permissions:
contents: read
packages: write # to publish to ghcr.io
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
- run: git fetch --unshallow --quiet --tags --force
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
id: meta
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# this will generate tags in the following format:
# latest
# 1
# 1.2
# 1.2.3
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
id: meta_rootless
with:
images: |-
gitea/gitea
ghcr.io/go-gitea/gitea
# each tag below will have the suffix of -rootless
flavor: |
suffix=-rootless,onlatest=true
# this will generate tags in the following format (with -rootless suffix added):
# latest
# 1
# 1.2
# 1.2.3
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
annotations: |
org.opencontainers.image.authors="maintainers@gitea.io"
- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR using PAT
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: build regular container image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
- name: build rootless container image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/riscv64
push: true
file: Dockerfile.rootless
tags: ${{ steps.meta_rootless.outputs.tags }}
annotations: ${{ steps.meta_rootless.outputs.annotations }}
+1 -1
View File
@@ -39,4 +39,4 @@ GITEA_TEST_E2E_FLAGS='<filepath>' make test-e2e # Single Playwright test
- Add `Co-Authored-By` lines to all commits
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
- **Attribution**: `Authored-by: Moko Consulting`
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)
+1 -1
View File
@@ -8,7 +8,7 @@ contact_links:
url: https://mokoconsulting.tech/
about: Get help or ask questions through our website
- name: 📚 MokoStandards Documentation
url: https://code.mokoconsulting.tech/MokoConsulting/moko-platform
url: https://code.mokoconsulting.tech/MokoConsulting/mokoplatform
about: View our coding standards and best practices
- name: 🔒 Report a Security Vulnerability
url: https://code.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
+3 -3
View File
@@ -2,8 +2,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# REPO: https://code.mokoconsulting.tech/MokoConsulting/moko-platform
# INGROUP: mokoplatform.Automation
# REPO: https://code.mokoconsulting.tech/MokoConsulting/mokoplatform
# PATH: /.gitea/workflows/branch-protection.yml
# BRIEF: Apply standardised branch protection rules to all governed repositories
#
@@ -62,7 +62,7 @@ jobs:
API="${GITEA_URL}/api/v1"
# Platform/standards/infra repos to exclude
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting"
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards mokoplatform MokoTesting"
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
if [ -n "${{ inputs.repos }}" ]; then
-20
View File
@@ -1,20 +0,0 @@
<?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>05.47.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://code.mokoconsulting.tech/MokoConsulting/moko-platform</standards-source>
</governance>
<build>
<language>Go</language>
<package-type>application</package-type>
<entry-point>./</entry-point>
</build>
</moko-platform>
-129
View File
@@ -1,129 +0,0 @@
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
DEFGROUP: gitea-api-mcp.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
-->
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- **Renamed** package from `@mokoconsulting/gitea-api-mcp` to `@mokoconsulting/mokogitea-api-mcp` to distinguish Moko's forked Gitea MCP from upstream
- **Renamed** McpServer name and bin entry to `mokogitea-api-mcp`
## [0.0] - 2026-05-07
### Added
#### User / Auth (3 tools)
- `gitea_me` -- Get the authenticated user info
- `gitea_user_orgs` -- List organizations the authenticated user belongs to
- `gitea_user_repos` -- List repositories owned by the authenticated user
#### Repositories (8 tools)
- `gitea_repo_get` -- Get repository details
- `gitea_repo_create` -- Create a new repository
- `gitea_repo_delete` -- Delete a repository
- `gitea_repo_edit` -- Edit repository settings
- `gitea_repo_fork` -- Fork a repository
- `gitea_repo_search` -- Search repositories
- `gitea_org_repos` -- List repositories in an organization
- `gitea_list_connections` -- List configured Gitea connections
#### File Contents (5 tools)
- `gitea_file_get` -- Get file contents from a repository
- `gitea_dir_get` -- Get directory contents (file listing) from a repository
- `gitea_file_create_or_update` -- Create or update a file in a repository
- `gitea_file_delete` -- Delete a file from a repository
- `gitea_tree_get` -- Get the git tree for a repository (recursive file listing)
#### Branches (4 tools)
- `gitea_branches_list` -- List branches in a repository
- `gitea_branch_get` -- Get a specific branch
- `gitea_branch_create` -- Create a new branch
- `gitea_branch_delete` -- Delete a branch
#### Commits (2 tools)
- `gitea_commits_list` -- List commits in a repository
- `gitea_commit_get` -- Get a specific commit
#### Issues (7 tools)
- `gitea_issues_list` -- List issues in a repository
- `gitea_issue_get` -- Get a single issue by number
- `gitea_issue_create` -- Create a new issue
- `gitea_issue_update` -- Update an issue
- `gitea_issue_comments_list` -- List comments on an issue
- `gitea_issue_comment_create` -- Add a comment to an issue
- `gitea_issue_search` -- Search issues across all repositories
#### Labels (2 tools)
- `gitea_labels_list` -- List labels in a repository
- `gitea_label_create` -- Create a label
#### Milestones (2 tools)
- `gitea_milestones_list` -- List milestones in a repository
- `gitea_milestone_create` -- Create a milestone
#### Pull Requests (6 tools)
- `gitea_pulls_list` -- List pull requests
- `gitea_pull_get` -- Get a single pull request
- `gitea_pull_create` -- Create a pull request
- `gitea_pull_merge` -- Merge a pull request
- `gitea_pull_files` -- List files changed in a pull request
- `gitea_pull_review_create` -- Create a pull request review
#### Releases (5 tools)
- `gitea_releases_list` -- List releases
- `gitea_release_get` -- Get a single release by ID
- `gitea_release_latest` -- Get the latest release
- `gitea_release_create` -- Create a new release
- `gitea_release_delete` -- Delete a release
#### Tags (3 tools)
- `gitea_tags_list` -- List tags
- `gitea_tag_create` -- Create a tag
- `gitea_tag_delete` -- Delete a tag
#### Actions (2 tools)
- `gitea_actions_runs_list` -- List workflow runs for a repository
- `gitea_actions_run_get` -- Get a specific workflow run
#### Organizations (3 tools)
- `gitea_org_get` -- Get organization details
- `gitea_org_teams_list` -- List teams in an organization
- `gitea_org_members_list` -- List members of an organization
#### Users (2 tools)
- `gitea_user_get` -- Get a user profile
- `gitea_users_search` -- Search users
#### Webhooks (2 tools)
- `gitea_webhooks_list` -- List webhooks for a repository
- `gitea_webhook_create` -- Create a webhook
#### Wiki (2 tools)
- `gitea_wiki_pages_list` -- List wiki pages
- `gitea_wiki_page_get` -- Get a wiki page
#### Notifications (2 tools)
- `gitea_notifications_list` -- List notifications for the authenticated user
- `gitea_notifications_read` -- Mark all notifications as read
#### Generic (2 tools)
- `gitea_api_request` -- Make a raw API request to any Gitea v1 endpoint
- `gitea_list_connections` -- List configured Gitea connections
### Infrastructure
- Multi-connection config support via `~/.gitea-api-mcp.json`
- Token-based authentication (Gitea native `Authorization: token` header)
- Built on `node:https` / `node:http` (zero HTTP dependencies)
- MCP SDK v1.12.x with stdio transport
[0.0.1]: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp/releases/tag/v0.0.1
-18
View File
@@ -1,18 +0,0 @@
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production=false
COPY tsconfig.json ./
COPY src/ ./src/
RUN npx tsc && npm prune --production
EXPOSE 3100
ENV PORT=3100
ENV NODE_ENV=production
# SSE mode by default for Docker deployments
CMD ["node", "dist/sse.js"]
-116
View File
@@ -1,116 +0,0 @@
# MokoGitea MCP Server
A comprehensive [Model Context Protocol](https://modelcontextprotocol.io) server for [Gitea](https://gitea.com) and [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea). 120+ tools for repos, issues, PRs, projects, releases, custom fields, statuses, priorities, and manifests.
Works with any Gitea instance. MokoGitea-specific features degrade gracefully on vanilla Gitea.
## Quick Start
### npx (no install)
```bash
GITEA_URL=https://gitea.example.com GITEA_TOKEN=your_token npx @mokoconsulting/mokogitea-mcp
```
### Claude Code
Add to `.claude.json`:
```json
{
"mcpServers": {
"mokogitea": {
"command": "npx",
"args": ["@mokoconsulting/mokogitea-mcp"],
"env": {
"GITEA_URL": "https://gitea.example.com",
"GITEA_TOKEN": "your_token"
}
}
}
}
```
### Docker (SSE mode)
```bash
docker run -p 3100:3100 \
-e GITEA_URL=https://gitea.example.com \
-e GITEA_TOKEN=your_token \
mokoconsulting/mokogitea-mcp
```
Connect MCP client to `http://localhost:3100/sse`.
### Multi-instance config
Create `~/.mcp_mokogitea.json`:
```json
{
"defaultConnection": "production",
"connections": {
"production": { "baseUrl": "https://gitea.example.com", "token": "your_token" },
"dev": { "baseUrl": "https://dev.gitea.example.com", "token": "dev_token" }
}
}
```
## Configuration
| Method | Use Case |
|--------|----------|
| `GITEA_URL` + `GITEA_TOKEN` env vars | Single instance, quick setup |
| `~/.mcp_mokogitea.json` config file | Multiple instances |
| `GITEA_API_MCP_CONFIG` env var | Custom config path |
| `GITEA_INSECURE=true` | Skip TLS verification |
## Tools (120+)
### Repositories
`gitea_repo_create` `gitea_repo_get` `gitea_repo_edit` `gitea_repo_delete` `gitea_repo_search` `gitea_repo_fork` `gitea_repo_generate` `gitea_repo_languages` `gitea_repo_contributors` `gitea_repo_topics` `gitea_repo_topics_set`
### Issues
`gitea_issue_create` (dedup by title) `gitea_issue_get` `gitea_issue_update` `gitea_issues_list` `gitea_issue_search` `gitea_issue_comment_create` `gitea_issue_comments_list` `gitea_issue_labels_set` `gitea_issue_bulk_set_status`
### Pull Requests
`gitea_pull_create` `gitea_pull_get` `gitea_pulls_list` `gitea_pull_merge` `gitea_pull_files` `gitea_pull_review_create`
### Branches and Tags
`gitea_branches_list` `gitea_branch_create` `gitea_branch_delete` `gitea_branch_get` `gitea_tags_list` `gitea_tag_create` `gitea_tag_delete`
### Releases
`gitea_releases_list` `gitea_release_create` `gitea_release_get` `gitea_release_latest` `gitea_release_delete` `gitea_release_asset_upload` `gitea_release_asset_delete`
### Files and Trees
`gitea_file_get` `gitea_file_create_or_update` `gitea_file_delete` `gitea_dir_get` `gitea_tree_get` `gitea_bulk_file_push`
### Projects
`gitea_project_list` `gitea_project_create` `gitea_project_get` `gitea_project_update` `gitea_project_delete` `gitea_project_overview` `gitea_project_columns_list` `gitea_project_column_create` `gitea_project_column_delete` `gitea_project_cards_list` `gitea_project_card_add` `gitea_project_card_move` `gitea_project_card_remove`
### Organizations
`gitea_org_get` `gitea_org_repos` `gitea_org_members_list` `gitea_org_teams_list` `gitea_org_labels_list` `gitea_org_label_create`
### Wiki
`gitea_wiki_pages_list` `gitea_wiki_page_get`
### MokoGitea Extensions
`gitea_manifest_get` `gitea_manifest_update` `gitea_org_custom_fields_list` `gitea_org_custom_field_create` `gitea_org_custom_field_delete` `gitea_issue_custom_fields_get` `gitea_issue_custom_fields_set` `gitea_org_issue_statuses_list` `gitea_issue_set_status` `gitea_org_issue_priorities_list` `gitea_issue_set_priority`
### Admin and Other
`gitea_me` `gitea_users_search` `gitea_user_get` `gitea_notifications_list` `gitea_notifications_read` `gitea_commits_list` `gitea_commit_get` `gitea_compare` `gitea_webhooks_list` `gitea_webhook_create` `gitea_admin_users_list` `gitea_admin_orgs_list` `gitea_admin_cron_list` `gitea_admin_cron_run` `gitea_list_connections`
## SSE Server
For hosted deployments:
```
GET / Server info
GET /sse SSE connection endpoint
POST /message Tool call messages
GET /health Health check
```
## License
GPL-3.0-or-later - [Moko Consulting](https://mokoconsulting.tech)
-13
View File
@@ -1,13 +0,0 @@
{
"defaultConnection": "moko",
"connections": {
"moko": {
"baseUrl": "https://git.mokoconsulting.tech",
"token": "your-gitea-access-token"
},
"github-mirror": {
"baseUrl": "https://gitea.example.com",
"token": "your-other-token"
}
}
}
-1198
View File
File diff suppressed because it is too large Load Diff
-58
View File
@@ -1,58 +0,0 @@
{
"name": "@mokoconsulting/mokogitea-mcp",
"version": "1.1.0",
"description": "MCP server for Gitea and MokoGitea - 120+ tools for repos, issues, PRs, projects, releases, custom fields, statuses, priorities, and manifests",
"type": "module",
"main": "dist/index.js",
"bin": {
"mokogitea-mcp": "dist/index.js",
"mokogitea-mcp-sse": "dist/sse.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/index.js",
"start:sse": "node dist/sse.js",
"setup": "node scripts/setup.mjs",
"clean": "rm -rf dist/"
},
"keywords": [
"mcp",
"gitea",
"mokogitea",
"model-context-protocol",
"claude",
"ai",
"git",
"self-hosted",
"api",
"devops"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
"zod": "^3.24.4"
},
"devDependencies": {
"@types/node": "^22.15.3",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=20.0.0"
},
"license": "GPL-3.0-or-later",
"author": "Moko Consulting <hello@mokoconsulting.tech>",
"homepage": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api",
"repository": {
"type": "git",
"url": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api.git"
},
"files": [
"dist/",
"config.example.json",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
}
}
-15
View File
@@ -1,15 +0,0 @@
# mcp_mokogitea_api PowerShell Profile
# Source this with: . ./profile.ps1
$env:MCP_ROOT = $PSScriptRoot
$env:TEMP = 'A:\temp'
$env:TMP = 'A:\temp'
function mcp { Set-Location $PSScriptRoot }
function mcp-src { Set-Location (Join-Path $PSScriptRoot 'src') }
function mcp-build { Set-Location $PSScriptRoot; npm run build }
function mcp-dev { Set-Location $PSScriptRoot; npm run dev }
Write-Host "mcp_mokogitea_api profile loaded" -ForegroundColor Cyan
Write-Host " Commands: mcp-build, mcp-dev" -ForegroundColor DarkGray
Write-Host " Navigate: mcp, mcp-src" -ForegroundColor DarkGray
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env node
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* BRIEF: Interactive setup — prompts for Gitea connection details
*/
import { createInterface } from 'node:readline/promises';
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { homedir } from 'node:os';
const CONFIG_PATH = resolve(homedir(), '.gitea-api-mcp.json');
const rl = createInterface({ input: process.stdin, output: process.stdout });
async function prompt(q, d) { const a = await rl.question(`${q}${d ? ` [${d}]` : ''}: `); return a.trim() || d || ''; }
async function promptRequired(q) { let a = ''; while (!a) { a = (await rl.question(`${q}: `)).trim(); if (!a) console.log(' Required.'); } return a; }
async function main() {
console.log('\n=== gitea-api-mcp Setup ===\n');
let existing = null;
try { existing = JSON.parse(await readFile(CONFIG_PATH, 'utf-8')); console.log(`Existing: ${Object.keys(existing.connections).join(', ')}\n`); } catch {}
const name = await prompt('Connection name', 'moko');
const baseUrl = await promptRequired('Gitea URL (e.g. https://git.mokoconsulting.tech)');
const token = await promptRequired('Access token (Settings > Applications > Generate Token)');
const insecure = (await prompt('Skip TLS verification? (y/N)', 'N')).toLowerCase() === 'y';
const conn = { baseUrl: baseUrl.replace(/\/+$/, ''), token };
if (insecure) conn.insecure = true;
const config = existing ?? { defaultConnection: name, connections: {} };
config.connections[name] = conn;
if (!existing) config.defaultConnection = name;
else if ((await prompt(`Set "${name}" as default? (y/N)`, 'N')).toLowerCase() === 'y') config.defaultConnection = name;
await writeFile(CONFIG_PATH, JSON.stringify(config, null, '\t') + '\n', 'utf-8');
console.log(`\nConfig written to ${CONFIG_PATH}\n`);
rl.close();
}
main().catch(e => { console.error(e.message); rl.close(); process.exit(1); });
-120
View File
@@ -1,120 +0,0 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: gitea-api-mcp.Client
* INGROUP: gitea-api-mcp
* REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
* PATH: /src/client.ts
* VERSION: 01.00.00
* BRIEF: HTTP client for Gitea REST API v1
*/
import * as https from 'node:https';
import * as http from 'node:http';
import type { GiteaConnection, ApiResponse } from './types.js';
const API_PREFIX = '/api/v1';
const TIMEOUT_MS = 30_000;
export class GiteaClient {
private readonly base_url: string;
private readonly headers: Record<string, string>;
private readonly insecure: boolean;
constructor(conn: GiteaConnection) {
this.base_url = conn.baseUrl.replace(/\/+$/, '') + API_PREFIX;
this.headers = {
'Authorization': `token ${conn.token}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
this.insecure = conn.insecure ?? false;
}
async get(endpoint: string, params?: Record<string, string>): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint, params), 'GET');
}
async post(endpoint: string, body?: unknown): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'POST', body);
}
async patch(endpoint: string, body: unknown): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'PATCH', body);
}
async put(endpoint: string, body: unknown): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'PUT', body);
}
async delete(endpoint: string): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'DELETE');
}
private buildUrl(endpoint: string, params?: Record<string, string>): string {
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
const url = new URL(`${this.base_url}${path}`);
if (params) {
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
}
return url.toString();
}
private request(url: string, method: string, body?: unknown): Promise<ApiResponse> {
return new Promise((resolve, reject) => {
const parsed = new URL(url);
const is_https = parsed.protocol === 'https:';
const transport = is_https ? https : http;
const options: https.RequestOptions = {
hostname: parsed.hostname,
port: parsed.port || (is_https ? 443 : 80),
path: parsed.pathname + parsed.search,
method,
headers: { ...this.headers },
timeout: TIMEOUT_MS,
};
if (this.insecure && is_https) {
options.rejectUnauthorized = false;
}
const payload = body !== undefined ? JSON.stringify(body) : undefined;
if (payload) {
(options.headers as Record<string, string>)['Content-Length'] = Buffer.byteLength(payload).toString();
}
const req = transport.request(options, (res) => {
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => chunks.push(chunk));
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf-8');
let data: unknown;
try {
data = JSON.parse(raw);
} catch {
data = raw;
}
resolve({ status: res.statusCode ?? 0, data });
});
});
req.on('error', (err) => reject(err));
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timed out'));
});
if (payload) {
req.write(payload);
}
req.end();
});
}
}
-61
View File
@@ -1,61 +0,0 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { homedir } from 'node:os';
import type { GiteaConfig, GiteaConnection } from './types.js';
const CONFIG_FILENAME = '.mcp_mokogitea.json';
export async function loadConfig(): Promise<GiteaConfig> {
// Priority 1: Environment variables (zero-config single instance)
if (process.env.GITEA_URL && process.env.GITEA_TOKEN) {
const conn: GiteaConnection = {
baseUrl: process.env.GITEA_URL,
token: process.env.GITEA_TOKEN,
insecure: process.env.GITEA_INSECURE === 'true',
};
return {
connections: { default: conn },
defaultConnection: 'default',
};
}
// Priority 2: Config file
const config_path = process.env.GITEA_API_MCP_CONFIG
? resolve(process.env.GITEA_API_MCP_CONFIG)
: resolve(homedir(), CONFIG_FILENAME);
try {
const raw = await readFile(config_path, 'utf-8');
const parsed = JSON.parse(raw) as Partial<GiteaConfig>;
if (!parsed.connections || Object.keys(parsed.connections).length === 0) {
throw new Error('No connections defined in config');
}
return {
connections: parsed.connections,
defaultConnection: parsed.defaultConnection ?? Object.keys(parsed.connections)[0],
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(
`Failed to load config from ${config_path}: ${message}\n` +
`Option 1: Set GITEA_URL and GITEA_TOKEN environment variables\n` +
`Option 2: Create ${config_path} - see config.example.json for format`,
);
}
}
export function getConnection(config: GiteaConfig, name?: string): GiteaConnection {
const key = name ?? config.defaultConnection;
const conn = config.connections[key];
if (!conn) {
throw new Error(
`Connection "${key}" not found. Available: ${Object.keys(config.connections).join(', ')}`,
);
}
return conn;
}
File diff suppressed because it is too large Load Diff
-16
View File
@@ -1,16 +0,0 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Creates a configured MCP server instance for use by both stdio and SSE transports.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { GiteaConfig } from './types.js';
// Import index.ts to register all tools on its exported `server` singleton,
// then re-export a factory that initializes config and returns the server.
import { server, initConfig } from './index.js';
export function createMcpServer(cfg: GiteaConfig): McpServer {
initConfig(cfg);
return server;
}
-100
View File
@@ -1,100 +0,0 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// SSE transport entry point for MokoGitea MCP server.
// Run with: node dist/sse.js
// Or: GITEA_URL=https://gitea.example.com GITEA_TOKEN=xxx node dist/sse.js
//
// Listens on PORT (default 3100) and serves SSE at /sse with POST at /message.
import { createServer } from 'node:http';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { createMcpServer } from './server.js';
import { loadConfig } from './config.js';
const PORT = parseInt(process.env.PORT ?? '3100', 10);
async function main(): Promise<void> {
const config = await loadConfig();
const transports = new Map<string, SSEServerTransport>();
const httpServer = createServer(async (req, res) => {
// CORS headers for browser clients
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// Health check
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', tools: 120 }));
return;
}
// SSE endpoint - client connects here
if (req.url === '/sse' && req.method === 'GET') {
const transport = new SSEServerTransport('/message', res);
const sessionId = transport.sessionId;
transports.set(sessionId, transport);
const server = createMcpServer(config);
await server.connect(transport);
req.on('close', () => {
transports.delete(sessionId);
});
return;
}
// Message endpoint - client sends tool calls here
if (req.url?.startsWith('/message') && req.method === 'POST') {
const url = new URL(req.url, `http://${req.headers.host}`);
const sessionId = url.searchParams.get('sessionId');
if (!sessionId || !transports.has(sessionId)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid or missing sessionId' }));
return;
}
const transport = transports.get(sessionId)!;
await transport.handlePostMessage(req, res);
return;
}
// Root - info page
if (req.url === '/' || req.url === '') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
name: '@mokoconsulting/mokogitea-mcp',
version: '1.1.0',
description: 'MCP server for Gitea and MokoGitea - 120+ tools',
endpoints: {
sse: '/sse',
message: '/message',
health: '/health',
},
docs: 'https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api',
}));
return;
}
res.writeHead(404);
res.end('Not found');
});
httpServer.listen(PORT, () => {
process.stderr.write(`MokoGitea MCP SSE server listening on port ${PORT}\n`);
process.stderr.write(` SSE: http://localhost:${PORT}/sse\n`);
process.stderr.write(` Health: http://localhost:${PORT}/health\n`);
});
}
main().catch((err) => {
process.stderr.write(`Fatal: ${err}\n`);
process.exit(1);
});
-37
View File
@@ -1,37 +0,0 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: gitea-api-mcp.Types
* INGROUP: gitea-api-mcp
* REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
* PATH: /src/types.ts
* VERSION: 01.00.00
* BRIEF: TypeScript type definitions for Gitea API MCP server
*/
export interface GiteaConnection {
baseUrl: string;
token: string;
/** Skip TLS certificate verification (self-signed certs) */
insecure?: boolean;
}
export interface GitHubBackupConfig {
token: string;
org: string;
}
export interface GiteaConfig {
connections: Record<string, GiteaConnection>;
defaultConnection: string;
github?: GitHubBackupConfig;
}
export interface ApiResponse {
status: number;
data: unknown;
}
-19
View File
@@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+66
View File
@@ -0,0 +1,66 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
name: "Universal: Auto Version Bump"
on:
push:
branches:
- dev
- rc
- 'feature/**'
- 'patch/**'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions:
contents: write
jobs:
bump:
name: Version Bump
runs-on: release
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request')
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup mokocli 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/mokocli/cli" ]; then
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
/tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
fi
- name: Bump version
run: |
php ${MOKO_CLI}/version_auto_bump.php \
--path . --branch "${GITHUB_REF_NAME}" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+421 -324
View File
@@ -1,324 +1,421 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
# +========================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+
# | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream |
# | |
# +========================================================================+
name: "Universal: Build & Release"
on:
pull_request:
types: [opened, closed]
branches:
- main
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: false
type: choice
default: release
options:
- release
- promote-rc
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions:
contents: write
jobs:
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
promote-rc:
name: Promote to RC
runs-on: release
if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; 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
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /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: Rename branch to rc
run: |
php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git
run: |
git fetch origin rc
git checkout rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
name: Build & Release Pipeline
runs-on: release
if: >-
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: |
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found — aborting release"
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "No conflict markers found"
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; 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
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /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: "Publish stable release"
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability stable --bump minor --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Update release notes from CHANGELOG.md
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Stable release"
else
NOTES="Stable release"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
- name: "Step 10: Push main to GitHub mirror"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
git fetch origin main --depth=1
git push github origin/main:refs/heads/main --force 2>/dev/null \
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch and recreate dev from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD)
# Delete old version branch if it exists (same version re-release)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
# Create version/XX.YY.ZZ from main
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Post-release: Reset dev version"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
if: always()
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
# +========================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+
# | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream |
# | |
# +========================================================================+
name: "Universal: Build & Release"
on:
pull_request:
types: [opened, closed]
branches:
- main
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: false
type: choice
default: release
options:
- release
- promote-rc
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions:
contents: write
jobs:
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
promote-rc:
name: Promote to RC
runs-on: release
if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; 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
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Rename branch to rc
run: |
php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git
run: |
git fetch origin rc
git checkout rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Update RC release notes from CHANGELOG.md
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Extract [Unreleased] section from changelog
NOTES=""
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
fi
[ -z "$NOTES" ] && NOTES="Release candidate"
# Find the RC release and update its body
RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/release-candidate" \
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${TOKEN}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "RC release notes updated from CHANGELOG.md"
fi
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
name: Build & Release Pipeline
runs-on: release
if: >-
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: |
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found — aborting release"
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "No conflict markers found"
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; 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
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: "Detect platform"
id: platform
run: |
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
- name: "Determine version bump level"
id: bump
run: |
# Fix/patch branches: version was already bumped by pre-release, just strip suffix
# Feature/dev branches: bump minor for the new stable release
HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}"
case "$HEAD_REF" in
fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;;
*) BUMP="minor" ;;
esac
echo "level=${BUMP}" >> "$GITHUB_OUTPUT"
echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})"
- name: "Publish stable release"
run: |
BUMP_FLAG=""
if [ "${{ steps.bump.outputs.level }}" != "none" ]; then
BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}"
fi
php ${MOKO_CLI}/release_publish.php \
--path . --stability stable ${BUMP_FLAG} --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: "Read published version"
id: version
run: |
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag=stable" >> "$GITHUB_OUTPUT"
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
echo "branch=main" >> "$GITHUB_OUTPUT"
echo "Published version: ${VERSION}"
- name: Update release notes and promote changelog
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Get the stable release info (version and ID)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
# Extract version from release name (e.g. "06.17.00" or "v06.17.00")
VERSION=$(python3 -c "
import json, sys, re
r = json.load(sys.stdin)
name = r.get('name', '')
m = re.search(r'(\d+\.\d+\.\d+)', name)
print(m.group(1) if m else '')
" <<< "$RELEASE_JSON" 2>/dev/null || true)
# Extract [Unreleased] section from changelog
NOTES=""
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
fi
[ -z "$NOTES" ] && NOTES="Stable release"
# Update release body via API
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${TOKEN}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
python3 -c "
import sys
version, date = sys.argv[1], sys.argv[2]
content = open('CHANGELOG.md').read()
old = '## [Unreleased]'
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
content = content.replace(old, new, 1)
open('CHANGELOG.md', 'w').write(content)
" "$VERSION" "$DATE"
git add CHANGELOG.md
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
git push origin main || true
echo "Changelog promoted: [Unreleased] → [${VERSION}]"
fi
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
- name: "Step 10: Push main to GitHub mirror"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
git fetch origin main --depth=1
git push github origin/main:refs/heads/main --force 2>/dev/null \
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch and recreate dev from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD)
# Delete old version branch if it exists (same version re-release)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
# Create version/XX.YY.ZZ from main
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Post-release: Reset dev version"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
if: always()
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
+2 -2
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://code.mokoconsulting.tech/MokoConsulting/moko-platform
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge
@@ -32,7 +32,7 @@ jobs:
- name: Delete source branch
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
API="${{ vars.GITEA_URL || 'https://code.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
+10
View File
@@ -0,0 +1,10 @@
# DISABLED — auto-release Step 11 recreates dev from main after every release.
# Cascade-dev is redundant and causes version conflicts when both main and dev
# have different version numbers in templateDetails.xml / manifest.xml.
name: "Cascade Main → Dev (DISABLED)"
on: workflow_dispatch
jobs:
noop:
runs-on: ubuntu-latest
steps:
- run: echo "Cascade disabled — auto-release handles dev recreation"
+191
View File
@@ -0,0 +1,191 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.gitea/workflows/ci-generic.yml
# VERSION: 01.00.00
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
name: "Generic: Project CI"
on:
workflow_dispatch:
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# ── Lint & Validate ───────────────────────────────────────────────────
lint:
name: Lint & Validate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect toolchain
id: detect
run: |
HAS_PHP=false
HAS_NODE=false
[ -f "composer.json" ] && HAS_PHP=true
[ -f "package.json" ] && HAS_NODE=true
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
- name: Setup PHP
if: steps.detect.outputs.has_php == 'true'
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
php -v
- name: Setup Node.js
if: steps.detect.outputs.has_node == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install PHP dependencies
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "composer.json" ]; then
composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
fi
- name: Install Node.js dependencies
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "package.json" ]; then
npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
fi
- name: PHP syntax check
if: steps.detect.outputs.has_php == 'true'
run: |
ERRORS=0
while IFS= read -r -d '' file; do
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
echo "::error file=${file}::PHP syntax error"
ERRORS=$((ERRORS + 1))
fi
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -eq 0 ]; then
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
else
echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: TypeScript/JavaScript lint
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "node_modules/.bin/eslint" ]; then
npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
echo "## ESLint" >> $GITHUB_STEP_SUMMARY
echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
echo "::warning::ESLint config found but eslint not installed"
else
echo "No ESLint configured — skipping"
fi
- name: TypeScript compile check
if: steps.detect.outputs.has_node == 'true'
run: |
if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
fi
- name: PHPStan static analysis
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
fi
# ── Tests ─────────────────────────────────────────────────────────────
test:
name: Tests
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Detect toolchain
id: detect
run: |
HAS_PHP=false
HAS_NODE=false
[ -f "composer.json" ] && HAS_PHP=true
[ -f "package.json" ] && HAS_NODE=true
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
- name: Setup PHP
if: steps.detect.outputs.has_php == 'true'
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
- name: Setup Node.js
if: steps.detect.outputs.has_node == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: |
[ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
[ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; }
- name: Run PHP tests
if: steps.detect.outputs.has_php == 'true'
run: |
if [ -f "vendor/bin/phpunit" ]; then
vendor/bin/phpunit --testdox 2>&1
echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
echo "::warning::PHPUnit config found but phpunit not installed"
else
echo "No PHPUnit configured — skipping"
fi
- name: Run Node.js tests
if: steps.detect.outputs.has_node == 'true'
run: |
if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
npm test 2>&1
echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
else
echo "No test script in package.json — skipping"
fi
- name: Build check
run: |
if [ -f "Makefile" ]; then
make build 2>&1 || echo "::warning::Build failed or not configured"
elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
npm run build 2>&1 || echo "::warning::Build failed"
fi
+87
View File
@@ -0,0 +1,87 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/cleanup.yml
# VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
name: "Universal: Repository Cleanup"
on:
schedule:
- cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC
workflow_dispatch:
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
cleanup:
name: Clean Merged Branches
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GA_TOKEN }}
- name: Delete merged branches
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0
for BRANCH in $BRANCHES; do
# Skip protected branches
case "$BRANCH" in
main|master|develop|release/*|hotfix/*) continue ;;
esac
# Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1))
fi
done
echo "Deleted ${DELETED} merged branch(es)"
- name: Clean old workflow runs
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0
for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1))
done
echo "Deleted ${DELETED} old workflow run(s)"
+126
View File
@@ -0,0 +1,126 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Deploy
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
name: "Universal: Deploy to Dev (Manual)"
on:
workflow_dispatch:
inputs:
clear_remote:
description: 'Delete all remote files before uploading'
required: false
default: 'false'
type: boolean
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
deploy:
name: SFTP Deploy to Dev
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Setup MokoStandards tools
env:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
id: check
env:
HOST: ${{ vars.DEV_FTP_HOST }}
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
PORT: ${{ vars.DEV_FTP_PORT }}
run: |
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "host=$HOST" >> "$GITHUB_OUTPUT"
REMOTE="${PATH_VAR%/}"
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
[ -z "$PORT" ] && PORT="22"
echo "port=$PORT" >> "$GITHUB_OUTPUT"
- name: Deploy via SFTP
if: steps.check.outputs.skip != 'true'
env:
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
> /tmp/sftp-config.json
if [ -n "$SFTP_KEY" ]; then
echo "$SFTP_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
fi
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
else
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Summary
if: always()
run: |
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
else
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
fi
+62 -173
View File
@@ -11,7 +11,7 @@ on:
workflow_dispatch:
inputs:
version:
description: 'Version tag (e.g. v1.26.1-moko.05.01.00)'
description: 'Version tag'
required: true
default: 'latest'
environment:
@@ -28,9 +28,9 @@ concurrency:
cancel-in-progress: false
env:
REGISTRY: code.mokoconsulting.tech
REGISTRY: git.mokoconsulting.tech
IMAGE: mokoconsulting/mokogitea
DEPLOY_HOST: code.mokoconsulting.tech
DEPLOY_HOST: git.mokoconsulting.tech
DEPLOY_PORT: 2918
DEPLOY_USER: mokoconsulting
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -47,8 +47,6 @@ jobs:
- name: Determine settings
id: config
run: |
# On push to main, auto-deploy to production with git-derived version.
# On workflow_dispatch, use the provided inputs.
if [ "${{ github.event_name }}" = "push" ]; then
VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)")
ENV="production"
@@ -56,206 +54,97 @@ jobs:
VERSION="${{ github.event.inputs.version }}"
ENV="${{ github.event.inputs.environment }}"
fi
if [ "$ENV" = "production" ]; then
echo "compose_dir=/opt/gitea" >> $GITHUB_OUTPUT
echo "container=mokogitea" >> $GITHUB_OUTPUT
echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT
echo "branch=main" >> $GITHUB_OUTPUT
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
echo "instance_url=https://code.mokoconsulting.tech" >> $GITHUB_OUTPUT
echo "tag=$VERSION" >> $GITHUB_OUTPUT
else
echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT
echo "container=mokogitea-dev" >> $GITHUB_OUTPUT
echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT
echo "branch=dev" >> $GITHUB_OUTPUT
echo "tag=${VERSION}-dev" >> $GITHUB_OUTPUT
echo "instance_url=https://git.dev.mokoconsulting.tech" >> $GITHUB_OUTPUT
echo "tag=$VERSION-dev" >> $GITHUB_OUTPUT
fi
- name: Enable maintenance mode
- name: Write deploy key
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
echo "Enabling maintenance mode on ${INSTANCE_URL}..."
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/x-www-form-urlencoded" \
"${INSTANCE_URL}/-/admin/config" \
-d 'key=instance.maintenance_mode&value={"AdminWebAccessOnly":true}' \
|| echo "WARNING: Could not enable maintenance mode (instance may be down)"
mkdir -p ~/.ssh
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
- name: Build and deploy via SSH
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
BRANCH: ${{ steps.config.outputs.branch }}
SOURCE_DIR: ${{ steps.config.outputs.source_dir }}
COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }}
CONTAINER: ${{ steps.config.outputs.container }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}'
IMAGE_FMT='Image: ${{ '{{' }}.Config.Image${{ '}}' }}'
SSH_CMD="ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}"
ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} \
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} bash -s <<DEPLOY_EOF
set -e
echo 'SSH connected'
$SSH_CMD "echo 'SSH connected'"
echo 'Cleaning Docker build cache...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
free -m | head -3
# Pre-deploy cleanup: free disk and memory for the build
$SSH_CMD "
echo 'Cleaning Docker build cache and unused images...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Clearing swap...'
sudo swapoff -a && sudo swapon -a 2>/dev/null || true
echo 'Cleanup complete'
free -m | head -3
"
echo 'Pulling source...'
if [ ! -d $SOURCE_DIR/.git ]; then
git clone -b $BRANCH https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git $SOURCE_DIR
fi
cd $SOURCE_DIR
# Ensure remote points to MokoGitea-Fork (not the upstream fork)
git remote set-url origin https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git 2>/dev/null || true
git fetch origin $BRANCH
git reset --hard origin/$BRANCH
# Pull latest source
$SSH_CMD "
set -e
if [ ! -d ${SOURCE_DIR}/.git ]; then
git clone -b ${BRANCH} https://code.mokoconsulting.tech/MokoConsulting/MokoGitea.git ${SOURCE_DIR}
echo 'Building Docker image...'
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \
-f Dockerfile .
echo 'Pushing to registry...'
echo '$REGISTRY_TOKEN' | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
echo 'Restarting container...'
cd $COMPOSE_DIR
sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:$TAG|' docker-compose.yml
docker compose up -d $CONTAINER
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format='$HEALTH_FMT' $CONTAINER 2>/dev/null | grep -q healthy; then
echo 'Container healthy!'
docker inspect --format='$IMAGE_FMT' $CONTAINER
exit 0
fi
cd ${SOURCE_DIR}
git fetch origin ${BRANCH}
git reset --hard origin/${BRANCH}
"
# Build Docker image
$SSH_CMD "
set -e
cd ${SOURCE_DIR}
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG} \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \
-f Dockerfile .
"
# Push to container registry
$SSH_CMD "
set -e
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
"
# Update compose and restart
$SSH_CMD "
set -e
cd ${COMPOSE_DIR}
sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:${TAG}|' docker-compose.yml
docker compose up -d ${CONTAINER}
"
# Health check
$SSH_CMD "
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format='{{.State.Health.Status}}' ${CONTAINER} 2>/dev/null | grep -q healthy; then
echo 'Container healthy!'
docker inspect --format='Image: {{.Config.Image}}' ${CONTAINER}
exit 0
fi
echo \"Waiting... (attempt \$i/8)\"
done
echo 'Health check failed'
docker logs ${CONTAINER} --tail 20
exit 1
"
- name: Update updates.xml
if: success()
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
DEPLOY_ENV: ${{ github.event.inputs.environment || 'production' }}
run: |
# Only update updates.xml for production stable releases
if [ "$DEPLOY_ENV" != "production" ]; then
echo "Skipping updates.xml — dev deployments don't update stable channel"
exit 0
fi
# Extract moko version from tag (e.g. v1.26.1-moko.05.01.01 -> 05.01.01)
MOKO_VER=$(echo "$TAG" | sed -n 's/.*-moko\.\(.*\)/\1/p')
if [ -z "$MOKO_VER" ]; then
echo "Could not extract moko version from tag: $TAG"
exit 0
fi
RELEASE_URL="https://${REGISTRY}/MokoConsulting/MokoGitea/releases/tag/${TAG}"
DOCKER_IMG="${REGISTRY}/${IMAGE}:${TAG}"
python3 << PYEOF
import json, os, re, base64, urllib.request
token = os.environ["GITEA_TOKEN"]
registry = os.environ["REGISTRY"]
tag = os.environ["TAG"]
moko_ver = os.environ["MOKO_VER"]
release_url = os.environ["RELEASE_URL"]
docker_img = os.environ["DOCKER_IMG"]
api = f"https://{registry}/api/v1/repos/MokoConsulting/MokoGitea"
# Fetch current updates.xml
req = urllib.request.Request(f"{api}/contents/updates.xml?ref=main",
headers={"Authorization": f"token {token}"})
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
sha = data["sha"]
content = base64.b64decode(data["content"]).decode("utf-8")
# Update stable channel — match the <update> block containing <tag>stable</tag>
def replace_channel(xml, channel, ver, url, docker):
pattern = rf"(<update>\s*<name>MokoGitea</name>[\s\S]*?<tags><tag>{channel}</tag></tags>[\s\S]*?</update>)"
def replacer(m):
block = m.group(1)
block = re.sub(r"<version>[^<]*</version>", f"<version>{ver}</version>", block)
block = re.sub(r"(<infourl[^>]*>)[^<]*(</infourl>)", rf"\1{url}\2", block)
block = re.sub(r"(<downloadurl[^>]*>)[^<]*(</downloadurl>)", rf"\1{docker}\2", block)
return block
return re.sub(pattern, replacer, xml)
content = replace_channel(content, "stable", moko_ver, release_url, docker_img)
content = re.sub(r"VERSION: [^\n]*", f"VERSION: {moko_ver}", content)
# Push updated file
encoded = base64.b64encode(content.encode()).decode()
payload = json.dumps({
"message": f"chore(ci): update updates.xml to {moko_ver}",
"content": encoded,
"sha": sha,
"branch": "main",
}).encode()
req = urllib.request.Request(f"{api}/contents/updates.xml",
data=payload, method="PUT",
headers={"Authorization": f"token {token}", "Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
print(f"updates.xml updated to {moko_ver}")
PYEOF
- name: Disable maintenance mode
if: always()
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
run: |
echo "Disabling maintenance mode on ${INSTANCE_URL}..."
curl -sf -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/x-www-form-urlencoded" \
"${INSTANCE_URL}/-/admin/config" \
-d 'key=instance.maintenance_mode&value={"AdminWebAccessOnly":false}' \
|| echo "WARNING: Could not disable maintenance mode"
echo "Waiting... (attempt \$i/8)"
done
echo 'Health check failed'
docker logs $CONTAINER --tail 20
exit 1
DEPLOY_EOF
- name: Verify
run: |
sleep 5
curl -sf https://${{ env.DEPLOY_HOST }}/api/healthz && echo " API healthy"
curl -sf https://${{ env.DEPLOY_HOST }}/api/healthz && echo " API healthy"
- name: Notify on failure
if: failure()
+92
View File
@@ -0,0 +1,92 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
#
# +========================================================================+
# | SECRET SCANNING |
# +========================================================================+
# | |
# | Scans commits for leaked secrets using Gitleaks. |
# | |
# | - PR scan: only new commits in the PR |
# | - Scheduled: full repo scan weekly |
# | - Alerts via ntfy on findings |
# | |
# +========================================================================+
name: "Universal: Secret Scanning"
on:
schedule:
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
workflow_dispatch:
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
jobs:
gitleaks:
name: Gitleaks Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
gitleaks version
- name: Scan for secrets
id: scan
run: |
echo "### Secret Scanning" >> $GITHUB_STEP_SUMMARY
ARGS="--source . --verbose --report-format json --report-path /tmp/gitleaks-report.json"
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Scan only PR commits
ARGS="$ARGS --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
echo "Scanning PR commits only" >> $GITHUB_STEP_SUMMARY
else
echo "Full repository scan" >> $GITHUB_STEP_SUMMARY
fi
if gitleaks detect $ARGS 2>&1; then
echo "result=clean" >> "$GITHUB_OUTPUT"
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
else
echo "result=found" >> "$GITHUB_OUTPUT"
FINDINGS=$(jq length /tmp/gitleaks-report.json 2>/dev/null || echo "unknown")
echo "**${FINDINGS} potential secret(s) detected.**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Review the findings and rotate any exposed credentials immediately." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Notify on findings
if: failure() && steps.scan.outputs.result == 'found'
run: |
REPO="${{ github.event.repository.name }}"
curl -sS \
-H "Title: ${REPO} — secrets detected in code" \
-H "Tags: rotating_light,key" \
-H "Priority: urgent" \
-d "Gitleaks found potential secrets. Review and rotate credentials immediately." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
+4 -4
View File
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 05.47.00
# INGROUP: mokocli.Automation
# VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
@@ -19,7 +19,7 @@ permissions:
issues: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://code.mokoconsulting.tech' }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs:
create-branch:
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Create branch and comment
run: |
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}"
+70
View File
@@ -0,0 +1,70 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/notify.yml
# VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure
name: "Universal: Notifications"
on:
workflow_run:
workflows:
- "Joomla Build & Release"
- "Joomla Extension CI"
- "Deploy"
types:
- completed
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }}
jobs:
notify:
name: Send Notification
runs-on: ubuntu-latest
if: >-
github.event.workflow_run.conclusion == 'success' ||
github.event.workflow_run.conclusion == 'failure'
steps:
- name: Notify on success (releases only)
if: >-
github.event.workflow_run.conclusion == 'success' &&
contains(github.event.workflow_run.name, 'Release')
run: |
REPO="${{ github.event.repository.name }}"
WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}"
curl -sS \
-H "Title: ${REPO} released" \
-H "Tags: white_check_mark,package" \
-H "Priority: default" \
-H "Click: ${URL}" \
-d "${WORKFLOW} completed successfully." \
"${NTFY_URL}/${NTFY_TOPIC}"
- name: Notify on failure
if: github.event.workflow_run.conclusion == 'failure'
run: |
REPO="${{ github.event.repository.name }}"
WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}"
curl -sS \
-H "Title: ${REPO} workflow failed" \
-H "Tags: x,warning" \
-H "Priority: high" \
-H "Click: ${URL}" \
-d "${WORKFLOW} failed. Check the run for details." \
"${NTFY_URL}/${NTFY_TOPIC}"
+1 -1
View File
@@ -48,4 +48,4 @@ jobs:
working-directory: .mokogitea/mcp
run: |
npm publish --registry ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/npm/ \
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.GITEA_TOKEN }}
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.MOKOGITEA_TOKEN }}
-90
View File
@@ -1,90 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Enforces branch merge policy:
# feature/* → dev only
# fix/* → dev only
# hotfix/* → dev or main (emergency)
# dev → main only
# alpha/* → dev only
# beta/* → dev only
# rc/* → main only
name: Branch Policy Check
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
check-target:
name: Verify merge target
runs-on: ubuntu-latest
steps:
- name: Check branch policy
run: |
HEAD="${{ github.head_ref }}"
BASE="${{ github.base_ref }}"
echo "PR: ${HEAD} → ${BASE}"
ALLOWED=true
REASON=""
case "$HEAD" in
feature/*|feat/*)
if [ "$BASE" != "dev" ]; then
ALLOWED=false
REASON="Feature branches must target 'dev', not '${BASE}'"
fi
;;
fix/*|bugfix/*)
if [ "$BASE" != "dev" ]; then
ALLOWED=false
REASON="Fix branches must target 'dev', not '${BASE}'"
fi
;;
hotfix/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
fi
;;
alpha/*|beta/*)
if [ "$BASE" != "dev" ]; then
ALLOWED=false
REASON="Pre-release branches must target 'dev', not '${BASE}'"
fi
;;
rc/*)
if [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="Release candidate branches must target 'main', not '${BASE}'"
fi
;;
dev)
if [ "$BASE" != "main" ]; then
ALLOWED=false
REASON="Dev branch can only merge into 'main', not '${BASE}'"
fi
;;
esac
if [ "$ALLOWED" = false ]; then
echo "::error::${REASON}"
echo ""
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
+26
View File
@@ -96,6 +96,32 @@ jobs:
echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
# ── Secret Scanning ──────────────────────────────────────────────────
gitleaks:
name: Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
- name: Scan PR commits for secrets
run: |
if gitleaks detect --source . --verbose \
--log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>&1; then
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
else
echo "::error::Potential secrets detected in PR commits"
exit 1
fi
# ── Code Validation ────────────────────────────────────────────────────
validate:
name: Validate PR
+252 -243
View File
@@ -1,243 +1,252 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
name: "Universal: Pre-Release"
on:
pull_request:
types: [closed]
branches:
- dev
pull_request_target:
types: [synchronize, opened, reopened]
branches:
- main
workflow_dispatch:
inputs:
stability:
description: 'Pre-release channel'
required: true
type: choice
options:
- development
- alpha
- beta
- release-candidate
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
runs-on: release
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') ||
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; 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
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /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: Detect platform
id: platform
run: |
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
run: |
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
STABILITY="release-candidate"
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
case "$STABILITY" in
release-candidate) BUMP="minor" ;;
*) BUMP="patch" ;;
esac
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
# Set stability suffix and verify consistency
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
# Append suffix for output
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Create release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch dev --prerelease
- name: Update release notes from CHANGELOG.md
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
else
NOTES="Release ${VERSION}"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
- name: Build package and upload
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGitea license server
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
--token "${TOKEN}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release"
on:
push:
branches:
- dev
- 'fix/**'
- 'patch/**'
- 'hotfix/**'
- 'bugfix/**'
- 'chore/**'
- alpha
- beta
- rc
workflow_dispatch:
inputs:
stability:
description: 'Pre-release channel'
required: true
type: choice
options:
- development
- alpha
- beta
- release-candidate
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release
if: >-
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.ref_name }}
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
echo Using pre-installed /opt/mokocli
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; 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
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
- name: Detect platform
id: platform
run: |
# Auto-detect and update platform if not set in manifest
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
run: |
# Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then
case "${{ github.ref_name }}" in
rc) STABILITY="release-candidate" ;;
alpha) STABILITY="alpha" ;;
beta) STABILITY="beta" ;;
*) STABILITY="development" ;;
esac
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
case "$STABILITY" in
release-candidate) BUMP="minor" ;;
*) BUMP="patch" ;;
esac
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
# Set stability suffix and verify consistency
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
# Append suffix for output
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Create release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
else
NOTES="Release ${VERSION}"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
- name: Build package and upload
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGitea license server
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
--token "${TOKEN}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
+66
View File
@@ -0,0 +1,66 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/rc-revert.yml
# VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
name: "RC Revert"
on:
pull_request:
types: [closed]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
revert:
name: Rename rc/ back to dev/
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == false &&
startsWith(github.event.pull_request.head.ref, 'rc/')
steps:
- name: Rename branch
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
SUFFIX="${BRANCH#rc/}"
DEV_BRANCH="dev/${SUFFIX}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Create dev/ branch from rc/ branch
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
"${API}" 2>/dev/null || true)
if [ "$STATUS" = "201" ]; then
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
else
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
exit 1
fi
# Delete rc/ branch
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
else
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
fi
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
+4 -3
View File
@@ -7,8 +7,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
@@ -33,7 +33,8 @@ on:
- scripts
- repo
pull_request:
push:
branches:
- main
permissions:
contents: read
+82
View File
@@ -0,0 +1,82 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# PATH: /.gitea/workflows/security-audit.yml
# VERSION: 01.00.00
# BRIEF: Dependency vulnerability scanning for composer and npm packages
name: "Universal: Security Audit"
on:
schedule:
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
pull_request:
branches:
- main
paths:
- 'composer.json'
- 'composer.lock'
- 'package.json'
- 'package-lock.json'
workflow_dispatch:
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
jobs:
audit:
name: Dependency Audit
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Composer audit
if: hashFiles('composer.lock') != ''
run: |
echo "=== Composer Security Audit ==="
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
fi
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "::warning::Composer vulnerabilities found"
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
else
echo "No known vulnerabilities in composer dependencies"
fi
- name: NPM audit
if: hashFiles('package-lock.json') != ''
run: |
echo "=== NPM Security Audit ==="
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
echo "No known vulnerabilities in npm dependencies"
else
echo "::warning::NPM vulnerabilities found"
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
fi
- name: Notify on vulnerabilities
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
run: |
REPO="${{ github.event.repository.name }}"
curl -sS \
-H "Title: ${REPO} has vulnerable dependencies" \
-H "Tags: lock,warning" \
-H "Priority: high" \
-d "Security audit found vulnerabilities. Review dependency updates." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Sync upstream bugs
env:
GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
MOKOGITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGITEA_URL: https://code.mokoconsulting.tech
MOKOGITEA_REPO: MokoConsulting/MokoGitea
UPSTREAM_BRANCH: release/v1.26
@@ -0,0 +1,73 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
name: "Universal: Workflow Sync Trigger"
on:
pull_request:
types: [closed]
branches:
- main
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
sync:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]')
steps:
- name: Determine platform from repo name
id: platform
run: |
REPO="${{ github.event.repository.name }}"
case "$REPO" in
Template-Joomla) PLATFORM="joomla" ;;
Template-Dolibarr) PLATFORM="dolibarr" ;;
Template-Go) PLATFORM="go" ;;
Template-MCP) PLATFORM="mcp" ;;
Template-Generic) PLATFORM="" ;;
*) PLATFORM="" ;;
esac
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokocli
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
- name: Install dependencies
run: |
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
- name: Run workflow sync
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
ARGS="--token ${MOKOGITEA_TOKEN}"
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
ARGS="${ARGS} --phase repos"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$PLATFORM" ]; then
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
+13 -222
View File
@@ -1,229 +1,20 @@
# Changelog
All notable changes to MokoGitea are documented here. Versions follow the format
`v{upstream}-moko.{major}.{minor}` (e.g. `v1.26.1-moko.06.03`).
## [Unreleased]
## [v1.26.1-moko.06.10] - 2026-06-06
### Added
- DLID licensing system: license, entitlement, activation, product_tier, audit_log tables (v359 migration)
- License CRUD with CRC32-checksummed DLID generation and format validation
- Entitlement model with tier-based rebuild and custom entitlement preservation
- Domain activation tracking with limit enforcement and auto-activate on first use
- 13 seeded product tiers from base to enterprise
- DLID-gated update XML endpoint: GET /api/v1/licensing/updates/{product}.xml
- Profile repo fallback chain: .mokogitea > .profile > .github
* FEATURES
* feat(issues): first-class Type field with 12 auto-seeded defaults (Bug, Feature, Enhancement, Task, Documentation, Security, Roadmap, Client, Dolibarr, Infrastructure, Joomla, WaaS)
* feat(issues): first-class Status field with 13 auto-seeded defaults including 7 Pending states
* feat(issues): first-class Priority field with 4 auto-seeded defaults (Critical, High, Medium, Low)
* feat(issues): Type/Status/Priority colored badges in issue list view
* feat(issues): status dropdown replaces close/reopen button in comment form
* feat(security): built-in security scanning platform with secret scanner (15 patterns)
* feat(security): Security tab in repo navigation with alerts, scan controls
* feat(wiki): hierarchical folder navigation with sidebar tree and breadcrumbs
* feat(ui): well-known file tabs (README/LICENSE/CONTRIBUTING/SECURITY/CHANGELOG)
* feat(settings): repo manifest settings with REST API and auto-sync on push
* feat(mcp): public MCP server published to npm (@mokoconsulting/mokogitea-mcp)
* feat(mcp): SSE transport, env var config, Docker support, 120+ tools
* feat(mcp): issue dedup on create, type_id/status_id/priority_id params
## [06.19.00] --- 2026-06-20
* MIGRATIONS
* All org labels migrated to first-class Type/Status/Priority fields and deleted
* Type custom field (id=9) migrated to type_id and deleted
* Status custom field (id=1) deleted (replaced by first-class field)
* Priority labels migrated to priority_id
* Pending labels migrated to status definitions
* Scope labels migrated to type definitions
* Manifests populated for all 61 repos via API
## [06.19.00] --- 2026-06-20
* FIXES
* fix(ui): dashboard issue count badges use label spans instead of strong tags
* fix(wiki): directory check before raw redirect for folder navigation
* fix(wiki): proper display names in sidebar tree (strip dash markers)
* fix: replace non-ASCII em dashes with hyphens for hook compatibility
* fix: hookify __init__.py for stop hook JSON validation
## [06.19.00] --- 2026-06-20
* INFRASTRUCTURE
* npm: @mokoconsulting/mokogitea-mcp@1.1.0 and @mokoconsulting/mokowaas-mcp@1.0.0
* MCP servers consolidated under moko-platform/mcp/servers/
* Remote MCP repos renamed to hyphens
* Wiki restructured into features/, api/, operations/ folders
* Swagger API docs enabled at /api/swagger
## [v1.26.1-moko.06.04] - 2026-06-06
* FEATURES
* feat(licenses): full commercial license management system
* Package archiving with soft-delete and collapsible archived section
* Search keys by customer, domain, key number, email, or payment ref
* Download gating (none/prerelease/all modes)
* Domain lock grace period (DomainLockHours)
* Domain restriction on packages and keys (comma-separated allowed domains)
* RepoScope enforcement — packages scoped to specific repos
* Configurable license key prefix per organization
* Master key auto-generates, sorts first in key list
* License package creation at repo level via modal
* Key generation modal with licensee name, email, and domain fields
* Manual release-to-stream mapping with UI selector
* Double confirmation modals for permanent deletion
* Combolist channel picker (replaces checkboxes)
* Extension metadata in repo settings (per-repo override)
* API: package CRUD, key revoke, key renew, settings GET/PUT
* API: purchase webhook with PaymentRef idempotency
* API: public validation endpoint (no auth)
* Migration v340-v344: all new columns synced
* feat(updates): Update Server system (renamed from "Licensing")
* Joomla XML with SHA256, changelog URL, version from asset filename
* Dolibarr JSON with channel filtering
* WordPress PUC-compatible JSON (plugin-update-checker)
* Composer packages.json
* PrestaShop module update XML
* Drupal update status XML
* WHMCS module update JSON
* Feed always public — downloads gated separately
* Stream-name tags supported alongside version tags
* Omit `<client>` for package extension types
* `<downloadkey>` only when download_gating is prerelease or all
* Version extracted from asset filename (matches actual download)
* Joomla tag values verified: dev, alpha, beta, rc, stable
* feat(orgs): enterprise sub-org hierarchy with parent-child relationships
* feat(repos): three-level visibility — Public (200), Private (403), Hidden (404)
* feat(settings): Update Server settings page with enable toggle in Advanced Settings
* feat(settings): advanced settings on dedicated page with dividing headers
* feat(settings): icons on all settings navbars (repo, org, user, admin)
* feat(ui): styled 403 Access Denied page with inline login form
* feat(issues): custom fields with inline editing in issue sidebar
* feat(issues): pre-fill custom fields from issue template YAML frontmatter (#493)
* Templates specify `custom_fields:` map (field name → default value)
* New issue sidebar shows org-level fields with template defaults pre-selected
* API create issue accepts `custom_fields` map by name
* feat(updateserver): resolve extension metadata from org-level custom fields (#492)
* Cascading fallback: custom fields → config table → repo-derived defaults
* All six generators updated (Joomla, WordPress, Composer, Drupal, PrestaShop, WHMCS)
* Repos can be migrated to custom fields gradually
* feat(ui): two-in-one Update Server / Licenses tab
* No gating: shows "Update Server" tab with feed URLs only
* Gated: shows "Licenses" tab with full key management
* `<downloadkey>` only appears when downloads are gated
* SECURITY
* fix(security): ownership guards on all API handlers (cross-org prevention)
* fix(security): RepoScope JSON parsing (substring matching bug)
* fix(security): CSRF tokens in delete confirmation modals
* fix(security): XSS escaping in WordPress changelog HTML
* fix(security): require login for licenses and actions pages
* fix(security): 403 for all users on private repos (not 404)
* fix(security): licensed private repos allow release viewing for signed-in users
* fix(security): anonymous download access respects download_gating setting
* FIXES
* fix(licenses): explicit xorm column names for UpdateStreamConfig fields
* fix(licenses): feed always public when licensing enabled
* fix(settings): prevent double-highlight on Advanced Settings nav item
* fix(settings): redirect back to /settings/advanced after save
* fix(build): remove stale custom field API routes and dead code
* fix(build): replace invalid UTF-8 character in API comment
* fix(build): permanent fixes for AI migration, feed/file.go, unused imports
* fix(updateserver): version extracted from asset filename (not release title)
* fix(updateserver): omit `<client>` for package types per Joomla spec
* fix(updateserver): `<downloadkey>` only shown when downloads are gated
* fix(updateserver): prevent stream name tag from overriding asset-derived version
* fix(build): restore build/ directory after accidental deletion
* fix(licenses): master key banner removed, master keys sort first in table
* fix(issues): issue sidebar loads org-level fields instead of legacy repo-level fields
## [v1.26.1-moko.05] - 2026-05-31
* BREAKING CHANGES
* Deprecated Issue.Ref branch selector UI (#307)
* Removed branch/tag selector from issue sidebar and new issue form
* DB column and commit-close logic preserved for backward compatibility
* FEATURES
* feat(ui): generic combo-multiselect component (#361)
* Reusable dropdown with search, checkable items, and selected-items display
* Template: `shared/combolist.tmpl`
* feat(updates): extension metadata settings for update feed generation
* feat(licenses): platform enforcement, key deletion, expired key cleanup
* feat(actions): rebrand actions bot user to mokogitea-actions (#233, #234)
* Backward-compatible: recognizes github-actions[bot], gitea-actions[bot]
* feat(actions): actions bot user in branch protection whitelist (#233, #234)
* WhitelistActionsUser, MergeWhitelistActionsUser, ForcePushAllowlistActionsUser
* TECH DEBT
* chore: full namespace migration to code.mokoconsulting.tech (#336, #337, #344)
* fix(blame): set HasSourceRenderedToggle for renderable files (#344)
* fix(settings): translate team permission strings via data-locale (#344)
* fix(dropzone): use relative path for non-image attachment markdown links (#344)
* fix(templates): add required validation to issue dropdown fields (#350)
* refactor(go): replace ValuesRepository with maps.Values (Go 1.21+) (#357)
* refactor(go): remove CanEnableEditor wrapper (#357)
* fix(ts): parseIssueHref uses URL pathname and trims appSubUrl (#360)
* fix(actions): enforce MaxJobNumPerRun (256) limit (#360)
* fix(css): use calc(infinity * 1px) for --border-radius-full (#361)
* fix(css): remove legacy .center class, replace with tw-text-center (#361)
* fix(routes): remove dead legacy /cherry-pick/{sha} route
* fix(feed): use full ref name instead of ShortName for file feed revision
* BUGFIXES
* fix(build): use slices.Collect for maps.Values (Go 1.23+ compat)
* fix(licenses): remove duplicate DeleteLicenseKey declaration
* fix(licenses): only show licenses tab when licensing is enabled
* fix(licenses): show feed URLs based on repo update platform setting
* fix(updates): correct dlid prefix and align XML with Joomla standard
* INFRASTRUCTURE
* fix(ci): auto-deploy to production on merge to main (#235)
## [v1.26.1-moko.04] - 2026-05-24
* SECURITY
* Backport 12 upstream v1.26.2 security fixes:
* golang.org/x/net v0.55.0 security update (#140)
* Token scope enforcement on raw/media/attachment downloads (#141)
* OAuth PKCE hardening and refresh token replay protection (#142)
* Wiki git write and LFS token access enforcement (#143)
* Public-only token filtering in API queries (#144)
* Artifact signature payload hardening (#146)
* AWS credentials encryption (#161)
* Mermaid v11.15.0 security update (#162)
* Composer package permission check (#164)
* BUGFIXES
* fix(actions): nil pointer dereference in concurrency during PR creation (#136)
* fix(ui): actions runs list broken row layout (#138)
* fix: scheduled action panic with null event payload
* fix: treat email addresses case-insensitively
* fix: .mod lexer panic — removed invalid AMPL mapping
* FEATURES
* Joomla-style updates.xml with channel selection
* Update checker with configurable CHANNEL setting
* Admin dashboard update banner with docker pull command
* Upstream bug sync workflow — daily automated issue creation
* PR RC release workflow — auto-build RC on PR to main
* INFRASTRUCTURE
* New 3-part versioning: v{upstream}-moko.{major}.{minor}.{patch}
* Branding updates: error pages, home page, settings link
* Deploy workflow updated for new version format
* PROCESS
* Created `type: bug` and `upstream` labels for automated issue tracking
* Closed 24 upstream bug/security issues after backporting
## [v1.26.1-moko.03] - 2026-05-15
* FEATURES
* feat(api): Bulk issue operations — add/remove/replace labels, close/reopen, set milestone, assignees (#21)
* INFRASTRUCTURE
* Grafana: Standardized kiosk header across all 14 playlist dashboards
* PROCESS
* Reopened 9 closed issues lacking documented testing proof
* Created `pending: testing` label for features awaiting verification
* Established policy: issues must not be closed without documented testing proof
## [1.26.1](https://github.com/go-gitea/gitea/releases/tag/v1.26.1) - 2026-04-21
* BUGFIXES
* Add event.schedule context for schedule actions task (#37320) (#37348)
* Fix an issue where changing an organization's visibility caused problems when users had forked its repositories. (#37324) (#37344)
* Use modern "git update-index --cacheinfo" syntax to support more file names (#37338) (#37343)
* Fix URL related escaping for oauth2 (#37334) (#37340)
* When the requested arch rpm is missing fall back to noarch (#37236) (#37339)
* Fix actions concurrency groups cross-branch leak (#37311) (#37331)
* Fix bug when accessing user badges (#37321) (#37329)
* Fix AppFullLink (#37325) (#37328)
* Fix container auth for public instance (#37290) (#37294)
* Enhance GetActionWorkflow to support fallback references (#37189) (#37283)
* Fix vite manifest update masking build errors (#37279) (#37310)
* Fix Mermaid diagrams failing when node labels contain line breaks (#37296) (#37299)
* Use TriggerEvent instead of Event in workflow runs API response for scheduled runs (#37288) #37360
* Add URL to Learn more about blocking a user. (#37355) #37367
* Fix button layout shift when collapsing file tree in editor (#37363) #37375
* Fix org team assignee/reviewer lookups for team member permissions (#37365) #37391
* Fix repo init README EOL (#37388) #37399
* Fix: dump with default zip type produces uncompressed zip (#37401)#37402
## [06.19.00] --- 2026-06-19
+2 -2
View File
@@ -18,7 +18,7 @@ Custom Gitea fork with Project Board API
---
**Category:** Infrastructure | **Platform:** [moko-platform wiki](https://code.mokoconsulting.tech/MokoConsulting/moko-platform/wiki)
**Category:** Infrastructure | **Platform:** [MokoPlatform wiki](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki)
---
@@ -40,4 +40,4 @@ This project is licensed under the GNU General Public License v3.0 or later -- s
---
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://code.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki/Home)*
+1
Submodule mcp-mokogitea-api added at dbaf91546e
+134
View File
@@ -0,0 +1,134 @@
// Copyright 2026 The MokoGitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package ai
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
// OrgSetting stores AI configuration for an organization.
type OrgSetting struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"UNIQUE NOT NULL"`
Enabled bool `xorm:"NOT NULL DEFAULT true"`
APIKeyEncrypted string `xorm:"TEXT"`
Model string `xorm:"VARCHAR(50) NOT NULL DEFAULT 'claude-sonnet-4-6'"`
RateLimitRequests int `xorm:"NOT NULL DEFAULT 100"`
RateLimitTokensMonth int64 `xorm:"NOT NULL DEFAULT 5000000"`
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
}
func init() {
db.RegisterModel(new(OrgSetting))
db.RegisterModel(new(RepoSetting))
db.RegisterModel(new(UsageLog))
}
// TableName returns the table name for OrgSetting.
func (OrgSetting) TableName() string {
return "ai_org_setting"
}
// RepoSetting stores AI configuration for a repository.
type RepoSetting struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE NOT NULL"`
Enabled bool `xorm:"NOT NULL DEFAULT true"`
AutoReview bool `xorm:"NOT NULL DEFAULT true"`
Strictness string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'standard'"`
IgnorePatterns string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
}
// TableName returns the table name for RepoSetting.
func (RepoSetting) TableName() string {
return "ai_repo_setting"
}
// UsageLog records AI token usage per action.
type UsageLog struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL"`
RepoID int64 `xorm:"INDEX NOT NULL"`
TriggeredByID int64
ActionType string `xorm:"VARCHAR(20) NOT NULL"` // review, chat, agent
Model string `xorm:"VARCHAR(50)"`
TokensInput int64
TokensOutput int64
DurationMs int64
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
}
// TableName returns the table name for UsageLog.
func (UsageLog) TableName() string {
return "ai_usage_log"
}
// GetOrgSetting returns the AI settings for an org, or nil if not configured.
func GetOrgSetting(ctx context.Context, orgID int64) (*OrgSetting, error) {
setting := &OrgSetting{OrgID: orgID}
has, err := db.GetEngine(ctx).Where("org_id = ?", orgID).Get(setting)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return setting, nil
}
// GetRepoSetting returns the AI settings for a repo, or nil if not configured.
func GetRepoSetting(ctx context.Context, repoID int64) (*RepoSetting, error) {
setting := &RepoSetting{RepoID: repoID}
has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Get(setting)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return setting, nil
}
// CreateOrgSetting inserts a new org AI setting.
func CreateOrgSetting(ctx context.Context, setting *OrgSetting) error {
setting.CreatedUnix = timeutil.TimeStampNow()
setting.UpdatedUnix = timeutil.TimeStampNow()
_, err := db.GetEngine(ctx).Insert(setting)
return err
}
// UpdateOrgSetting updates an existing org AI setting.
func UpdateOrgSetting(ctx context.Context, setting *OrgSetting) error {
setting.UpdatedUnix = timeutil.TimeStampNow()
_, err := db.GetEngine(ctx).ID(setting.ID).AllCols().Update(setting)
return err
}
// CreateRepoSetting inserts a new repo AI setting.
func CreateRepoSetting(ctx context.Context, setting *RepoSetting) error {
setting.CreatedUnix = timeutil.TimeStampNow()
setting.UpdatedUnix = timeutil.TimeStampNow()
_, err := db.GetEngine(ctx).Insert(setting)
return err
}
// UpdateRepoSetting updates an existing repo AI setting.
func UpdateRepoSetting(ctx context.Context, setting *RepoSetting) error {
setting.UpdatedUnix = timeutil.TimeStampNow()
_, err := db.GetEngine(ctx).ID(setting.ID).AllCols().Update(setting)
return err
}
// LogUsage records an AI usage event.
func LogUsage(ctx context.Context, log *UsageLog) error {
log.CreatedUnix = timeutil.TimeStampNow()
_, err := db.GetEngine(ctx).Insert(log)
return err
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(LicenseActivation))
}
// LicenseActivation tracks a domain that has activated a license.
type LicenseActivation struct {
ID int64 `xorm:"pk autoincr"`
LicenseID int64 `xorm:"INDEX NOT NULL"`
Domain string `xorm:"VARCHAR(255) NOT NULL"`
IPAddress string `xorm:"VARCHAR(64)"`
JoomlaVer string `xorm:"VARCHAR(20)"`
ActivatedAt timeutil.TimeStamp `xorm:"CREATED"`
LastSeenAt timeutil.TimeStamp
}
func (LicenseActivation) TableName() string {
return "license_activation"
}
// GetActivationsByLicense returns all domain activations for a license.
func GetActivationsByLicense(ctx context.Context, licenseID int64) ([]*LicenseActivation, error) {
var acts []*LicenseActivation
return acts, db.GetEngine(ctx).Where("license_id = ?", licenseID).Find(&acts)
}
// CountActivations returns the number of activated domains for a license.
func CountActivations(ctx context.Context, licenseID int64) (int64, error) {
return db.GetEngine(ctx).Where("license_id = ?", licenseID).Count(new(LicenseActivation))
}
// ActivateDomain registers a domain for a license. Returns the activation
// (existing or new) and whether it was newly created.
func ActivateDomain(ctx context.Context, licenseID int64, domain, ip, joomlaVer string, maxDomains int) (*LicenseActivation, bool, error) {
// Check if already activated
existing := new(LicenseActivation)
has, err := db.GetEngine(ctx).
Where("license_id = ? AND domain = ?", licenseID, domain).
Get(existing)
if err != nil {
return nil, false, err
}
if has {
// Update last seen
existing.LastSeenAt = timeutil.TimeStampNow()
existing.IPAddress = ip
if joomlaVer != "" {
existing.JoomlaVer = joomlaVer
}
_, _ = db.GetEngine(ctx).ID(existing.ID).Cols("last_seen_at", "ip_address", "joomla_ver").Update(existing)
return existing, false, nil
}
// Check domain limit (0 = unlimited)
if maxDomains > 0 {
count, err := CountActivations(ctx, licenseID)
if err != nil {
return nil, false, err
}
if count >= int64(maxDomains) {
return nil, false, ErrDomainLimitReached{LicenseID: licenseID, Max: maxDomains}
}
}
act := &LicenseActivation{
LicenseID: licenseID,
Domain: domain,
IPAddress: ip,
JoomlaVer: joomlaVer,
}
_, err = db.GetEngine(ctx).Insert(act)
if err != nil {
return nil, false, err
}
return act, true, nil
}
// DeactivateDomain removes a domain activation.
func DeactivateDomain(ctx context.Context, licenseID int64, domain string) error {
_, err := db.GetEngine(ctx).
Where("license_id = ? AND domain = ?", licenseID, domain).
Delete(new(LicenseActivation))
return err
}
// ErrDomainLimitReached is returned when a license has reached its max activated domains.
type ErrDomainLimitReached struct {
LicenseID int64
Max int
}
func (e ErrDomainLimitReached) Error() string {
return "license domain limit reached"
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(LicenseAuditLog))
}
// LicenseAuditLog records status transitions and other license events.
type LicenseAuditLog struct {
ID int64 `xorm:"pk autoincr"`
LicenseID int64 `xorm:"INDEX NOT NULL"`
Action string `xorm:"VARCHAR(50) NOT NULL"` // status_change, tier_change, domain_activate, domain_deactivate
OldValue string `xorm:"VARCHAR(100)"`
NewValue string `xorm:"VARCHAR(100)"`
CreatedAt timeutil.TimeStamp `xorm:"INDEX CREATED"`
}
func (LicenseAuditLog) TableName() string {
return "license_audit_log"
}
// LogLicenseAudit records a license event.
func LogLicenseAudit(ctx context.Context, licenseID int64, action, oldVal, newVal string) error {
entry := &LicenseAuditLog{
LicenseID: licenseID,
Action: action,
OldValue: oldVal,
NewValue: newVal,
}
_, err := db.GetEngine(ctx).Insert(entry)
return err
}
// GetAuditLog returns audit entries for a license, newest first.
func GetAuditLog(ctx context.Context, licenseID int64) ([]*LicenseAuditLog, error) {
var entries []*LicenseAuditLog
return entries, db.GetEngine(ctx).
Where("license_id = ?", licenseID).
OrderBy("created_at DESC").
Find(&entries)
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(LicenseEntitlement))
}
// LicenseEntitlement maps a license to an individual product (repo) it can access.
type LicenseEntitlement struct {
ID int64 `xorm:"pk autoincr"`
LicenseID int64 `xorm:"INDEX NOT NULL"`
ProductCode string `xorm:"VARCHAR(30) NOT NULL"`
RepoOwner string `xorm:"VARCHAR(100) NOT NULL DEFAULT 'MokoConsulting'"`
RepoName string `xorm:"VARCHAR(100) NOT NULL"`
IsCustom bool `xorm:"NOT NULL DEFAULT false"` // true = manually added, survives tier changes
CreatedAt timeutil.TimeStamp `xorm:"CREATED"`
}
func (LicenseEntitlement) TableName() string {
return "license_entitlement"
}
// GetEntitlementsByLicense returns all entitlements for a license.
func GetEntitlementsByLicense(ctx context.Context, licenseID int64) ([]*LicenseEntitlement, error) {
var ents []*LicenseEntitlement
return ents, db.GetEngine(ctx).Where("license_id = ?", licenseID).Find(&ents)
}
// HasEntitlement checks if a license has access to a specific product code.
func HasEntitlement(ctx context.Context, licenseID int64, productCode string) (bool, error) {
return db.GetEngine(ctx).
Where("license_id = ? AND product_code = ?", licenseID, productCode).
Exist(new(LicenseEntitlement))
}
// AddCustomEntitlement adds a manual entitlement that survives tier changes.
func AddCustomEntitlement(ctx context.Context, licenseID int64, productCode, repoName string) error {
ent := &LicenseEntitlement{
LicenseID: licenseID,
ProductCode: productCode,
RepoOwner: "MokoConsulting",
RepoName: repoName,
IsCustom: true,
}
_, err := db.GetEngine(ctx).Insert(ent)
return err
}
// RebuildEntitlements deletes non-custom entitlements and rebuilds from the product tier.
// Custom entitlements (manually added) are preserved.
func RebuildEntitlements(ctx context.Context, licenseID int64, tierKey string) error {
// Delete non-custom entitlements
_, err := db.GetEngine(ctx).
Where("license_id = ? AND is_custom = ?", licenseID, false).
Delete(new(LicenseEntitlement))
if err != nil {
return err
}
// Look up tier
tier, err := GetProductTierByKey(ctx, tierKey)
if err != nil || tier == nil {
return err
}
// Parse repos JSON
var repos []string
if err := json.Unmarshal([]byte(tier.Repos), &repos); err != nil {
return err
}
// Build product code from repo name (lowercase, stripped)
for _, repoName := range repos {
productCode := repoName
// Check if this entitlement already exists (custom)
exists, err := db.GetEngine(ctx).
Where("license_id = ? AND product_code = ?", licenseID, productCode).
Exist(new(LicenseEntitlement))
if err != nil {
return err
}
if exists {
continue
}
ent := &LicenseEntitlement{
LicenseID: licenseID,
ProductCode: productCode,
RepoOwner: "MokoConsulting",
RepoName: repoName,
IsCustom: false,
}
if _, err := db.GetEngine(ctx).Insert(ent); err != nil {
return err
}
}
return nil
}
+184
View File
@@ -0,0 +1,184 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"hash/crc32"
"strings"
"time"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(License))
}
// License represents a consumer-facing license with a DLID (Download ID).
type License struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX NOT NULL"`
DLID string `xorm:"VARCHAR(36) UNIQUE NOT NULL"`
Tier string `xorm:"VARCHAR(30) NOT NULL DEFAULT 'base'"`
MaxDomains int `xorm:"NOT NULL DEFAULT 1"`
Status string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'active'"` // active, expired, revoked, suspended
ExpiresAt timeutil.TimeStamp `xorm:"INDEX"`
Notes string `xorm:"TEXT"`
CreatedAt timeutil.TimeStamp `xorm:"INDEX CREATED"`
UpdatedAt timeutil.TimeStamp `xorm:"UPDATED"`
}
func (License) TableName() string {
return "license"
}
// IsExpired returns true if the license has a set expiry that has passed.
func (l *License) IsExpired() bool {
if l.ExpiresAt == 0 {
return false
}
return time.Unix(int64(l.ExpiresAt), 0).Before(time.Now())
}
// IsActive returns true if the license status is "active" and not expired.
func (l *License) IsActive() bool {
return l.Status == "active" && !l.IsExpired()
}
// GenerateDLID creates a new DLID: 28 random hex chars + 4 CRC32 checksum chars,
// formatted as 8-8-8-8 groups.
func GenerateDLID() (string, error) {
b := make([]byte, 14) // 14 bytes = 28 hex chars
if _, err := rand.Read(b); err != nil {
return "", err
}
prefix := hex.EncodeToString(b)
checksum := crc32.ChecksumIEEE([]byte(prefix))
full := fmt.Sprintf("%s%04x", prefix, checksum&0xFFFF)
// Format as 8-8-8-8
return fmt.Sprintf("%s-%s-%s-%s", full[0:8], full[8:16], full[16:24], full[24:32]), nil
}
// ValidateDLIDFormat checks if a DLID has valid format and CRC32 checksum.
// This is a client-side check that catches typos without a database hit.
func ValidateDLIDFormat(dlid string) bool {
clean := strings.ReplaceAll(dlid, "-", "")
if len(clean) != 32 {
return false
}
// Validate hex
if _, err := hex.DecodeString(clean); err != nil {
return false
}
// CRC32 check: last 4 chars should match CRC32 of first 28
prefix := clean[:28]
expected := fmt.Sprintf("%04x", crc32.ChecksumIEEE([]byte(prefix))&0xFFFF)
return clean[28:32] == expected
}
// CreateLicense creates a new license with an auto-generated DLID.
func CreateLicense(ctx context.Context, userID int64, tier string, maxDomains int, expiresAt timeutil.TimeStamp) (*License, error) {
dlid, err := GenerateDLID()
if err != nil {
return nil, err
}
license := &License{
UserID: userID,
DLID: dlid,
Tier: tier,
MaxDomains: maxDomains,
Status: "active",
ExpiresAt: expiresAt,
}
_, err = db.GetEngine(ctx).Insert(license)
if err != nil {
return nil, err
}
return license, nil
}
// GetLicenseByDLID looks up a license by its DLID string.
func GetLicenseByDLID(ctx context.Context, dlid string) (*License, error) {
license := new(License)
has, err := db.GetEngine(ctx).Where("dlid = ?", dlid).Get(license)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return license, nil
}
// GetLicenseByID returns a license by primary key.
func GetLicenseByID(ctx context.Context, id int64) (*License, error) {
license := new(License)
has, err := db.GetEngine(ctx).ID(id).Get(license)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return license, nil
}
// GetLicensesByUser returns all licenses for a user.
func GetLicensesByUser(ctx context.Context, userID int64) ([]*License, error) {
var licenses []*License
return licenses, db.GetEngine(ctx).Where("user_id = ?", userID).Find(&licenses)
}
// UpdateLicenseTier changes a license's tier, rebuilds entitlements, and logs the change.
func UpdateLicenseTier(ctx context.Context, licenseID int64, newTier string) error {
license, err := GetLicenseByID(ctx, licenseID)
if err != nil || license == nil {
return err
}
oldTier := license.Tier
_, err = db.GetEngine(ctx).ID(licenseID).Cols("tier", "updated_at").Update(&License{Tier: newTier})
if err != nil {
return err
}
if err := LogLicenseAudit(ctx, licenseID, "tier_change", oldTier, newTier); err != nil {
return err
}
return RebuildEntitlements(ctx, licenseID, newTier)
}
// SetLicenseStatus updates the status field and logs the transition.
func SetLicenseStatus(ctx context.Context, licenseID int64, status string) error {
license, err := GetLicenseByID(ctx, licenseID)
if err != nil || license == nil {
return err
}
oldStatus := license.Status
_, err = db.GetEngine(ctx).ID(licenseID).Cols("status", "updated_at").Update(&License{Status: status})
if err != nil {
return err
}
return LogLicenseAudit(ctx, licenseID, "status_change", oldStatus, status)
}
// RevokeLicense permanently revokes a license.
func RevokeLicense(ctx context.Context, licenseID int64) error {
return SetLicenseStatus(ctx, licenseID, "revoked")
}
// SuspendLicense temporarily suspends a license.
func SuspendLicense(ctx context.Context, licenseID int64) error {
return SetLicenseStatus(ctx, licenseID, "suspended")
}
// ReactivateLicense restores a suspended or expired license to active.
func ReactivateLicense(ctx context.Context, licenseID int64) error {
return SetLicenseStatus(ctx, licenseID, "active")
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
)
func init() {
db.RegisterModel(new(ProductTier))
}
// ProductTier defines a licensing tier and its entitled repositories.
type ProductTier struct {
ID int64 `xorm:"pk autoincr"`
TierKey string `xorm:"VARCHAR(30) UNIQUE NOT NULL"`
TierName string `xorm:"VARCHAR(100) NOT NULL"`
Repos string `xorm:"TEXT"` // JSON array of repo names
MaxDomains int `xorm:"NOT NULL DEFAULT 1"`
SortOrder int `xorm:"NOT NULL DEFAULT 0"`
}
func (ProductTier) TableName() string {
return "product_tier"
}
// RepoList parses the Repos JSON field into a string slice.
func (t *ProductTier) RepoList() []string {
var repos []string
if t.Repos == "" {
return repos
}
_ = json.Unmarshal([]byte(t.Repos), &repos)
return repos
}
// GetProductTierByKey looks up a tier by its key (e.g. "pos", "suite").
func GetProductTierByKey(ctx context.Context, key string) (*ProductTier, error) {
tier := new(ProductTier)
has, err := db.GetEngine(ctx).Where("tier_key = ?", key).Get(tier)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return tier, nil
}
// GetAllProductTiers returns all tiers ordered by sort_order.
func GetAllProductTiers(ctx context.Context) ([]*ProductTier, error) {
var tiers []*ProductTier
return tiers, db.GetEngine(ctx).OrderBy("sort_order ASC").Find(&tiers)
}
+8
View File
@@ -428,6 +428,14 @@ func prepareMigrationTasks() []*migration {
newMigration(348, "Add issue priority definitions table", v1_27.AddIssuePriorityDefTable),
newMigration(349, "Add security scanning tables", v1_27.AddSecurityScanningTables),
newMigration(350, "Add issue type definitions table", v1_27.AddIssueTypeDefTable),
newMigration(351, "Add CDN public flag to attachments", v1_27.AddAttachmentCDNPublic),
newMigration(352, "Add version prefix and element name to repo manifest", v1_27.AddManifestVersionPrefixAndElement),
newMigration(353, "Add distribution metadata fields to repo manifest", v1_27.AddManifestDistributionFields),
newMigration(354, "Add org wiki settings to user table", v1_27.AddOrgWikiSettings),
newMigration(355, "Migrate update server metadata to repo manifest", v1_27.MigrateUpdateServerFieldsToManifest),
newMigration(356, "Rename package_type to extension_type in repo manifest", v1_27.RenamePackageTypeToExtensionType),
newMigration(357, "Drop display_name from repo manifest and update stream config", v1_27.DropDisplayNameColumns),
newMigration(358, "Add licensing tables (license, entitlement, activation, product_tier)", v1_27.AddLicensingTables),
}
return preparedMigrations
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2026 The MokoGitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_27
import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
"xorm.io/xorm"
)
type aiOrgSetting struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"UNIQUE NOT NULL"`
Enabled bool `xorm:"NOT NULL DEFAULT true"`
APIKeyEncrypted string `xorm:"TEXT"`
Model string `xorm:"VARCHAR(50) NOT NULL DEFAULT 'claude-sonnet-4-6'"`
RateLimitRequests int `xorm:"NOT NULL DEFAULT 100"`
RateLimitTokensMonth int64 `xorm:"NOT NULL DEFAULT 5000000"`
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
}
func (aiOrgSetting) TableName() string {
return "ai_org_setting"
}
type aiRepoSetting struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE NOT NULL"`
Enabled bool `xorm:"NOT NULL DEFAULT true"`
AutoReview bool `xorm:"NOT NULL DEFAULT true"`
Strictness string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'standard'"`
IgnorePatterns string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
}
func (aiRepoSetting) TableName() string {
return "ai_repo_setting"
}
type aiUsageLog struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL"`
RepoID int64 `xorm:"INDEX NOT NULL"`
TriggeredByID int64
ActionType string `xorm:"VARCHAR(20) NOT NULL"`
Model string `xorm:"VARCHAR(50)"`
TokensInput int64
TokensOutput int64
DurationMs int64
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
}
func (aiUsageLog) TableName() string {
return "ai_usage_log"
}
func AddAITables(x *xorm.Engine) error {
return x.Sync(new(aiOrgSetting), new(aiRepoSetting), new(aiUsageLog))
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
)
// AddRepoManifestTable creates the repo_manifest table for storing
// moko-platform manifest settings per repository.
// mokoplatform manifest settings per repository.
func AddRepoManifestTable(x *xorm.Engine) error {
type RepoManifest struct {
ID int64 `xorm:"pk autoincr"`
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// AddAttachmentCDNPublic adds the cdn_public column to the attachment table.
func AddAttachmentCDNPublic(x *xorm.Engine) error {
type Attachment struct {
CDNPublic bool `xorm:"NOT NULL DEFAULT false 'cdn_public'"`
}
return x.Sync(new(Attachment))
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// AddManifestVersionPrefixAndElement adds version_prefix and element_name columns to repo_manifest.
func AddManifestVersionPrefixAndElement(x *xorm.Engine) error {
type RepoManifest struct {
VersionPrefix string `xorm:"TEXT 'version_prefix'"`
ElementName string `xorm:"TEXT 'element_name'"`
}
return x.Sync(new(RepoManifest))
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// AddManifestDistributionFields adds distribution metadata fields to repo_manifest
// for update server feed generation (consolidating from UpdateStreamConfig).
func AddManifestDistributionFields(x *xorm.Engine) error {
type RepoManifest struct {
DisplayName string `xorm:"TEXT 'display_name'"`
Maintainer string `xorm:"TEXT 'maintainer'"`
MaintainerURL string `xorm:"TEXT 'maintainer_url'"`
InfoURL string `xorm:"TEXT 'info_url'"`
TargetVersion string `xorm:"TEXT 'target_version'"`
PHPMinimum string `xorm:"VARCHAR(20) 'php_minimum'"`
}
return x.Sync(new(RepoManifest))
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// AddOrgWikiSettings adds wiki_mode and wiki_url columns to the user table
// for configuring org-level wiki behavior (internal convention repos vs external link).
func AddOrgWikiSettings(x *xorm.Engine) error {
type User struct {
WikiMode string `xorm:"VARCHAR(20) NOT NULL DEFAULT '' 'wiki_mode'"`
WikiURL string `xorm:"TEXT 'wiki_url'"`
}
return x.Sync(new(User))
}
+108
View File
@@ -0,0 +1,108 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// MigrateUpdateServerFieldsToManifest copies extension metadata from
// update_stream_config into repo_manifest where the manifest fields are empty.
// This consolidates the source of truth into repo_manifest.
func MigrateUpdateServerFieldsToManifest(x *xorm.Engine) error {
// Copy display_name from config to manifest where manifest is empty
_, err := x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.display_name = c.display_name
WHERE (m.display_name IS NULL OR m.display_name = '') AND c.display_name != ''
`)
if err != nil {
return err
}
// Copy extension_name → element_name
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.element_name = c.extension_name
WHERE (m.element_name IS NULL OR m.element_name = '') AND c.extension_name != ''
`)
if err != nil {
return err
}
// Copy extension_type → package_type
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.package_type = c.extension_type
WHERE (m.package_type IS NULL OR m.package_type = '') AND c.extension_type != ''
`)
if err != nil {
return err
}
// Copy target_version
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.target_version = c.target_version
WHERE (m.target_version IS NULL OR m.target_version = '') AND c.target_version != ''
`)
if err != nil {
return err
}
// Copy maintainer
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.maintainer = c.maintainer
WHERE (m.maintainer IS NULL OR m.maintainer = '') AND c.maintainer != ''
`)
if err != nil {
return err
}
// Copy maintainer_url
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.maintainer_url = c.maintainer_url
WHERE (m.maintainer_url IS NULL OR m.maintainer_url = '') AND c.maintainer_url != ''
`)
if err != nil {
return err
}
// Copy info_url
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.info_url = c.info_url
WHERE (m.info_url IS NULL OR m.info_url = '') AND c.info_url != ''
`)
if err != nil {
return err
}
// Copy php_minimum
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.php_minimum = c.php_minimum
WHERE (m.php_minimum IS NULL OR m.php_minimum = '') AND c.php_minimum != ''
`)
if err != nil {
return err
}
// Copy platform from config to manifest where manifest platform is empty
_, err = x.Exec(`
UPDATE repo_manifest m
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
SET m.platform = c.platform
WHERE (m.platform IS NULL OR m.platform = '') AND c.platform != ''
`)
return err
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// RenamePackageTypeToExtensionType renames the package_type column to extension_type
// in the repo_manifest table for clarity (the field represents the Joomla extension type,
// not just packages).
func RenamePackageTypeToExtensionType(x *xorm.Engine) error {
_, err := x.Exec("ALTER TABLE repo_manifest CHANGE COLUMN package_type extension_type VARCHAR(50) NOT NULL DEFAULT ''")
return err
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// DropDisplayNameColumns removes the display_name column from repo_manifest
// and update_stream_config. Display name is now computed from extension_type + name.
func DropDisplayNameColumns(x *xorm.Engine) error {
// MySQL 8.0.x does not support DROP COLUMN IF EXISTS — check first.
for _, table := range []string{"repo_manifest", "update_stream_config"} {
var count int
if _, err := x.SQL("SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'display_name'", table).Get(&count); err != nil {
return err
}
if count > 0 {
if _, err := x.Exec("ALTER TABLE `" + table + "` DROP COLUMN `display_name`"); err != nil {
return err
}
}
}
return nil
}
+108
View File
@@ -0,0 +1,108 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// AddLicensingTables creates the license, license_entitlement, license_activation,
// and product_tier tables for the consumer-facing DLID licensing system (#617).
func AddLicensingTables(x *xorm.Engine) error {
type License struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX NOT NULL"`
DLID string `xorm:"VARCHAR(36) UNIQUE NOT NULL"`
Tier string `xorm:"VARCHAR(30) NOT NULL DEFAULT 'base'"`
MaxDomains int `xorm:"NOT NULL DEFAULT 1"`
Status string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'active'"`
ExpiresAt int64 `xorm:"INDEX"`
Notes string `xorm:"TEXT"`
CreatedAt int64 `xorm:"INDEX CREATED"`
UpdatedAt int64 `xorm:"UPDATED"`
}
type LicenseEntitlement struct {
ID int64 `xorm:"pk autoincr"`
LicenseID int64 `xorm:"INDEX NOT NULL"`
ProductCode string `xorm:"VARCHAR(30) NOT NULL"`
RepoOwner string `xorm:"VARCHAR(100) NOT NULL DEFAULT 'MokoConsulting'"`
RepoName string `xorm:"VARCHAR(100) NOT NULL"`
IsCustom bool `xorm:"NOT NULL DEFAULT false"`
CreatedAt int64 `xorm:"CREATED"`
}
type LicenseActivation struct {
ID int64 `xorm:"pk autoincr"`
LicenseID int64 `xorm:"INDEX NOT NULL"`
Domain string `xorm:"VARCHAR(255) NOT NULL"`
IPAddress string `xorm:"VARCHAR(64)"`
JoomlaVer string `xorm:"VARCHAR(20)"`
ActivatedAt int64 `xorm:"CREATED"`
LastSeenAt int64
}
type ProductTier struct {
ID int64 `xorm:"pk autoincr"`
TierKey string `xorm:"VARCHAR(30) UNIQUE NOT NULL"`
TierName string `xorm:"VARCHAR(100) NOT NULL"`
Repos string `xorm:"TEXT"`
MaxDomains int `xorm:"NOT NULL DEFAULT 1"`
SortOrder int `xorm:"NOT NULL DEFAULT 0"`
}
type LicenseAuditLog struct {
ID int64 `xorm:"pk autoincr"`
LicenseID int64 `xorm:"INDEX NOT NULL"`
Action string `xorm:"VARCHAR(50) NOT NULL"`
OldValue string `xorm:"VARCHAR(100)"`
NewValue string `xorm:"VARCHAR(100)"`
CreatedAt int64 `xorm:"INDEX CREATED"`
}
if err := x.Sync(new(License), new(LicenseEntitlement), new(LicenseActivation), new(ProductTier), new(LicenseAuditLog)); err != nil {
return err
}
// Add composite unique indexes
if _, err := x.Exec("CREATE UNIQUE INDEX IF NOT EXISTS UQE_license_entitlement_lic_prod ON license_entitlement (license_id, product_code)"); err != nil {
// MySQL doesn't support IF NOT EXISTS for indexes — try without
x.Exec("CREATE UNIQUE INDEX UQE_license_entitlement_lic_prod ON license_entitlement (license_id, product_code)")
}
if _, err := x.Exec("CREATE UNIQUE INDEX IF NOT EXISTS UQE_license_activation_lic_domain ON license_activation (license_id, domain)"); err != nil {
x.Exec("CREATE UNIQUE INDEX UQE_license_activation_lic_domain ON license_activation (license_id, domain)")
}
// Seed product tiers
tiers := []ProductTier{
{TierKey: "base", TierName: "MokoSuite Base", Repos: `["MokoSuite"]`, MaxDomains: 1, SortOrder: 0},
{TierKey: "crm", TierName: "MokoSuite CRM", Repos: `["MokoSuite","MokoSuiteCRM"]`, MaxDomains: 3, SortOrder: 10},
{TierKey: "erp", TierName: "MokoSuite ERP", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP"]`, MaxDomains: 3, SortOrder: 20},
{TierKey: "child", TierName: "MokoSuite Child", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteChild"]`, MaxDomains: 3, SortOrder: 25},
{TierKey: "create", TierName: "MokoSuite Create", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteCreate"]`, MaxDomains: 3, SortOrder: 26},
{TierKey: "npo", TierName: "MokoSuite NPO", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteNPO"]`, MaxDomains: 3, SortOrder: 27},
{TierKey: "hrm", TierName: "MokoSuite HRM", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteHRM"]`, MaxDomains: 3, SortOrder: 30},
{TierKey: "mrp", TierName: "MokoSuite MRP", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuiteMRP"]`, MaxDomains: 3, SortOrder: 35},
{TierKey: "pos", TierName: "MokoSuite POS", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS"]`, MaxDomains: 5, SortOrder: 40},
{TierKey: "shop", TierName: "MokoSuite Shop", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuiteShop"]`, MaxDomains: 5, SortOrder: 45},
{TierKey: "restaurant", TierName: "MokoSuite Restaurant", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS","MokoSuiteRestaurant"]`, MaxDomains: 5, SortOrder: 50},
{TierKey: "suite", TierName: "MokoSuite Suite", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS","MokoSuiteShop","MokoSuiteHRM","MokoSuiteMRP","MokoSuiteChild","MokoSuiteCreate","MokoSuiteNPO","MokoSuiteRestaurant","MokoSuiteForms"]`, MaxDomains: 10, SortOrder: 90},
{TierKey: "enterprise", TierName: "MokoSuite Enterprise", Repos: `["MokoSuite","MokoSuiteCRM","MokoSuiteERP","MokoSuitePOS","MokoSuiteShop","MokoSuiteHRM","MokoSuiteMRP","MokoSuiteChild","MokoSuiteCreate","MokoSuiteNPO","MokoSuiteRestaurant","MokoSuiteForms","MokoSuiteCommunity","MokoSuiteBackup","MokoSuiteStoreLocator","MokoSuiteOpenGraph","MokoSuiteCross"]`, MaxDomains: 0, SortOrder: 100},
}
for _, t := range tiers {
// Only insert if the tier doesn't already exist
count, err := x.Where("tier_key = ?", t.TierKey).Count(new(ProductTier))
if err != nil {
return err
}
if count == 0 {
if _, err := x.Insert(&t); err != nil {
return err
}
}
}
return nil
}
+1
View File
@@ -31,6 +31,7 @@ type Attachment struct {
Name string
DownloadCount int64 `xorm:"DEFAULT 0"`
Size int64 `xorm:"DEFAULT 0"`
CDNPublic bool `xorm:"NOT NULL DEFAULT false 'cdn_public'"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
CustomDownloadURL string `xorm:"-"`
}
+98 -17
View File
@@ -5,19 +5,20 @@ package repo
import (
"context"
"strings"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(RepoManifest))
db.RegisterModel(new(RepoMetadata))
}
// RepoManifest stores moko-platform manifest settings for a repository.
// RepoMetadata stores mokoplatform metadata settings for a repository.
// These fields correspond to the .mokogitea/manifest.xml schema and are
// exposed via API for use by Actions workflows and the moko-platform CLI.
type RepoManifest struct {
// exposed via API for use by Actions workflows and the mokoplatform CLI.
type RepoMetadata struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"`
@@ -25,31 +26,111 @@ type RepoManifest struct {
Name string `xorm:"TEXT 'name'"` // project name
Org string `xorm:"TEXT 'org'"` // organization name
Description string `xorm:"TEXT 'description'"` // project description
Version string `xorm:"TEXT 'version'"` // current version string
LicenseSPDX string `xorm:"VARCHAR(50) 'license_spdx'"` // SPDX identifier, e.g. "GPL-3.0-or-later"
LicenseName string `xorm:"TEXT 'license_name'"` // human-readable license name
// governance section
Platform string `xorm:"VARCHAR(50) 'platform'"` // go, php, node, python, etc.
StandardsVersion string `xorm:"VARCHAR(20) 'standards_version'"` // moko-platform standards version
StandardsVersion string `xorm:"VARCHAR(20) 'standards_version'"` // mokoplatform standards version
StandardsSource string `xorm:"TEXT 'standards_source'"` // URL to standards repo
// versioning
VersionPrefix string `xorm:"TEXT 'version_prefix'"` // tag prefix stripped for version display, e.g. "v1.26.1-moko."
ElementName string `xorm:"TEXT 'element_name'"` // full element name override, e.g. "pkg_mokowaas" (auto-constructed if empty)
// distribution metadata (used by update server feed generation)
Maintainer string `xorm:"TEXT 'maintainer'"` // maintainer/author name
MaintainerURL string `xorm:"TEXT 'maintainer_url'"` // maintainer website
InfoURL string `xorm:"TEXT 'info_url'"` // extension info/product page URL
TargetVersion string `xorm:"TEXT 'target_version'"` // target platform version regex, e.g. "(5|6)\..*"
PHPMinimum string `xorm:"VARCHAR(20) 'php_minimum'"` // minimum PHP version, e.g. "8.1"
// build section
Language string `xorm:"VARCHAR(50) 'language'"` // Go, PHP, TypeScript, etc.
PackageType string `xorm:"VARCHAR(50) 'package_type'"` // application, library, plugin, module, component, package
ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library, file
EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (RepoManifest) TableName() string {
func (RepoMetadata) TableName() string {
return "repo_manifest"
}
// GetRepoManifest returns the manifest for a repo, or nil if none exists.
func GetRepoManifest(ctx context.Context, repoID int64) (*RepoManifest, error) {
m := new(RepoManifest)
// joomlaTypePrefix maps Joomla extension types to their element name prefixes.
// Derived from Joomla 6 source: libraries/src/Installer/Adapter/*Adapter.php
// Plugins use bare names (no prefix) — the folder column determines the plugin group.
var joomlaTypePrefix = map[string]string{
"component": "com_",
"module": "mod_",
"package": "pkg_",
"template": "tpl_",
"library": "lib_",
"file": "file_",
}
// cleanJoomlaElement replicates Joomla's InputFilter::clean($value, 'cmd')
// which lowercases and strips all characters except [a-z0-9._-].
func cleanJoomlaElement(name string) string {
lower := strings.ToLower(name)
var b strings.Builder
b.Grow(len(lower))
for _, r := range lower {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '.' || r == '_' || r == '-' {
b.WriteRune(r)
}
}
return b.String()
}
// AutoElementName returns the auto-constructed Joomla element name (e.g. pkg_mokosuitebackup).
// The name is lowercased and cleaned to match Joomla's InputFilter::clean('cmd') behavior.
func (m *RepoMetadata) AutoElementName() string {
if m.Name == "" || m.ExtensionType == "" {
return ""
}
cleaned := cleanJoomlaElement(m.Name)
if prefix, ok := joomlaTypePrefix[m.ExtensionType]; ok {
return prefix + cleaned
}
return cleaned
}
// FullElementName returns the effective element name: override if set, otherwise auto-constructed.
func (m *RepoMetadata) FullElementName() string {
if m.ElementName != "" {
return m.ElementName
}
return m.AutoElementName()
}
// ElementNameMismatch returns true if an override is set that differs from the auto-constructed name.
func (m *RepoMetadata) ElementNameMismatch() bool {
if m.ElementName == "" {
return false
}
auto := m.AutoElementName()
return auto != "" && m.ElementName != auto
}
// DerivedDisplayName computes the display name from ExtensionType and Name.
// Format: "Package - MokoSuiteBackup" (titlecased type + repo name).
// Falls back to just the Name if ExtensionType is empty.
func (m *RepoMetadata) DerivedDisplayName() string {
if m.Name == "" {
return ""
}
if m.ExtensionType == "" {
return m.Name
}
title := strings.ToUpper(m.ExtensionType[:1]) + m.ExtensionType[1:]
return title + " - " + m.Name
}
// GetRepoMetadata returns the metadata for a repo, or nil if none exists.
func GetRepoMetadata(ctx context.Context, repoID int64) (*RepoMetadata, error) {
m := new(RepoMetadata)
has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Get(m)
if err != nil {
return nil, err
@@ -60,9 +141,9 @@ func GetRepoManifest(ctx context.Context, repoID int64) (*RepoManifest, error) {
return m, nil
}
// CreateOrUpdateRepoManifest upserts a repo manifest.
func CreateOrUpdateRepoManifest(ctx context.Context, m *RepoManifest) error {
existing := new(RepoManifest)
// CreateOrUpdateRepoMetadata upserts a repo metadata.
func CreateOrUpdateRepoMetadata(ctx context.Context, m *RepoMetadata) error {
existing := new(RepoMetadata)
has, err := db.GetEngine(ctx).Where("repo_id = ?", m.RepoID).Get(existing)
if err != nil {
return err
@@ -76,8 +157,8 @@ func CreateOrUpdateRepoManifest(ctx context.Context, m *RepoManifest) error {
return err
}
// DeleteRepoManifest deletes the manifest for a repo.
func DeleteRepoManifest(ctx context.Context, repoID int64) error {
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(RepoManifest))
// DeleteRepoMetadata deletes the metadata for a repo.
func DeleteRepoMetadata(ctx context.Context, repoID int64) error {
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(RepoMetadata))
return err
}
+2
View File
@@ -212,6 +212,8 @@ func TestIsUsableRepoName(t *testing.T) {
assert.NoError(t, IsUsableRepoName("a"))
assert.NoError(t, IsUsableRepoName("-1_."))
assert.NoError(t, IsUsableRepoName(".profile"))
assert.NoError(t, IsUsableRepoName(".mokogitea"))
assert.NoError(t, IsUsableRepoName(".github"))
assert.Error(t, IsUsableRepoName("-"))
assert.Error(t, IsUsableRepoName("🌞"))
@@ -1,7 +1,7 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licenses
package updateserver
import (
"context"
@@ -256,74 +256,8 @@ func ValidateLicenseKey(ctx context.Context, rawKey, domain string) (*LicenseKey
// Domain restriction check — skip for internal/master keys.
if domain != "" && !key.IsInternal {
now := timeutil.TimeStampNow()
if key.DomainRestriction != "" {
// Domain restriction is set — enforce it.
allowed := false
for _, d := range strings.Split(key.DomainRestriction, ",") {
if strings.EqualFold(strings.TrimSpace(d), domain) {
allowed = true
break
}
}
if !allowed {
// Check if still within the domain lock grace period.
lockHours := pkg.DomainLockHours
if lockHours > 0 && key.FirstUsedUnix > 0 {
lockDeadline := key.FirstUsedUnix + timeutil.TimeStamp(int64(lockHours)*3600)
if now < lockDeadline {
// Grace period active — allow and auto-add this domain.
_ = updateDomainRestriction(ctx, key.ID, domain)
key.DomainRestriction = key.DomainRestriction + "," + domain
allowed = true
}
}
if !allowed {
return nil, nil, fmt.Errorf("domain not allowed for this license key")
}
}
} else {
// No domain restriction set — auto-associate domain.
maxSites := key.MaxSites
if maxSites == 0 {
maxSites = pkg.MaxSites
}
domainKnown, _ := IsDomainKnownForKey(ctx, key.ID, domain)
if !domainKnown {
if maxSites > 0 {
uniqueDomains, err := CountUniqueDomainsByKey(ctx, key.ID)
if err != nil {
return nil, nil, fmt.Errorf("failed to count domains: %w", err)
}
if uniqueDomains >= int64(maxSites) {
return nil, nil, fmt.Errorf("site limit reached (%d/%d)", uniqueDomains, maxSites)
}
}
_ = updateDomainRestriction(ctx, key.ID, domain)
if key.DomainRestriction == "" {
key.DomainRestriction = domain
} else {
key.DomainRestriction = key.DomainRestriction + "," + domain
}
}
}
// Site limit check: use key's MaxSites, fall back to package default.
maxSites := key.MaxSites
if maxSites == 0 {
maxSites = pkg.MaxSites
}
if maxSites > 0 {
uniqueDomains, err := CountUniqueDomainsByKey(ctx, key.ID)
if err != nil {
return nil, nil, fmt.Errorf("failed to count domains: %w", err)
}
// Allow if this domain is already recorded, or if under the limit.
domainKnown, _ := IsDomainKnownForKey(ctx, key.ID, domain)
if !domainKnown && uniqueDomains >= int64(maxSites) {
return nil, nil, fmt.Errorf("site limit reached (%d/%d)", uniqueDomains, maxSites)
}
if err := validateAndAssociateDomain(ctx, key, pkg, domain); err != nil {
return nil, nil, err
}
}
@@ -374,6 +308,80 @@ func ValidateLicenseKeyForRepo(ctx context.Context, rawKey, domain string, repoI
return key, pkg, nil
}
// validateAndAssociateDomain checks domain restrictions and auto-associates new
// domains. The auto-associate path (no existing restriction) runs inside a
// transaction to prevent TOCTOU races on the MaxSites limit. The grace-period
// path (existing restriction, lock window open) also propagates DB errors.
func validateAndAssociateDomain(ctx context.Context, key *LicenseKey, pkg *LicensePackage, domain string) error {
if key.DomainRestriction != "" {
// Domain restriction is set — enforce it.
allowed := false
for _, d := range strings.Split(key.DomainRestriction, ",") {
if strings.EqualFold(strings.TrimSpace(d), domain) {
allowed = true
break
}
}
if !allowed {
// Check if still within the domain lock grace period.
now := timeutil.TimeStampNow()
lockHours := pkg.DomainLockHours
if lockHours > 0 && key.FirstUsedUnix > 0 {
lockDeadline := key.FirstUsedUnix + timeutil.TimeStamp(int64(lockHours)*3600)
if now < lockDeadline {
// Grace period active — allow and auto-add this domain.
if err := updateDomainRestriction(ctx, key.ID, domain); err != nil {
return fmt.Errorf("failed to auto-add domain during grace period: %w", err)
}
key.DomainRestriction = key.DomainRestriction + "," + domain
allowed = true
}
}
if !allowed {
return fmt.Errorf("domain not allowed for this license key")
}
}
return nil
}
// No domain restriction set — auto-associate domain within a transaction
// so the count check and insert are atomic (prevents exceeding MaxSites).
maxSites := key.MaxSites
if maxSites == 0 {
maxSites = pkg.MaxSites
}
return db.WithTx(ctx, func(txCtx context.Context) error {
domainKnown, err := IsDomainKnownForKey(txCtx, key.ID, domain)
if err != nil {
return fmt.Errorf("failed to check domain association: %w", err)
}
if domainKnown {
return nil // already associated, nothing to do
}
if maxSites > 0 {
uniqueDomains, err := CountUniqueDomainsByKey(txCtx, key.ID)
if err != nil {
return fmt.Errorf("failed to count domains: %w", err)
}
if uniqueDomains >= int64(maxSites) {
return fmt.Errorf("site limit reached (%d/%d)", uniqueDomains, maxSites)
}
}
if err := updateDomainRestriction(txCtx, key.ID, domain); err != nil {
return fmt.Errorf("failed to update domain restriction: %w", err)
}
if key.DomainRestriction == "" {
key.DomainRestriction = domain
} else {
key.DomainRestriction = key.DomainRestriction + "," + domain
}
return nil
})
}
// updateDomainRestriction appends a domain to a key's DomainRestriction field in the DB.
func updateDomainRestriction(ctx context.Context, keyID int64, domain string) error {
key, err := GetLicenseKeyByID(ctx, keyID)
@@ -1,7 +1,7 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licenses
package updateserver
import (
"context"
@@ -1,7 +1,7 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licenses
package updateserver
import (
"context"
@@ -1,7 +1,7 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licenses
package updateserver
import (
"context"
@@ -1,7 +1,7 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licenses
package updateserver
import (
"context"
@@ -1,7 +1,7 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licenses
package updateserver
import (
"context"
@@ -33,7 +33,6 @@ type UpdateStreamConfig struct {
KeyPrefix string `xorm:"VARCHAR(20) 'key_prefix'"` // org-specific license key prefix (e.g. "ACME")
// Extension metadata — used in update feed generation.
ExtensionName string `xorm:"TEXT 'extension_name'"` // element identifier (e.g. pkg_mokowaas, com_mokowaas)
DisplayName string `xorm:"TEXT 'display_name'"` // human-readable name (e.g. "Package - MokoWaaS")
Description string `xorm:"TEXT"` // short description for update feeds
ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library
Maintainer string `xorm:"TEXT"` // maintainer/author name
@@ -82,6 +81,12 @@ func (c *UpdateStreamConfig) GetCustomStreams() []StreamDef {
return streams
}
// DeleteRepoConfig removes the repo-level update stream config override.
func DeleteRepoConfig(ctx context.Context, repoID int64) error {
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(UpdateStreamConfig))
return err
}
// GetActiveStreams returns the effective streams for this config.
func (c *UpdateStreamConfig) GetActiveStreams() []StreamDef {
if c.StreamMode == "custom" {
+2
View File
@@ -153,6 +153,8 @@ type User struct {
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
ParentOrgID int64 `xorm:"INDEX DEFAULT 0"` // 0 = no parent (top-level org)
WikiMode string `xorm:"VARCHAR(20) NOT NULL DEFAULT '' 'wiki_mode'"` // "" = internal (convention repos), "external" = link to WikiURL
WikiURL string `xorm:"TEXT 'wiki_url'"` // external wiki URL (used when WikiMode == "external")
// Preferences
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2026 The MokoGitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
// AI settings
var (
AI = struct {
Enabled bool
DefaultModel string `ini:"DEFAULT_MODEL"`
DefaultKey string `ini:"DEFAULT_API_KEY"`
ClaudeBinPath string `ini:"CLAUDE_BIN_PATH"`
}{
Enabled: false,
DefaultModel: "claude-sonnet-4-6",
}
)
func loadAIFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("ai")
AI.Enabled = sec.Key("ENABLED").MustBool(AI.Enabled)
AI.DefaultModel = sec.Key("DEFAULT_MODEL").MustString(AI.DefaultModel)
AI.DefaultKey = sec.Key("DEFAULT_API_KEY").String()
AI.ClaudeBinPath = sec.Key("CLAUDE_BIN_PATH").MustString("claude")
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
import "time"
// CDN holds configuration for the built-in CDN asset delivery system.
var CDN = struct {
Enabled bool
Domain string // e.g. "cdn.mokoconsulting.tech"
CacheTTL time.Duration // Cache-Control max-age for CDN responses
AllowedOrigins []string // CORS origins allowed to fetch CDN assets
AllowedIPs []string // IP/CIDR allowlist (empty = allow all)
AllowedDomains []string // Referrer domain allowlist (empty = allow all)
MaxFileSize int64 // max file size to serve (bytes)
}{
Enabled: false,
Domain: "",
CacheTTL: 24 * time.Hour,
MaxFileSize: 100 * 1024 * 1024, // 100MB
}
func loadCDNFrom(cfg ConfigProvider) {
sec := cfg.Section("cdn")
CDN.Enabled = sec.Key("ENABLED").MustBool(false)
CDN.Domain = sec.Key("DOMAIN").String()
CDN.CacheTTL = sec.Key("CACHE_TTL").MustDuration(CDN.CacheTTL)
CDN.MaxFileSize = sec.Key("MAX_FILE_SIZE").MustInt64(CDN.MaxFileSize)
CDN.AllowedOrigins = sec.Key("ALLOWED_ORIGINS").Strings(",")
CDN.AllowedIPs = sec.Key("ALLOWED_IPS").Strings(",")
CDN.AllowedDomains = sec.Key("ALLOWED_DOMAINS").Strings(",")
}
+1
View File
@@ -178,6 +178,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
loadOtherFrom(cfg)
loadUpdateCheckerFrom(cfg)
loadNtfyFrom(cfg)
loadCDNFrom(cfg)
loadLoginNotificationFrom(cfg)
return nil
}
+49
View File
@@ -84,6 +84,14 @@ type Issue struct {
PinOrder int `json:"pin_order"`
// The version of the issue content for optimistic locking
ContentVersion int `json:"content_version"`
// Issue metadata (org-level definitions)
StatusID int64 `json:"status_id"`
StatusName string `json:"status_name"`
PriorityID int64 `json:"priority_id"`
PriorityName string `json:"priority_name"`
TypeID int64 `json:"type_id"`
TypeName string `json:"type_name"`
}
// CreateIssueOption options to create one issue
@@ -106,6 +114,10 @@ type CreateIssueOption struct {
Closed bool `json:"closed"`
// custom field values keyed by field name
CustomFields map[string]string `json:"custom_fields,omitempty"`
// org-level issue metadata IDs (auto-assigned from org defaults when 0)
StatusID int64 `json:"status_id"`
PriorityID int64 `json:"priority_id"`
TypeID int64 `json:"type_id"`
}
// EditIssueOption options for editing an issue
@@ -125,6 +137,10 @@ type EditIssueOption struct {
RemoveDeadline *bool `json:"unset_due_date"`
// The current version of the issue content to detect conflicts during editing
ContentVersion *int `json:"content_version"`
// org-level issue metadata IDs
StatusID *int64 `json:"status_id,omitempty"`
PriorityID *int64 `json:"priority_id,omitempty"`
TypeID *int64 `json:"type_id,omitempty"`
}
// EditDeadlineOption options for creating a deadline
@@ -141,6 +157,39 @@ type IssueDeadline struct {
Deadline *time.Time `json:"due_date"`
}
// IssueStatusDef represents an org-level issue status definition
// swagger:model
type IssueStatusDef struct {
ID int64 `json:"id"`
Name string `json:"name"`
Color string `json:"color"`
Description string `json:"description"`
ClosesIssue bool `json:"closes_issue"`
SortOrder int `json:"sort_order"`
}
// IssuePriorityDef represents an org-level issue priority definition
// swagger:model
type IssuePriorityDef struct {
ID int64 `json:"id"`
Name string `json:"name"`
Color string `json:"color"`
Description string `json:"description"`
SortOrder int `json:"sort_order"`
IsDefault bool `json:"is_default"`
}
// IssueTypeDef represents an org-level issue type definition
// swagger:model
type IssueTypeDef struct {
ID int64 `json:"id"`
Name string `json:"name"`
Color string `json:"color"`
Description string `json:"description"`
SortOrder int `json:"sort_order"`
IsDefault bool `json:"is_default"`
}
// IssueFormFieldType defines issue form field type, can be "markdown", "textarea", "input", "dropdown" or "checkboxes"
//
// swagger:enum IssueFormFieldType
+19 -1
View File
@@ -2734,12 +2734,19 @@
"repo.settings.support_url_help": "Shown when downloads are gated. Can point to your wiki, product page, or external support site.",
"repo.settings.custom_fields": "Custom Fields",
"repo.settings.manifest": "Manifest",
"repo.settings.manifest_desc": "Project identity, governance, and build settings from the moko-platform manifest. These are accessible via API for Actions workflows and the moko-platform CLI.",
"repo.settings.manifest_desc": "Project identity, governance, and build settings from the MokoPlatform manifest. These are accessible via API for Actions workflows and the MokoPlatform CLI.",
"repo.settings.manifest_identity": "Identity",
"repo.settings.manifest_name": "Project Name",
"repo.settings.manifest_element_name": "Element Name",
"repo.settings.manifest_element_name_help": "Base name used to construct the Joomla element identifier (e.g. 'mokowaas'). Combined with the extension type to produce the full element name.",
"repo.settings.manifest_element_full": "Full Element Name",
"repo.settings.manifest_element_full_help": "Auto-constructed from type + name. Leave blank to use the default, or override for non-standard naming.",
"repo.settings.manifest_element_mismatch": "Warning: this overrides the auto-constructed name '%s'. Make sure this matches your Joomla extension's element identifier.",
"repo.settings.manifest_package_type_help": "Maps to the Joomla extension type and determines the element prefix (com_, mod_, plg_, pkg_, tpl_, lib_, file_).",
"repo.settings.manifest_org": "Organization",
"repo.settings.manifest_description": "Description",
"repo.settings.manifest_version": "Version",
"repo.settings.manifest_version_prefix": "Version Prefix",
"repo.settings.manifest_license_spdx": "License (SPDX)",
"repo.settings.manifest_license_name": "License Name",
"repo.settings.manifest_governance": "Governance",
@@ -2749,6 +2756,13 @@
"repo.settings.manifest_build": "Build",
"repo.settings.manifest_language": "Language",
"repo.settings.manifest_package_type": "Package Type",
"repo.settings.manifest_distribution": "Distribution",
"repo.settings.manifest_display_name": "Display Name",
"repo.settings.manifest_maintainer": "Maintainer",
"repo.settings.manifest_maintainer_url": "Maintainer URL",
"repo.settings.manifest_info_url": "Info / Product URL",
"repo.settings.manifest_target_version": "Target Platform Version",
"repo.settings.manifest_php_minimum": "Minimum PHP Version",
"repo.settings.manifest_entry_point": "Entry Point",
"repo.settings.manifest_save": "Save Manifest",
"repo.settings.manifest_saved": "Manifest settings saved.",
@@ -2831,6 +2845,8 @@
"repo.release.message": "Describe this release",
"repo.release.prerelease_desc": "Mark as Pre-Release",
"repo.release.prerelease_helper": "Mark this release unsuitable for production use.",
"repo.release.cdn_public": "CDN",
"repo.release.cdn_public_tooltip": "Make this asset available via the CDN. Disabled when the release is assigned to an update stream.",
"repo.release.cancel": "Cancel",
"repo.release.publish": "Publish Release",
"repo.release.save_draft": "Save Draft",
@@ -2960,6 +2976,8 @@
"org.settings.custom_field_options": "Options (JSON)",
"org.settings.custom_field_options_help": "For dropdown fields, enter options as a JSON array.",
"org.settings.custom_field_description": "Description",
"org.settings.custom_field_required": "Required",
"org.settings.custom_field_required_help": "When checked, this field must be filled in when creating or editing issues.",
"org.settings.custom_field_created": "Custom field created.",
"org.settings.custom_field_updated": "Custom field updated.",
"org.settings.custom_field_deleted": "Custom field deleted.",
+51 -9
View File
@@ -82,6 +82,7 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/activitypub"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/admin"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/licensing"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/misc"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/notify"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/api/v1/org"
@@ -1306,11 +1307,11 @@ func Routes() *web.Router {
m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
}, mustEnableIssues, reqToken())
m.Group("/wiki", func() {
m.Combo("/page/{pageName}").
m.Combo("/page/*").
Get(repo.GetWikiPage).
Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
m.Get("/revisions/{pageName}", repo.ListPageRevisions)
m.Get("/revisions/*", repo.ListPageRevisions)
m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
m.Get("/pages", repo.ListWikiPages)
}, mustEnableWiki)
@@ -1479,9 +1480,12 @@ func Routes() *web.Router {
Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin())
}, reqAnyRepoReader())
m.Combo("/manifest", reqRepoReader(unit.TypeCode)).
Get(repo.GetRepoManifest).
Put(reqToken(), reqAdmin(), repo.UpdateRepoManifest)
m.Combo("/metadata", reqRepoReader(unit.TypeCode)).
Get(repo.GetRepoMetadata).
Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
m.Combo("/manifest", reqRepoReader(unit.TypeCode)). // backward compat
Get(repo.GetRepoMetadata).
Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
// MokoGitea badge engine
m.Get("/badge/{type}.svg", repo.GetRepoBadge)
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
@@ -1658,10 +1662,10 @@ func Routes() *web.Router {
// })
// })
// })
// Repo metadata (repo-scoped custom fields)
m.Group("/metadata", func() {
m.Get("", repo.GetRepoMetadata)
m.Put("", reqToken(), reqRepoWriter(unit.TypeCode), repo.SetRepoMetadata)
// Repo custom fields (repo-scoped key-value metadata)
m.Group("/custom-fields", func() {
m.Get("", repo.GetRepoCustomFields)
m.Put("", reqToken(), reqRepoWriter(unit.TypeCode), repo.SetRepoCustomFields)
})
// Issue custom fields
m.Group("/issues/{index}/custom-fields", func() {
@@ -1773,6 +1777,9 @@ func Routes() *web.Router {
m.Post("", reqToken(), reqOrgOwnership(), org.CreateOrgCustomField)
m.Delete("/{id}", reqToken(), reqOrgOwnership(), org.DeleteOrgCustomField)
})
m.Get("/issue-statuses", org.ListIssueStatuses)
m.Get("/issue-priorities", org.ListIssuePriorities)
m.Get("/issue-types", org.ListIssueTypes)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
m.Group("/teams/{teamid}", func() {
m.Combo("").Get(reqToken(), org.GetTeam).
@@ -1852,6 +1859,41 @@ func Routes() *web.Router {
m.Group("/topics", func() {
m.Get("/search", repo.TopicSearch)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
// Licensing endpoints
m.Group("/licensing", func() {
// Public (no auth)
m.Get("/updates/{product}", licensing.ServeUpdates)
m.Get("/validate", licensing.Validate)
m.Get("/download/{product}/{version}", licensing.ServeDownload)
// User self-service (authenticated)
m.Group("/my", func() {
m.Get("/licenses", licensing.MyLicenses)
m.Get("/licenses/{id}/domains", licensing.MyLicenseDomains)
m.Delete("/licenses/{id}/domains/{domain}", licensing.MyDeactivateDomain)
}, reqToken())
// Admin license management
m.Group("/licenses", func() {
m.Get("", licensing.ListLicenses)
m.Post("", licensing.CreateLicense)
m.Get("/{id}", licensing.GetLicense)
m.Patch("/{id}", licensing.UpdateLicense)
m.Delete("/{id}", licensing.DeleteLicense)
}, reqToken(), reqSiteAdmin())
// Admin tier management
m.Group("/tiers", func() {
m.Get("", licensing.ListTiers)
m.Post("", licensing.CreateTier)
m.Patch("/{id}", licensing.UpdateTier)
m.Delete("/{id}", licensing.DeleteTier)
}, reqToken(), reqSiteAdmin())
// Authenticated license detail
m.Get("/{dlid}/status", reqToken(), licensing.Status)
})
}, sudo())
return m
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"fmt"
"io"
"net/http"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
licensing_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/licensing"
)
// ServeDownload handles GET /api/v1/licensing/download/{product}/{version}.zip?token=XXX&expires=YYY&dlid=ZZZ
func ServeDownload(ctx *context.APIContext) {
product := ctx.PathParam("product")
versionFile := ctx.PathParam("version")
token := ctx.FormString("token")
expiresStr := ctx.FormString("expires")
dlid := ctx.FormString("dlid")
version, ok := licensing_service.ParseDownloadParams(versionFile)
if !ok {
ctx.APIError(http.StatusBadRequest, "invalid version format")
return
}
expires, ok := licensing_service.ParseExpires(expiresStr)
if !ok || token == "" || dlid == "" {
ctx.APIError(http.StatusForbidden, "missing or invalid download parameters")
return
}
// Verify signed token
if !licensing_service.VerifyDownloadToken(token, product, version, dlid, expires) {
ctx.APIError(http.StatusForbidden, "invalid or expired download token")
return
}
// Verify DLID is still valid
license, err := licensing_model.GetLicenseByDLID(ctx, dlid)
if err != nil || license == nil || !license.IsActive() {
ctx.APIError(http.StatusForbidden, "license invalid or expired")
return
}
// Verify entitlement
has, _ := licensing_model.HasEntitlement(ctx, license.ID, product)
if !has {
ctx.APIError(http.StatusForbidden, "no entitlement for product")
return
}
// Resolve repo from entitlement
ents, err := licensing_model.GetEntitlementsByLicense(ctx, license.ID)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to get entitlements")
return
}
var repoOwner, repoName string
for _, ent := range ents {
if ent.ProductCode == product {
repoOwner = ent.RepoOwner
repoName = ent.RepoName
break
}
}
if repoName == "" {
ctx.APIError(http.StatusNotFound, "product repo not found")
return
}
// Find repo
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, repoOwner, repoName)
if err != nil || repo == nil {
ctx.APIError(http.StatusNotFound, "repository not found")
return
}
// Find the release with matching version
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID,
ListOptions: db.ListOptionsAll,
IncludeDrafts: false,
IncludeTags: false,
})
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to list releases")
return
}
var targetRelease *repo_model.Release
for _, rel := range releases {
relVersion := extractVersion(rel.TagName)
if relVersion == version {
targetRelease = rel
break
}
if rel.Title != "" && extractVersion(rel.Title) == version {
targetRelease = rel
break
}
}
if targetRelease == nil {
ctx.APIError(http.StatusNotFound, fmt.Sprintf("release version %s not found", version))
return
}
// Find ZIP attachment
attachments, err := repo_model.GetAttachmentsByReleaseID(ctx, targetRelease.ID)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to get attachments")
return
}
var zipAttachment *repo_model.Attachment
for _, att := range attachments {
if att.Name != "" && len(att.Name) > 4 && att.Name[len(att.Name)-4:] == ".zip" {
zipAttachment = att
break
}
}
if zipAttachment == nil {
ctx.APIError(http.StatusNotFound, "no zip attachment found for release")
return
}
// Log the download
licensing_model.LogLicenseAudit(ctx, license.ID, "download",
product, fmt.Sprintf("%s/%s", version, zipAttachment.Name))
// Serve the file
fr, err := storage.Attachments.Open(zipAttachment.RelativePath())
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to open attachment")
return
}
defer fr.Close()
ctx.Resp.Header().Set("Content-Type", "application/zip")
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", zipAttachment.Name))
ctx.Resp.WriteHeader(http.StatusOK)
if _, err := io.Copy(ctx.Resp, fr); err != nil {
log.Error("ServeDownload: io.Copy: %v", err)
}
}
+477
View File
@@ -0,0 +1,477 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"net/http"
"strconv"
"time"
licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// ── Admin: License CRUD ─────────────────────────────────────────────────
type createLicenseRequest struct {
UserID int64 `json:"user_id" binding:"Required"`
Tier string `json:"tier" binding:"Required"`
MaxDomains int `json:"max_domains"`
ExpiresMonths int `json:"expires_months"`
Notes string `json:"notes"`
}
// CreateLicense handles POST /api/v1/licensing/licenses
func CreateLicense(ctx *context.APIContext) {
var req createLicenseRequest
if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body")
return
}
// Resolve max_domains from tier if not specified
maxDomains := req.MaxDomains
if maxDomains == 0 {
tier, _ := licensing_model.GetProductTierByKey(ctx, req.Tier)
if tier != nil {
maxDomains = tier.MaxDomains
}
if maxDomains == 0 {
maxDomains = 1
}
}
var expiresAt timeutil.TimeStamp
if req.ExpiresMonths > 0 {
expiresAt = timeutil.TimeStamp(time.Now().AddDate(0, req.ExpiresMonths, 0).Unix())
}
license, err := licensing_model.CreateLicense(ctx, req.UserID, req.Tier, maxDomains, expiresAt)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to create license")
return
}
if req.Notes != "" {
license.Notes = req.Notes
// TODO: update notes field
}
// Build entitlements from tier
if err := licensing_model.RebuildEntitlements(ctx, license.ID, req.Tier); err != nil {
log.Error("CreateLicense: RebuildEntitlements: %v", err)
}
ctx.JSON(http.StatusCreated, licenseToJSON(ctx, license))
}
// ListLicenses handles GET /api/v1/licensing/licenses
func ListLicenses(ctx *context.APIContext) {
page := ctx.FormInt("page")
if page <= 0 {
page = 1
}
limit := ctx.FormInt("limit")
if limit <= 0 || limit > 50 {
limit = 20
}
// For now, get all licenses (pagination via offset)
// TODO: add proper pagination to the model layer
var licenses []*licensing_model.License
err := ctx.Orm().Limit(limit, (page-1)*limit).Find(&licenses)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to list licenses")
return
}
results := make([]map[string]any, 0, len(licenses))
for _, l := range licenses {
results = append(results, licenseToJSON(ctx, l))
}
ctx.JSON(http.StatusOK, results)
}
// GetLicense handles GET /api/v1/licensing/licenses/{id}
func GetLicense(ctx *context.APIContext) {
id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
if err != nil {
ctx.APIError(http.StatusBadRequest, "invalid license ID")
return
}
license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil {
ctx.NotFound()
return
}
result := licenseToJSON(ctx, license)
// Include entitlements
ents, _ := licensing_model.GetEntitlementsByLicense(ctx, license.ID)
entList := make([]map[string]any, 0, len(ents))
for _, e := range ents {
entList = append(entList, map[string]any{
"product_code": e.ProductCode,
"repo_owner": e.RepoOwner,
"repo_name": e.RepoName,
"is_custom": e.IsCustom,
})
}
result["entitlements"] = entList
// Include activations
acts, _ := licensing_model.GetActivationsByLicense(ctx, license.ID)
actList := make([]map[string]any, 0, len(acts))
for _, a := range acts {
actList = append(actList, map[string]any{
"domain": a.Domain,
"ip_address": a.IPAddress,
"joomla_ver": a.JoomlaVer,
"activated_at": formatTime(a.ActivatedAt),
"last_seen_at": formatTime(a.LastSeenAt),
})
}
result["activations"] = actList
ctx.JSON(http.StatusOK, result)
}
type updateLicenseRequest struct {
Tier *string `json:"tier"`
Status *string `json:"status"`
MaxDomains *int `json:"max_domains"`
ExpiresAt *string `json:"expires_at"`
Notes *string `json:"notes"`
}
// UpdateLicense handles PATCH /api/v1/licensing/licenses/{id}
func UpdateLicense(ctx *context.APIContext) {
id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
if err != nil {
ctx.APIError(http.StatusBadRequest, "invalid license ID")
return
}
license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil {
ctx.NotFound()
return
}
var req updateLicenseRequest
if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body")
return
}
if req.Tier != nil && *req.Tier != license.Tier {
if err := licensing_model.UpdateLicenseTier(ctx, id, *req.Tier); err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to update tier")
return
}
license.Tier = *req.Tier
}
if req.Status != nil && *req.Status != license.Status {
if err := licensing_model.SetLicenseStatus(ctx, id, *req.Status); err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to update status")
return
}
license.Status = *req.Status
}
// Update simple fields directly
cols := make([]string, 0)
if req.MaxDomains != nil {
license.MaxDomains = *req.MaxDomains
cols = append(cols, "max_domains")
}
if req.Notes != nil {
license.Notes = *req.Notes
cols = append(cols, "notes")
}
if req.ExpiresAt != nil {
t, err := time.Parse(time.RFC3339, *req.ExpiresAt)
if err == nil {
license.ExpiresAt = timeutil.TimeStamp(t.Unix())
cols = append(cols, "expires_at")
}
}
if len(cols) > 0 {
cols = append(cols, "updated_at")
ctx.Orm().ID(id).Cols(cols...).Update(license)
}
ctx.JSON(http.StatusOK, licenseToJSON(ctx, license))
}
// DeleteLicense handles DELETE /api/v1/licensing/licenses/{id}
func DeleteLicense(ctx *context.APIContext) {
id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
if err != nil {
ctx.APIError(http.StatusBadRequest, "invalid license ID")
return
}
if err := licensing_model.RevokeLicense(ctx, id); err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to revoke license")
return
}
ctx.Status(http.StatusNoContent)
}
// ── User: Self-service ──────────────────────────────────────────────────
// MyLicenses handles GET /api/v1/licensing/my/licenses
func MyLicenses(ctx *context.APIContext) {
licenses, err := licensing_model.GetLicensesByUser(ctx, ctx.Doer.ID)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to list licenses")
return
}
results := make([]map[string]any, 0, len(licenses))
for _, l := range licenses {
results = append(results, licenseToJSON(ctx, l))
}
ctx.JSON(http.StatusOK, results)
}
// MyLicenseDomains handles GET /api/v1/licensing/my/licenses/{id}/domains
func MyLicenseDomains(ctx *context.APIContext) {
id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
if err != nil {
ctx.APIError(http.StatusBadRequest, "invalid license ID")
return
}
license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil || license.UserID != ctx.Doer.ID {
ctx.NotFound()
return
}
acts, err := licensing_model.GetActivationsByLicense(ctx, id)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to list domains")
return
}
results := make([]map[string]any, 0, len(acts))
for _, a := range acts {
results = append(results, map[string]any{
"domain": a.Domain,
"activated_at": formatTime(a.ActivatedAt),
"last_seen_at": formatTime(a.LastSeenAt),
})
}
ctx.JSON(http.StatusOK, results)
}
// MyDeactivateDomain handles DELETE /api/v1/licensing/my/licenses/{id}/domains/{domain}
func MyDeactivateDomain(ctx *context.APIContext) {
id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
if err != nil {
ctx.APIError(http.StatusBadRequest, "invalid license ID")
return
}
license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil || license.UserID != ctx.Doer.ID {
ctx.NotFound()
return
}
domain := ctx.PathParam("domain")
if err := licensing_model.DeactivateDomain(ctx, id, domain); err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to deactivate domain")
return
}
ctx.Status(http.StatusNoContent)
}
// ── Admin: Product Tier CRUD ────────────────────────────────────────────
// ListTiers handles GET /api/v1/licensing/tiers
func ListTiers(ctx *context.APIContext) {
tiers, err := licensing_model.GetAllProductTiers(ctx)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to list tiers")
return
}
results := make([]map[string]any, 0, len(tiers))
for _, t := range tiers {
results = append(results, tierToJSON(t))
}
ctx.JSON(http.StatusOK, results)
}
type createTierRequest struct {
TierKey string `json:"tier_key" binding:"Required"`
TierName string `json:"tier_name" binding:"Required"`
Repos []string `json:"repos"`
MaxDomains int `json:"max_domains"`
SortOrder int `json:"sort_order"`
}
// CreateTier handles POST /api/v1/licensing/tiers
func CreateTier(ctx *context.APIContext) {
var req createTierRequest
if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body")
return
}
reposJSON, _ := json.Marshal(req.Repos)
tier := &licensing_model.ProductTier{
TierKey: req.TierKey,
TierName: req.TierName,
Repos: string(reposJSON),
MaxDomains: req.MaxDomains,
SortOrder: req.SortOrder,
}
_, err := ctx.Orm().Insert(tier)
if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to create tier")
return
}
ctx.JSON(http.StatusCreated, tierToJSON(tier))
}
type updateTierRequest struct {
TierName *string `json:"tier_name"`
Repos []string `json:"repos"`
MaxDomains *int `json:"max_domains"`
SortOrder *int `json:"sort_order"`
}
// UpdateTier handles PATCH /api/v1/licensing/tiers/{id}
func UpdateTier(ctx *context.APIContext) {
id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
if err != nil {
ctx.APIError(http.StatusBadRequest, "invalid tier ID")
return
}
tier := new(licensing_model.ProductTier)
has, err := ctx.Orm().ID(id).Get(tier)
if err != nil || !has {
ctx.NotFound()
return
}
var req updateTierRequest
if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body")
return
}
cols := make([]string, 0)
if req.TierName != nil {
tier.TierName = *req.TierName
cols = append(cols, "tier_name")
}
if req.Repos != nil {
reposJSON, _ := json.Marshal(req.Repos)
tier.Repos = string(reposJSON)
cols = append(cols, "repos")
}
if req.MaxDomains != nil {
tier.MaxDomains = *req.MaxDomains
cols = append(cols, "max_domains")
}
if req.SortOrder != nil {
tier.SortOrder = *req.SortOrder
cols = append(cols, "sort_order")
}
if len(cols) > 0 {
ctx.Orm().ID(id).Cols(cols...).Update(tier)
}
ctx.JSON(http.StatusOK, tierToJSON(tier))
}
// DeleteTier handles DELETE /api/v1/licensing/tiers/{id}
func DeleteTier(ctx *context.APIContext) {
id, err := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
if err != nil {
ctx.APIError(http.StatusBadRequest, "invalid tier ID")
return
}
// Check if any licenses use this tier
tier := new(licensing_model.ProductTier)
has, _ := ctx.Orm().ID(id).Get(tier)
if !has {
ctx.NotFound()
return
}
count, _ := ctx.Orm().Where("tier = ?", tier.TierKey).Count(new(licensing_model.License))
if count > 0 {
ctx.APIError(http.StatusConflict, "cannot delete tier with active licenses")
return
}
ctx.Orm().ID(id).Delete(new(licensing_model.ProductTier))
ctx.Status(http.StatusNoContent)
}
// ── Helpers ─────────────────────────────────────────────────────────────
func licenseToJSON(ctx *context.APIContext, l *licensing_model.License) map[string]any {
tierName := l.Tier
tier, _ := licensing_model.GetProductTierByKey(ctx, l.Tier)
if tier != nil {
tierName = tier.TierName
}
domainCount, _ := licensing_model.CountActivations(ctx, l.ID)
result := map[string]any{
"id": l.ID,
"user_id": l.UserID,
"dlid": l.DLID,
"tier": l.Tier,
"tier_name": tierName,
"max_domains": l.MaxDomains,
"domains_used": domainCount,
"status": l.Status,
"notes": l.Notes,
"created_at": formatTime(l.CreatedAt),
"updated_at": formatTime(l.UpdatedAt),
}
if l.ExpiresAt > 0 {
result["expires_at"] = formatTime(l.ExpiresAt)
}
return result
}
func tierToJSON(t *licensing_model.ProductTier) map[string]any {
return map[string]any{
"id": t.ID,
"tier_key": t.TierKey,
"tier_name": t.TierName,
"repos": t.RepoList(),
"max_domains": t.MaxDomains,
"sort_order": t.SortOrder,
}
}
func formatTime(ts timeutil.TimeStamp) string {
if ts == 0 {
return ""
}
return time.Unix(int64(ts), 0).UTC().Format(time.RFC3339)
}
+242
View File
@@ -0,0 +1,242 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"encoding/xml"
"fmt"
"net/http"
"strings"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
licensing_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/licensing"
)
// Joomla update XML structures.
type xmlUpdates struct {
XMLName xml.Name `xml:"updates"`
Updates []xmlUpdate `xml:"update"`
}
type xmlUpdate struct {
Name string `xml:"name"`
Element string `xml:"element"`
Type string `xml:"type"`
Version string `xml:"version"`
Tag string `xml:"tag"`
DownloadURL xmlDownload `xml:"downloadurl"`
TargetPlatform xmlTarget `xml:"targetplatform"`
PHPMinimum string `xml:"php_minimum,omitempty"`
}
type xmlDownload struct {
Type string `xml:"type,attr"`
Format string `xml:"format,attr"`
URL string `xml:",chardata"`
}
type xmlTarget struct {
Name string `xml:"name,attr"`
Version string `xml:"version,attr"`
}
// ServeUpdates handles GET /api/v1/licensing/updates/{product}.xml?dlid=XXX&domain=YYY
func ServeUpdates(ctx *context.APIContext) {
productFile := ctx.PathParam("product")
productCode := strings.TrimSuffix(productFile, ".xml")
dlid := ctx.FormString("dlid")
domain := ctx.FormString("domain")
// Always return XML content type
ctx.Resp.Header().Set("Content-Type", "application/xml; charset=utf-8")
// Validation failure → empty <updates/>
if dlid == "" || !licensing_model.ValidateDLIDFormat(dlid) {
writeEmptyUpdates(ctx)
return
}
// Look up license
license, err := licensing_model.GetLicenseByDLID(ctx, dlid)
if err != nil {
log.Error("ServeUpdates: GetLicenseByDLID: %v", err)
writeEmptyUpdates(ctx)
return
}
if license == nil || !license.IsActive() {
writeEmptyUpdates(ctx)
return
}
// Check entitlement
hasAccess, err := licensing_model.HasEntitlement(ctx, license.ID, productCode)
if err != nil {
log.Error("ServeUpdates: HasEntitlement: %v", err)
writeEmptyUpdates(ctx)
return
}
if !hasAccess {
writeEmptyUpdates(ctx)
return
}
// Auto-activate domain
if domain != "" {
ip := ctx.Req.RemoteAddr
if idx := strings.LastIndex(ip, ":"); idx >= 0 {
ip = ip[:idx]
}
_, _, err := licensing_model.ActivateDomain(ctx, license.ID, domain, ip, ctx.FormString("joomla_version"), license.MaxDomains)
if err != nil {
if _, ok := err.(licensing_model.ErrDomainLimitReached); ok {
writeEmptyUpdates(ctx)
return
}
log.Error("ServeUpdates: ActivateDomain: %v", err)
}
}
// Resolve repo from entitlement
ents, err := licensing_model.GetEntitlementsByLicense(ctx, license.ID)
if err != nil {
log.Error("ServeUpdates: GetEntitlementsByLicense: %v", err)
writeEmptyUpdates(ctx)
return
}
var repoOwner, repoName string
for _, ent := range ents {
if ent.ProductCode == productCode {
repoOwner = ent.RepoOwner
repoName = ent.RepoName
break
}
}
if repoName == "" {
writeEmptyUpdates(ctx)
return
}
// Find the repo
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, repoOwner, repoName)
if err != nil || repo == nil {
log.Error("ServeUpdates: repo %s/%s not found: %v", repoOwner, repoName, err)
writeEmptyUpdates(ctx)
return
}
// Get stable release
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID,
ListOptions: db.ListOptions{PageSize: 1, Page: 1},
IncludeDrafts: false,
IncludeTags: false,
TagNames: []string{"stable"},
})
if err != nil || len(releases) == 0 {
// Try latest non-draft release (default order is newest first)
releases, err = db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
RepoID: repo.ID,
ListOptions: db.ListOptions{PageSize: 1, Page: 1},
IncludeDrafts: false,
IncludeTags: false,
})
if err != nil || len(releases) == 0 {
writeEmptyUpdates(ctx)
return
}
}
rel := releases[0]
version := extractVersion(rel.TagName)
if version == "" && rel.Title != "" {
version = extractVersion(rel.Title)
}
if version == "" {
version = rel.TagName
}
// Get repo metadata for element name, type, etc.
manifest, _ := repo_model.GetRepoMetadata(ctx, repo.ID)
element := strings.ToLower(repoName)
extType := "package"
phpMin := "8.1"
targetVer := "6..*"
displayName := repoName
if manifest != nil {
if e := manifest.FullElementName(); e != "" {
element = e
}
if manifest.ExtensionType != "" {
extType = manifest.ExtensionType
}
if manifest.PHPMinimum != "" {
phpMin = manifest.PHPMinimum
}
if manifest.TargetVersion != "" {
targetVer = manifest.TargetVersion
}
displayName = manifest.DerivedDisplayName()
}
// Build signed download URL
baseURL := setting.AppURL
token, expires := licensing_service.SignDownloadToken(productCode, version, dlid)
downloadURL := fmt.Sprintf("%sapi/v1/licensing/download/%s/%s.zip?dlid=%s&token=%s&expires=%d",
baseURL, productCode, version, dlid, token, expires)
updates := xmlUpdates{
Updates: []xmlUpdate{
{
Name: displayName,
Element: element,
Type: extType,
Version: version,
Tag: "stable",
DownloadURL: xmlDownload{
Type: "full",
Format: "zip",
URL: downloadURL,
},
TargetPlatform: xmlTarget{
Name: "joomla",
Version: targetVer,
},
PHPMinimum: phpMin,
},
},
}
output, err := xml.MarshalIndent(updates, "", " ")
if err != nil {
log.Error("ServeUpdates: xml.MarshalIndent: %v", err)
writeEmptyUpdates(ctx)
return
}
ctx.Resp.WriteHeader(http.StatusOK)
_, _ = ctx.Resp.Write([]byte(xml.Header))
_, _ = ctx.Resp.Write(output)
}
func writeEmptyUpdates(ctx *context.APIContext) {
ctx.Resp.WriteHeader(http.StatusOK)
_, _ = ctx.Resp.Write([]byte(xml.Header + "<updates/>\n"))
}
// extractVersion strips common tag prefixes to get a clean version.
func extractVersion(s string) string {
v := s
for _, prefix := range []string{"v", "release-", "release/", "stable-"} {
v = strings.TrimPrefix(v, prefix)
}
return v
}
+194
View File
@@ -0,0 +1,194 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package licensing
import (
"net/http"
"time"
licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// validateResponse is the public validation result.
type validateResponse struct {
Valid bool `json:"valid"`
Tier string `json:"tier,omitempty"`
TierName string `json:"tier_name,omitempty"`
Status string `json:"status,omitempty"`
Reason string `json:"reason,omitempty"`
DomainsUsed int `json:"domains_used,omitempty"`
DomainsMax int `json:"domains_max,omitempty"`
ExpiresAt string `json:"expires_at,omitempty"`
}
// statusResponse is the full license detail for authenticated callers.
type statusResponse struct {
Valid bool `json:"valid"`
DLID string `json:"dlid"`
Tier string `json:"tier"`
TierName string `json:"tier_name"`
Status string `json:"status"`
Products []string `json:"products"`
DomainsUsed int `json:"domains_used"`
DomainsMax int `json:"domains_max"`
ExpiresAt string `json:"expires_at,omitempty"`
CreatedAt string `json:"created_at"`
}
// Validate handles GET /api/v1/licensing/validate?dlid=XXX&product=YYY&domain=ZZZ
// Public endpoint — no auth required. Returns minimal valid/invalid with reason.
func Validate(ctx *context.APIContext) {
dlid := ctx.FormString("dlid")
product := ctx.FormString("product")
domain := ctx.FormString("domain")
if dlid == "" {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "missing_dlid"})
return
}
if !licensing_model.ValidateDLIDFormat(dlid) {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "invalid_dlid"})
return
}
license, err := licensing_model.GetLicenseByDLID(ctx, dlid)
if err != nil {
log.Error("Validate: GetLicenseByDLID: %v", err)
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "internal_error"})
return
}
if license == nil {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "invalid_dlid"})
return
}
if license.Status == "revoked" {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "revoked"})
return
}
if license.Status == "suspended" {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "suspended"})
return
}
if license.IsExpired() {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "expired"})
return
}
// Check product entitlement if product is specified
if product != "" {
has, err := licensing_model.HasEntitlement(ctx, license.ID, product)
if err != nil {
log.Error("Validate: HasEntitlement: %v", err)
}
if !has {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "no_entitlement"})
return
}
}
// Check domain limit if domain is specified
if domain != "" {
domainCount, _ := licensing_model.CountActivations(ctx, license.ID)
if license.MaxDomains > 0 && domainCount >= int64(license.MaxDomains) {
// Check if this domain is already activated
acts, _ := licensing_model.GetActivationsByLicense(ctx, license.ID)
found := false
for _, a := range acts {
if a.Domain == domain {
found = true
break
}
}
if !found {
ctx.JSON(http.StatusOK, validateResponse{Valid: false, Reason: "domain_limit"})
return
}
}
}
// Look up tier name
tierName := license.Tier
tier, _ := licensing_model.GetProductTierByKey(ctx, license.Tier)
if tier != nil {
tierName = tier.TierName
}
domainCount, _ := licensing_model.CountActivations(ctx, license.ID)
resp := validateResponse{
Valid: true,
Tier: license.Tier,
TierName: tierName,
Status: license.Status,
DomainsUsed: int(domainCount),
DomainsMax: license.MaxDomains,
}
if license.ExpiresAt > 0 {
resp.ExpiresAt = time.Unix(int64(license.ExpiresAt), 0).UTC().Format(time.RFC3339)
}
ctx.JSON(http.StatusOK, resp)
}
// Status handles GET /api/v1/licensing/{dlid}/status
// Authenticated endpoint — returns full license detail with entitlement list.
func Status(ctx *context.APIContext) {
dlid := ctx.PathParam("dlid")
if dlid == "" || !licensing_model.ValidateDLIDFormat(dlid) {
ctx.JSON(http.StatusBadRequest, map[string]string{"error": "invalid DLID format"})
return
}
license, err := licensing_model.GetLicenseByDLID(ctx, dlid)
if err != nil {
log.Error("Status: GetLicenseByDLID: %v", err)
ctx.JSON(http.StatusInternalServerError, map[string]string{"error": "internal error"})
return
}
if license == nil {
ctx.JSON(http.StatusNotFound, map[string]string{"error": "license not found"})
return
}
// Get entitlements
ents, err := licensing_model.GetEntitlementsByLicense(ctx, license.ID)
if err != nil {
log.Error("Status: GetEntitlementsByLicense: %v", err)
}
products := make([]string, 0, len(ents))
for _, e := range ents {
products = append(products, e.ProductCode)
}
// Get tier name
tierName := license.Tier
tier, _ := licensing_model.GetProductTierByKey(ctx, license.Tier)
if tier != nil {
tierName = tier.TierName
}
domainCount, _ := licensing_model.CountActivations(ctx, license.ID)
resp := statusResponse{
Valid: license.IsActive(),
DLID: license.DLID,
Tier: license.Tier,
TierName: tierName,
Status: license.Status,
Products: products,
DomainsUsed: int(domainCount),
DomainsMax: license.MaxDomains,
CreatedAt: time.Unix(int64(license.CreatedAt), 0).UTC().Format(time.RFC3339),
}
if license.ExpiresAt > 0 {
resp.ExpiresAt = time.Unix(int64(license.ExpiresAt), 0).UTC().Format(time.RFC3339)
}
ctx.JSON(http.StatusOK, resp)
}
+138
View File
@@ -0,0 +1,138 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package org
import (
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// ListIssueStatuses returns active issue status definitions for an org.
func ListIssueStatuses(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/issue-statuses organization orgListIssueStatuses
// ---
// summary: List an organization's issue status definitions
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// description: "IssueStatusDefList"
// schema:
// type: array
// items:
// "$ref": "#/definitions/IssueStatusDef"
// "404":
// "$ref": "#/responses/notFound"
defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*api.IssueStatusDef, 0, len(defs))
for _, d := range defs {
result = append(result, &api.IssueStatusDef{
ID: d.ID,
Name: d.Name,
Color: d.Color,
Description: d.Description,
ClosesIssue: d.ClosesIssue,
SortOrder: d.SortOrder,
})
}
ctx.JSON(http.StatusOK, result)
}
// ListIssuePriorities returns active issue priority definitions for an org.
func ListIssuePriorities(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/issue-priorities organization orgListIssuePriorities
// ---
// summary: List an organization's issue priority definitions
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// description: "IssuePriorityDefList"
// schema:
// type: array
// items:
// "$ref": "#/definitions/IssuePriorityDef"
// "404":
// "$ref": "#/responses/notFound"
defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*api.IssuePriorityDef, 0, len(defs))
for _, d := range defs {
result = append(result, &api.IssuePriorityDef{
ID: d.ID,
Name: d.Name,
Color: d.Color,
Description: d.Description,
SortOrder: d.SortOrder,
IsDefault: d.IsDefault,
})
}
ctx.JSON(http.StatusOK, result)
}
// ListIssueTypes returns active issue type definitions for an org.
func ListIssueTypes(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/issue-types organization orgListIssueTypes
// ---
// summary: List an organization's issue type definitions
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// description: "IssueTypeDefList"
// schema:
// type: array
// items:
// "$ref": "#/definitions/IssueTypeDef"
// "404":
// "$ref": "#/responses/notFound"
defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*api.IssueTypeDef, 0, len(defs))
for _, d := range defs {
result = append(result, &api.IssueTypeDef{
ID: d.ID,
Name: d.Name,
Color: d.Color,
Description: d.Description,
SortOrder: d.SortOrder,
IsDefault: d.IsDefault,
})
}
ctx.JSON(http.StatusOK, result)
}
+4 -4
View File
@@ -11,8 +11,8 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// GetRepoMetadata returns all repo-scoped custom field values.
func GetRepoMetadata(ctx *context.APIContext) {
// GetRepoCustomFields returns all repo-scoped custom field values.
func GetRepoCustomFields(ctx *context.APIContext) {
ownerID := ctx.Repo.Repository.OwnerID
repoID := ctx.Repo.Repository.ID
@@ -35,8 +35,8 @@ func GetRepoMetadata(ctx *context.APIContext) {
ctx.JSON(http.StatusOK, result)
}
// SetRepoMetadata sets repo-scoped custom field values.
func SetRepoMetadata(ctx *context.APIContext) {
// SetRepoCustomFields sets repo-scoped custom field values.
func SetRepoCustomFields(ctx *context.APIContext) {
ownerID := ctx.Repo.Repository.OwnerID
repoID := ctx.Repo.Repository.ID
+91 -14
View File
@@ -722,6 +722,22 @@ func CreateIssue(ctx *context.APIContext) {
form.Labels = make([]int64, 0)
}
// Validate required custom fields BEFORE creating the issue to avoid
// leaving orphaned issues when validation fails.
customFieldDefs, defErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
if defErr != nil {
ctx.APIErrorInternal(defErr)
return
}
for _, def := range customFieldDefs {
if def.Required {
if v, ok := form.CustomFields[def.Name]; !ok || strings.TrimSpace(v) == "" {
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("custom field %q is required", def.Name))
return
}
}
}
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, form.Projects); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.APIError(http.StatusForbidden, err)
@@ -733,24 +749,65 @@ func CreateIssue(ctx *context.APIContext) {
return
}
// Save custom field values if provided (resolve field names to IDs).
if len(form.CustomFields) > 0 {
defs, defErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
if defErr != nil {
ctx.APIErrorInternal(defErr)
// Save custom field values (reuse defs from validation above).
if len(customFieldDefs) > 0 && len(form.CustomFields) > 0 {
vals := make(map[int64]string)
for _, def := range customFieldDefs {
if v, ok := form.CustomFields[def.Name]; ok {
vals[def.ID] = v
}
}
if len(vals) > 0 {
if setErr := issues_model.SetCustomFieldValues(ctx, issue.ID, vals); setErr != nil {
ctx.APIErrorInternal(setErr)
return
}
}
}
// Set org-level issue metadata (status/priority/type).
// Use provided value if > 0, otherwise auto-assign org default.
if form.StatusID > 0 {
if err := issues_model.SetIssueStatusID(ctx, issue.ID, form.StatusID); err != nil {
ctx.APIErrorInternal(err)
return
}
if len(defs) > 0 {
vals := make(map[int64]string)
for _, def := range defs {
if v, ok := form.CustomFields[def.Name]; ok {
vals[def.ID] = v
} else {
if defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
for _, d := range defs {
if !d.ClosesIssue {
_ = issues_model.SetIssueStatusID(ctx, issue.ID, d.ID)
break
}
}
if len(vals) > 0 {
if setErr := issues_model.SetCustomFieldValues(ctx, issue.ID, vals); setErr != nil {
ctx.APIErrorInternal(setErr)
return
}
}
if form.PriorityID > 0 {
if err := issues_model.SetIssuePriorityID(ctx, issue.ID, form.PriorityID); err != nil {
ctx.APIErrorInternal(err)
return
}
} else {
if defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
for _, d := range defs {
if d.IsDefault {
_ = issues_model.SetIssuePriorityID(ctx, issue.ID, d.ID)
break
}
}
}
}
if form.TypeID > 0 {
if err := issues_model.SetIssueTypeID(ctx, issue.ID, form.TypeID); err != nil {
ctx.APIErrorInternal(err)
return
}
} else {
if defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
for _, d := range defs {
if d.IsDefault {
_ = issues_model.SetIssueTypeID(ctx, issue.ID, d.ID)
break
}
}
}
@@ -980,6 +1037,26 @@ func EditIssue(ctx *context.APIContext) {
}
}
// Update org-level issue metadata (status/priority/type)
if canWrite && form.StatusID != nil {
if err := issues_model.SetIssueStatusID(ctx, issue.ID, *form.StatusID); err != nil {
ctx.APIErrorInternal(err)
return
}
}
if canWrite && form.PriorityID != nil {
if err := issues_model.SetIssuePriorityID(ctx, issue.ID, *form.PriorityID); err != nil {
ctx.APIErrorInternal(err)
return
}
}
if canWrite && form.TypeID != nil {
if err := issues_model.SetIssueTypeID(ctx, issue.ID, *form.TypeID); err != nil {
ctx.APIErrorInternal(err)
return
}
}
// Refetch from database to assign some automatic values
issue, err = issues_model.GetIssueByID(ctx, issue.ID)
if err != nil {
+51 -46
View File
@@ -7,7 +7,8 @@ import (
"net/http"
"time"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
@@ -16,11 +17,16 @@ import (
// GetLicenseSettings returns the licensing/update stream settings for a repo.
func GetLicenseSettings(ctx *context.APIContext) {
cfg := licenses.GetEffectiveConfig(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
cfg := updateserver_model.GetEffectiveConfig(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
if cfg == nil {
ctx.JSON(http.StatusOK, &structs.LicenseSettings{})
return
}
// Compute display_name from repo metadata
var displayName string
if meta, err := repo_model.GetRepoMetadata(ctx, ctx.Repo.Repository.ID); err == nil && meta != nil {
displayName = meta.DerivedDisplayName()
}
ctx.JSON(http.StatusOK, &structs.LicenseSettings{
LicensingEnabled: cfg.LicensingEnabled,
RequireKey: cfg.RequireKey,
@@ -28,7 +34,7 @@ func GetLicenseSettings(ctx *context.APIContext) {
Platform: cfg.Platform,
SupportURL: cfg.SupportURL,
ExtensionName: cfg.ExtensionName,
DisplayName: cfg.DisplayName,
DisplayName: displayName,
ExtensionType: cfg.ExtensionType,
Maintainer: cfg.Maintainer,
MaintainerURL: cfg.MaintainerURL,
@@ -42,7 +48,7 @@ func GetLicenseSettings(ctx *context.APIContext) {
func UpdateLicenseSettings(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.LicenseSettings)
cfg := &licenses.UpdateStreamConfig{
cfg := &updateserver_model.UpdateStreamConfig{
OwnerID: ctx.Repo.Repository.OwnerID,
RepoID: ctx.Repo.Repository.ID,
LicensingEnabled: form.LicensingEnabled,
@@ -51,7 +57,6 @@ func UpdateLicenseSettings(ctx *context.APIContext) {
Platform: form.Platform,
SupportURL: form.SupportURL,
ExtensionName: form.ExtensionName,
DisplayName: form.DisplayName,
ExtensionType: form.ExtensionType,
Maintainer: form.Maintainer,
MaintainerURL: form.MaintainerURL,
@@ -61,7 +66,7 @@ func UpdateLicenseSettings(ctx *context.APIContext) {
StreamMode: "joomla",
}
if err := licenses.SaveConfig(ctx, cfg); err != nil {
if err := updateserver_model.SaveConfig(ctx, cfg); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -70,7 +75,7 @@ func UpdateLicenseSettings(ctx *context.APIContext) {
}
// verifyPackageOwnership checks that a package belongs to the current repo's owner.
func verifyPackageOwnership(ctx *context.APIContext, pkg *licenses.LicensePackage) bool {
func verifyPackageOwnership(ctx *context.APIContext, pkg *updateserver_model.LicensePackage) bool {
if pkg.OwnerID != ctx.Repo.Repository.OwnerID {
ctx.APIErrorNotFound(nil)
return false
@@ -79,7 +84,7 @@ func verifyPackageOwnership(ctx *context.APIContext, pkg *licenses.LicensePackag
}
// verifyKeyOwnership checks that a key belongs to the current repo's owner.
func verifyKeyOwnership(ctx *context.APIContext, key *licenses.LicenseKey) bool {
func verifyKeyOwnership(ctx *context.APIContext, key *updateserver_model.LicenseKey) bool {
if key.OwnerID != ctx.Repo.Repository.OwnerID {
ctx.APIErrorNotFound(nil)
return false
@@ -87,7 +92,7 @@ func verifyKeyOwnership(ctx *context.APIContext, key *licenses.LicenseKey) bool
return true
}
func toLicensePackageAPI(pkg *licenses.LicensePackage) *structs.LicensePackage {
func toLicensePackageAPI(pkg *updateserver_model.LicensePackage) *structs.LicensePackage {
return &structs.LicensePackage{
ID: pkg.ID,
OwnerID: pkg.OwnerID,
@@ -103,7 +108,7 @@ func toLicensePackageAPI(pkg *licenses.LicensePackage) *structs.LicensePackage {
}
}
func toLicenseKeyAPI(key *licenses.LicenseKey) *structs.LicenseKey {
func toLicenseKeyAPI(key *updateserver_model.LicenseKey) *structs.LicenseKey {
lk := &structs.LicenseKey{
ID: key.ID,
PackageID: key.PackageID,
@@ -134,7 +139,7 @@ func toLicenseKeyAPI(key *licenses.LicenseKey) *structs.LicenseKey {
// ListLicensePackages lists license packages for the repo owner.
func ListLicensePackages(ctx *context.APIContext) {
pkgs, err := licenses.ListLicensePackages(ctx, ctx.Repo.Repository.OwnerID)
pkgs, err := updateserver_model.ListLicensePackages(ctx, ctx.Repo.Repository.OwnerID)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -151,7 +156,7 @@ func ListLicensePackages(ctx *context.APIContext) {
func CreateLicensePackage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.CreateLicensePackageOption)
pkg := &licenses.LicensePackage{
pkg := &updateserver_model.LicensePackage{
OwnerID: ctx.Repo.Repository.OwnerID,
Name: form.Name,
Description: form.Description,
@@ -165,7 +170,7 @@ func CreateLicensePackage(ctx *context.APIContext) {
pkg.RepoScope = "all"
}
if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
if err := updateserver_model.CreateLicensePackage(ctx, pkg); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -178,7 +183,7 @@ func EditLicensePackage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.EditLicensePackageOption)
pkgID := ctx.PathParamInt64("id")
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -187,7 +192,7 @@ func EditLicensePackage(ctx *context.APIContext) {
return
}
if pkg.Name == licenses.MasterPackageName {
if pkg.Name == updateserver_model.MasterPackageName {
ctx.APIError(http.StatusForbidden, "master package cannot be edited")
return
}
@@ -214,7 +219,7 @@ func EditLicensePackage(ctx *context.APIContext) {
pkg.IsActive = *form.IsActive
}
if err := licenses.UpdateLicensePackage(ctx, pkg); err != nil {
if err := updateserver_model.UpdateLicensePackage(ctx, pkg); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -226,7 +231,7 @@ func EditLicensePackage(ctx *context.APIContext) {
func DeleteLicensePackage(ctx *context.APIContext) {
pkgID := ctx.PathParamInt64("id")
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -235,12 +240,12 @@ func DeleteLicensePackage(ctx *context.APIContext) {
return
}
if pkg.Name == licenses.MasterPackageName {
if pkg.Name == updateserver_model.MasterPackageName {
ctx.APIError(http.StatusForbidden, "master package cannot be deleted")
return
}
if err := licenses.DeleteLicensePackage(ctx, pkgID); err != nil {
if err := updateserver_model.DeleteLicensePackage(ctx, pkgID); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -252,7 +257,7 @@ func DeleteLicensePackage(ctx *context.APIContext) {
func ArchiveLicensePackage(ctx *context.APIContext) {
pkgID := ctx.PathParamInt64("id")
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -261,12 +266,12 @@ func ArchiveLicensePackage(ctx *context.APIContext) {
return
}
if pkg.Name == licenses.MasterPackageName {
if pkg.Name == updateserver_model.MasterPackageName {
ctx.APIError(http.StatusForbidden, "master package cannot be archived")
return
}
if err := licenses.ArchiveLicensePackage(ctx, pkgID); err != nil {
if err := updateserver_model.ArchiveLicensePackage(ctx, pkgID); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -277,7 +282,7 @@ func ArchiveLicensePackage(ctx *context.APIContext) {
// UnarchiveLicensePackage restores an archived license package via API.
func UnarchiveLicensePackage(ctx *context.APIContext) {
pkgID := ctx.PathParamInt64("id")
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -285,7 +290,7 @@ func UnarchiveLicensePackage(ctx *context.APIContext) {
if !verifyPackageOwnership(ctx, pkg) {
return
}
if err := licenses.UnarchiveLicensePackage(ctx, pkgID); err != nil {
if err := updateserver_model.UnarchiveLicensePackage(ctx, pkgID); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -294,7 +299,7 @@ func UnarchiveLicensePackage(ctx *context.APIContext) {
// ListLicenseKeys lists license keys for the repo owner.
func ListLicenseKeys(ctx *context.APIContext) {
keys, err := licenses.ListLicenseKeys(ctx, ctx.Repo.Repository.OwnerID)
keys, err := updateserver_model.ListLicenseKeys(ctx, ctx.Repo.Repository.OwnerID)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -311,7 +316,7 @@ func ListLicenseKeys(ctx *context.APIContext) {
func CreateLicenseKey(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.CreateLicenseKeyOption)
key := &licenses.LicenseKey{
key := &updateserver_model.LicenseKey{
PackageID: form.PackageID,
OwnerID: ctx.Repo.Repository.OwnerID,
LicenseeName: form.LicenseeName,
@@ -329,7 +334,7 @@ func CreateLicenseKey(ctx *context.APIContext) {
key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
} else {
// Auto-calculate from package duration.
pkg, err := licenses.GetLicensePackageByID(ctx, form.PackageID)
pkg, err := updateserver_model.GetLicensePackageByID(ctx, form.PackageID)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -344,7 +349,7 @@ func CreateLicenseKey(ctx *context.APIContext) {
}
}
rawKey, err := licenses.CreateLicenseKey(ctx, key)
rawKey, err := updateserver_model.CreateLicenseKey(ctx, key)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -362,7 +367,7 @@ func EditLicenseKey(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.EditLicenseKeyOption)
keyID := ctx.PathParamInt64("id")
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -395,7 +400,7 @@ func EditLicenseKey(ctx *context.APIContext) {
key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
}
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -409,7 +414,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
// Idempotency check: if payment_ref already exists, return existing key.
if form.PaymentRef != "" {
existing, err := licenses.GetLicenseKeyByPaymentRef(ctx, form.PaymentRef)
existing, err := updateserver_model.GetLicenseKeyByPaymentRef(ctx, form.PaymentRef)
if err == nil {
resp := &structs.LicenseKeyCreated{
LicenseKey: *toLicenseKeyAPI(existing),
@@ -420,7 +425,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
}
}
pkg, err := licenses.GetLicensePackageByID(ctx, form.PackageID)
pkg, err := updateserver_model.GetLicensePackageByID(ctx, form.PackageID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -429,7 +434,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
return
}
key := &licenses.LicenseKey{
key := &updateserver_model.LicenseKey{
PackageID: form.PackageID,
OwnerID: ctx.Repo.Repository.OwnerID,
LicenseeName: form.LicenseeName,
@@ -444,7 +449,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
}
rawKey, err := licenses.CreateLicenseKey(ctx, key)
rawKey, err := updateserver_model.CreateLicenseKey(ctx, key)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -460,7 +465,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
// RenewLicenseKey extends a key's expiration by its package duration.
func RenewLicenseKey(ctx *context.APIContext) {
keyID := ctx.PathParamInt64("id")
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -469,7 +474,7 @@ func RenewLicenseKey(ctx *context.APIContext) {
return
}
pkg, err := licenses.GetLicensePackageByID(ctx, key.PackageID)
pkg, err := updateserver_model.GetLicensePackageByID(ctx, key.PackageID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -480,20 +485,20 @@ func RenewLicenseKey(ctx *context.APIContext) {
days = 365
}
if err := licenses.RenewLicenseKey(ctx, keyID, days); err != nil {
if err := updateserver_model.RenewLicenseKey(ctx, keyID, days); err != nil {
ctx.APIErrorInternal(err)
return
}
// Reload key to get updated fields.
key, _ = licenses.GetLicenseKeyByID(ctx, keyID)
key, _ = updateserver_model.GetLicenseKeyByID(ctx, keyID)
ctx.JSON(http.StatusOK, toLicenseKeyAPI(key))
}
// RevokeLicenseKey deactivates a license key via API.
func RevokeLicenseKey(ctx *context.APIContext) {
keyID := ctx.PathParamInt64("id")
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -503,7 +508,7 @@ func RevokeLicenseKey(ctx *context.APIContext) {
}
key.IsActive = false
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -514,7 +519,7 @@ func RevokeLicenseKey(ctx *context.APIContext) {
// DeleteLicenseKey deletes a license key.
func DeleteLicenseKey(ctx *context.APIContext) {
keyID := ctx.PathParamInt64("id")
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
if err != nil {
ctx.APIErrorNotFound(err)
return
@@ -522,7 +527,7 @@ func DeleteLicenseKey(ctx *context.APIContext) {
if !verifyKeyOwnership(ctx, key) {
return
}
if err := licenses.DeleteLicenseKey(ctx, keyID); err != nil {
if err := updateserver_model.DeleteLicenseKey(ctx, keyID); err != nil {
ctx.APIErrorInternal(err)
return
}
@@ -533,7 +538,7 @@ func DeleteLicenseKey(ctx *context.APIContext) {
func ValidateLicenseKey(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.ValidateLicenseKeyOption)
key, pkg, err := licenses.ValidateLicenseKey(ctx, form.Key, form.Domain)
key, pkg, err := updateserver_model.ValidateLicenseKey(ctx, form.Key, form.Domain)
if err != nil {
ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
Valid: false,
@@ -542,7 +547,7 @@ func ValidateLicenseKey(ctx *context.APIContext) {
return
}
_ = licenses.TouchHeartbeat(ctx, key.ID)
_ = updateserver_model.TouchHeartbeat(ctx, key.ID)
var expiresAt *time.Time
if key.ExpiresUnix > 0 {
@@ -555,7 +560,7 @@ func ValidateLicenseKey(ctx *context.APIContext) {
maxSites = pkg.MaxSites
}
sitesUsed, _ := licenses.CountUniqueDomainsByKey(ctx, key.ID)
sitesUsed, _ := updateserver_model.CountUniqueDomainsByKey(ctx, key.ID)
ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
Valid: true,
@@ -569,7 +574,7 @@ func ValidateLicenseKey(ctx *context.APIContext) {
// GetLicenseKeyUsage returns usage logs for a license key.
func GetLicenseKeyUsage(ctx *context.APIContext) {
usages, err := licenses.GetRecentUsage(ctx, ctx.PathParamInt64("id"), 100)
usages, err := updateserver_model.GetRecentUsage(ctx, ctx.PathParamInt64("id"), 100)
if err != nil {
ctx.APIErrorInternal(err)
return
+51 -21
View File
@@ -11,24 +11,31 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// apiManifest is the JSON representation of a repo manifest.
type apiManifest struct {
// apiMetadata is the JSON representation of a repo manifest.
type apiMetadata struct {
Name string `json:"name"`
Org string `json:"org"`
Description string `json:"description"`
Version string `json:"version"`
LicenseSPDX string `json:"license_spdx"`
LicenseName string `json:"license_name"`
VersionPrefix string `json:"version_prefix"`
ElementName string `json:"element_name"`
Platform string `json:"platform"`
StandardsVersion string `json:"standards_version"`
StandardsSource string `json:"standards_source"`
DisplayName string `json:"display_name"` // read-only, computed from extension_type + name
Maintainer string `json:"maintainer"`
MaintainerURL string `json:"maintainer_url"`
InfoURL string `json:"info_url"`
TargetVersion string `json:"target_version"`
PHPMinimum string `json:"php_minimum"`
Language string `json:"language"`
PackageType string `json:"package_type"`
ExtensionType string `json:"extension_type"`
EntryPoint string `json:"entry_point"`
}
// GetRepoManifest returns the manifest settings for a repository.
func GetRepoManifest(ctx *context.APIContext) {
// GetRepoMetadata returns the manifest settings for a repository.
func GetRepoMetadata(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/manifest repository repoGetManifest
// ---
// summary: Get repo manifest settings
@@ -39,38 +46,46 @@ func GetRepoManifest(ctx *context.APIContext) {
// "$ref": "#/responses/Manifest"
// "404":
// "$ref": "#/responses/notFound"
m, err := repo_model.GetRepoManifest(ctx, ctx.Repo.Repository.ID)
m, err := repo_model.GetRepoMetadata(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if m == nil {
// Return defaults from repo metadata.
ctx.JSON(http.StatusOK, &apiManifest{
ctx.JSON(http.StatusOK, &apiMetadata{
Name: ctx.Repo.Repository.Name,
Org: ctx.Repo.Repository.OwnerName,
Description: ctx.Repo.Repository.Description,
})
return
}
ctx.JSON(http.StatusOK, &apiManifest{
ctx.JSON(http.StatusOK, &apiMetadata{
Name: m.Name,
Org: m.Org,
Description: m.Description,
Version: m.Version,
LicenseSPDX: m.LicenseSPDX,
LicenseName: m.LicenseName,
VersionPrefix: m.VersionPrefix,
ElementName: m.FullElementName(),
Platform: m.Platform,
StandardsVersion: m.StandardsVersion,
StandardsSource: m.StandardsSource,
DisplayName: m.DerivedDisplayName(),
Maintainer: m.Maintainer,
MaintainerURL: m.MaintainerURL,
InfoURL: m.InfoURL,
TargetVersion: m.TargetVersion,
PHPMinimum: m.PHPMinimum,
Language: m.Language,
PackageType: m.PackageType,
ExtensionType: m.ExtensionType,
EntryPoint: m.EntryPoint,
})
}
// UpdateRepoManifest updates the manifest settings for a repository.
func UpdateRepoManifest(ctx *context.APIContext) {
// UpdateRepoMetadata updates the manifest settings for a repository.
func UpdateRepoMetadata(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/manifest repository repoUpdateManifest
// ---
// summary: Update repo manifest settings
@@ -81,45 +96,60 @@ func UpdateRepoManifest(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/Manifest"
var req apiManifest
var req apiMetadata
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
ctx.APIError(http.StatusBadRequest, err)
return
}
m := &repo_model.RepoManifest{
m := &repo_model.RepoMetadata{
RepoID: ctx.Repo.Repository.ID,
Name: req.Name,
Org: req.Org,
Description: req.Description,
Version: req.Version,
LicenseSPDX: req.LicenseSPDX,
LicenseName: req.LicenseName,
VersionPrefix: req.VersionPrefix,
ElementName: req.ElementName,
Platform: req.Platform,
StandardsVersion: req.StandardsVersion,
StandardsSource: req.StandardsSource,
Maintainer: req.Maintainer,
MaintainerURL: req.MaintainerURL,
InfoURL: req.InfoURL,
TargetVersion: req.TargetVersion,
PHPMinimum: req.PHPMinimum,
Language: req.Language,
PackageType: req.PackageType,
ExtensionType: req.ExtensionType,
EntryPoint: req.EntryPoint,
}
if err := repo_model.CreateOrUpdateRepoManifest(ctx, m); err != nil {
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, m); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, &apiManifest{
ctx.JSON(http.StatusOK, &apiMetadata{
Name: m.Name,
Org: m.Org,
Description: m.Description,
Version: m.Version,
LicenseSPDX: m.LicenseSPDX,
LicenseName: m.LicenseName,
VersionPrefix: m.VersionPrefix,
ElementName: m.FullElementName(),
Platform: m.Platform,
StandardsVersion: m.StandardsVersion,
StandardsSource: m.StandardsSource,
DisplayName: m.DerivedDisplayName(),
Maintainer: m.Maintainer,
MaintainerURL: m.MaintainerURL,
InfoURL: m.InfoURL,
TargetVersion: m.TargetVersion,
PHPMinimum: m.PHPMinimum,
Language: m.Language,
PackageType: m.PackageType,
ExtensionType: m.ExtensionType,
EntryPoint: m.EntryPoint,
})
}
+25 -11
View File
@@ -12,6 +12,7 @@ import (
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/gitrepo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
@@ -136,7 +137,7 @@ func EditWikiPage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateWikiPageOptions)
oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParam("*"))
newWikiName := wiki_service.UserTitleToWebPath("", form.Title)
if len(newWikiName) == 0 {
@@ -242,7 +243,7 @@ func DeleteWikiPage(ctx *context.APIContext) {
// "423":
// "$ref": "#/responses/repoArchivedError"
wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
wikiName := wiki_service.WebPathFromRequest(ctx.PathParam("*"))
if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil {
if err.Error() == "file does not exist" {
@@ -307,14 +308,23 @@ func ListWikiPages(ctx *context.APIContext) {
skip := (page - 1) * limit
maxNum := page * limit
entries, err := commit.ListEntries()
entries, err := commit.ListEntriesRecursiveFast()
if err != nil {
ctx.APIErrorInternal(err)
return
}
pages := make([]*api.WikiPageMetaData, 0, len(entries))
for i, entry := range entries {
if i < skip || i >= maxNum || !entry.IsRegular() {
// Filter to regular files only and count for pagination.
var regularEntries []*git.TreeEntry
for _, entry := range entries {
if entry.IsRegular() {
regularEntries = append(regularEntries, entry)
}
}
pages := make([]*api.WikiPageMetaData, 0, min(len(regularEntries), limit))
for i, entry := range regularEntries {
if i < skip || i >= maxNum {
continue
}
c, err := wikiRepo.GetCommitByPath(entry.Name())
@@ -333,8 +343,8 @@ func ListWikiPages(ctx *context.APIContext) {
pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
}
ctx.SetLinkHeader(int64(len(entries)), limit)
ctx.SetTotalCountHeader(int64(len(entries)))
ctx.SetLinkHeader(int64(len(regularEntries)), limit)
ctx.SetTotalCountHeader(int64(len(regularEntries)))
ctx.JSON(http.StatusOK, pages)
}
@@ -368,7 +378,7 @@ func GetWikiPage(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
// get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
pageName := wiki_service.WebPathFromRequest(ctx.PathParam("*"))
wikiPage := getWikiPage(ctx, pageName)
if !ctx.Written() {
@@ -418,7 +428,7 @@ func ListPageRevisions(ctx *context.APIContext) {
}
// get requested pagename
pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName"))
pageName := wiki_service.WebPathFromRequest(ctx.PathParam("*"))
if len(pageName) == 0 {
pageName = "Home"
}
@@ -499,11 +509,15 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit)
func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string {
blob := entry.Blob()
if blob.Size() > setting.API.DefaultMaxBlobSize {
log.Warn("wikiContentsByEntry: blob %s exceeds max size (%d > %d), content omitted",
entry.Name(), blob.Size(), setting.API.DefaultMaxBlobSize)
return ""
}
content, err := blob.GetBlobContentBase64(nil)
if err != nil {
ctx.APIErrorInternal(err)
// Return the error details but don't abort — the page metadata
// is still useful even without content.
log.Error("wikiContentsByEntry: GetBlobContentBase64 for %s: %v", entry.Name(), err)
return ""
}
return content
+127
View File
@@ -0,0 +1,127 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package admin
import (
"net/http"
"strconv"
licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
const tplLicenseTiers templates.TplName = "admin/license_tiers"
// LicenseTiers shows the product tier management page.
func LicenseTiers(ctx *context.Context) {
ctx.Data["Title"] = "Product Tiers"
ctx.Data["PageIsAdminLicenseTiers"] = true
tiers, err := licensing_model.GetAllProductTiers(ctx)
if err != nil {
ctx.ServerError("GetAllProductTiers", err)
return
}
type tierView struct {
*licensing_model.ProductTier
Repos []string
LicenseCount int64
}
views := make([]tierView, 0, len(tiers))
for _, t := range tiers {
count, _ := ctx.Orm().Where("tier = ?", t.TierKey).Count(new(licensing_model.License))
views = append(views, tierView{
ProductTier: t,
Repos: t.RepoList(),
LicenseCount: count,
})
}
ctx.Data["Tiers"] = views
ctx.HTML(http.StatusOK, tplLicenseTiers)
}
// LicenseTierCreate handles POST to create a new tier.
func LicenseTierCreate(ctx *context.Context) {
tierKey := ctx.FormString("tier_key")
tierName := ctx.FormString("tier_name")
repos := ctx.FormStrings("repos")
maxDomains, _ := strconv.Atoi(ctx.FormString("max_domains"))
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
if tierKey == "" || tierName == "" {
ctx.Flash.Error("Tier key and name are required")
ctx.Redirect("/admin/license-tiers")
return
}
reposJSON, _ := json.Marshal(repos)
tier := &licensing_model.ProductTier{
TierKey: tierKey,
TierName: tierName,
Repos: string(reposJSON),
MaxDomains: maxDomains,
SortOrder: sortOrder,
}
if _, err := ctx.Orm().Insert(tier); err != nil {
ctx.Flash.Error("Failed to create tier: " + err.Error())
} else {
ctx.Flash.Success("Tier '" + tierName + "' created")
}
ctx.Redirect("/admin/license-tiers")
}
// LicenseTierUpdate handles POST to update a tier.
func LicenseTierUpdate(ctx *context.Context) {
id, _ := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
tier := new(licensing_model.ProductTier)
has, _ := ctx.Orm().ID(id).Get(tier)
if !has {
ctx.NotFound(nil)
return
}
tier.TierName = ctx.FormString("tier_name")
repos := ctx.FormStrings("repos")
reposJSON, _ := json.Marshal(repos)
tier.Repos = string(reposJSON)
tier.MaxDomains, _ = strconv.Atoi(ctx.FormString("max_domains"))
tier.SortOrder, _ = strconv.Atoi(ctx.FormString("sort_order"))
if _, err := ctx.Orm().ID(id).Cols("tier_name", "repos", "max_domains", "sort_order").Update(tier); err != nil {
ctx.Flash.Error("Failed to update tier: " + err.Error())
} else {
ctx.Flash.Success("Tier '" + tier.TierName + "' updated")
}
ctx.Redirect("/admin/license-tiers")
}
// LicenseTierDelete handles POST to delete a tier.
func LicenseTierDelete(ctx *context.Context) {
id, _ := strconv.ParseInt(ctx.FormString("id"), 10, 64)
tier := new(licensing_model.ProductTier)
has, _ := ctx.Orm().ID(id).Get(tier)
if !has {
ctx.NotFound(nil)
return
}
count, _ := ctx.Orm().Where("tier = ?", tier.TierKey).Count(new(licensing_model.License))
if count > 0 {
ctx.Flash.Error("Cannot delete tier with active licenses. Reassign licenses first.")
ctx.Redirect("/admin/license-tiers")
return
}
ctx.Orm().ID(id).Delete(new(licensing_model.ProductTier))
ctx.Flash.Success("Tier '" + tier.TierName + "' deleted")
ctx.Redirect("/admin/license-tiers")
}

Some files were not shown because too many files have changed in this diff Show More