Compare commits

..

67 Commits

Author SHA1 Message Date
jmiller bec7b70ff5 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 29s
2026-05-31 04:22:29 +00:00
jmiller 92b4cd61c2 Merge pull request 'fix(ui): details/summary toggle for create package' (#294) from fix/admin-delete-only into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 18s
PR RC Release / Build RC Release (pull_request) Successful in 32s
2026-05-31 04:22:08 +00:00
Jonathan Miller dc2647977c fix(ui): use HTML details/summary for package create toggle
Universal: PR Check / Build RC Package (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
PR RC Release / Build RC Release (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Branch Cleanup / Delete merged branch (pull_request) Successful in 4s
Replace broken JavaScript onclick toggle with native HTML
<details>/<summary> element. Works without JS, accessible,
and styled as a Fomantic UI button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 23:21:42 -05:00
jmiller 685d89acf9 Merge pull request 'chore: merge dev into main — admin permissions' (#293) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m9s
2026-05-31 04:19:17 +00:00
jmiller 8ceddefbdb Merge branch 'main' into dev
Universal: PR Check / Build RC Package (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
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 25s
2026-05-31 04:19:04 +00:00
jmiller ea2666948e Merge pull request 'feat(permissions): site admin only for delete' (#292) from fix/admin-delete-only into dev
Universal: PR Check / Build RC Package (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 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 25s
2026-05-31 04:18:53 +00:00
Jonathan Miller 3aabd1b1f9 feat(permissions): only site admins can delete license packages
Universal: PR Check / Build RC Package (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
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
- Delete button only visible to site admins (super admins)
- Delete handler checks ctx.IsUserSiteAdmin() and returns 404 otherwise
- Repo admins can still create, edit, revoke — but not delete
- IsSiteAdmin set in both repo and org context data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 23:18:31 -05:00
jmiller 0328258529 Merge pull request 'chore: merge dev into main — org update streams' (#291) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m3s
2026-05-31 04:09:53 +00:00
jmiller e6ff9a99f9 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (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
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 24s
2026-05-31 04:09:42 +00:00
jmiller 4138ab7d47 Merge pull request 'feat(org): Update Streams settings page + package edit/delete' (#290) from feat/repo-update-settings into dev
Universal: PR Check / Build RC Package (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 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
PR RC Release / Build RC Release (pull_request) Successful in 25s
2026-05-31 04:09:30 +00:00
Jonathan Miller d75e648970 feat(org): add Update Streams settings page in org settings
Universal: PR Check / Build RC Package (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
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 2s
Add "Licenses & Update Streams" tab to org settings sidebar with:
- Stream mode: Joomla standard or Custom
- Active streams table showing name, suffix, description
- Custom streams JSON editor
- Saves org-level defaults that repos inherit

Ref #265

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 23:09:07 -05:00
jmiller 06a382e82e Merge pull request 'chore: merge dev into main — package edit/delete' (#289) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m39s
2026-05-31 04:04:41 +00:00
jmiller 0ff4b12f27 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (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 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 23s
2026-05-31 04:04:29 +00:00
jmiller 53f0472e4f Merge pull request 'feat(licenses): edit and delete packages via web UI' (#288) from feat/repo-update-settings into dev
Universal: PR Check / Build RC Package (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
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 21s
2026-05-31 04:04:18 +00:00
Jonathan Miller 021ddbb17a feat(licenses): edit and delete license packages via web UI
Universal: PR Check / Build RC Package (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
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Add edit and delete actions for license packages:
- Edit button (pencil icon) opens edit form with all package fields
- Delete button (trash icon) with confirmation dialog
- Edit form includes active/inactive toggle
- Routes: GET/POST /licenses/packages/{id}/edit, POST /licenses/packages/{id}/delete

Ref #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 23:03:44 -05:00
jmiller 12f78e8feb Merge pull request 'chore: merge dev into main — platform settings' (#287) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m8s
2026-05-31 03:57:18 +00:00
jmiller b72d419fb1 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (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 3s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 28s
2026-05-31 03:57:06 +00:00
jmiller 865e8b9bfa Merge pull request 'feat(updates): per-repo platform + require-key + platform buttons' (#286) from feat/repo-update-settings into dev
Universal: PR Check / Build RC Package (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
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 24s
2026-05-31 03:56:56 +00:00
Jonathan Miller 3a5ca580db feat(updates): per-repo platform, require-key, platform-aware buttons
Universal: PR Check / Build RC Package (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
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
- Repo settings: platform dropdown (Joomla/Dolibarr/Both) + require key
- Releases page buttons change based on platform setting
- Update feed enforces require-key (empty response without valid key)
- key_plain column stores full key for copy functionality
- DB migrations v337 (key_plain) + v338 (platform, require_key)

Ref #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 22:55:55 -05:00
jmiller 93f186bd1d Merge pull request 'chore: merge dev into main — UI fixes' (#285) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m40s
2026-05-31 03:48:29 +00:00
jmiller 56988e810e Merge branch 'main' into dev
Universal: PR Check / Build RC Package (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
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) Successful in 47s
2026-05-31 03:48:11 +00:00
jmiller f8b7af30d9 Merge pull request 'fix(ui): always-visible create package form + org locale strings' (#284) from fix/licenses-ui-v3 into dev
Universal: PR Check / Build RC Package (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
Universal: PR Check / Validate PR (pull_request) Failing after 8s
PR RC Release / Build RC Release (pull_request) Successful in 39s
2026-05-31 03:47:53 +00:00
Jonathan Miller df06e11704 fix(ui): always-visible create package form + clearer labels + org locale strings
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Branch Policy Check / Verify merge target (pull_request) Successful in 3s
PR RC Release / Build RC Release (pull_request) Successful in 10s
Branch Cleanup / Delete merged branch (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 20s
Replace toggle button with always-visible "Create New License Package"
section. Added org settings locale strings for Update Streams page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 22:47:29 -05:00
gitea-actions[bot] 51517a5275 chore: update channels for 05.14.00 [skip ci] 2026-05-31 03:46:14 +00:00
gitea-actions[bot] 336338b541 chore(ci): remove update-server.yml for update server migration [skip ci] 2026-05-31 03:46:11 +00:00
gitea-actions[bot] 8f537df6c5 chore(ci): remove cascade-dev.yml for update server migration [skip ci] 2026-05-31 03:46:02 +00:00
gitea-actions[bot] 7ac8c3d0a1 chore(ci): remove auto-bump.yml for update server migration [skip ci] 2026-05-31 03:45:51 +00:00
gitea-actions[bot] 3465b4fa01 chore(ci): remove pre-release.yml for update server migration [skip ci] 2026-05-31 03:45:42 +00:00
gitea-actions[bot] 0b4e7575eb chore(ci): remove auto-release.yml for update server migration [skip ci] 2026-05-31 03:45:33 +00:00
gitea-actions[bot] 508185f7ad chore(release): build 05.14.00 [skip ci] 2026-05-31 03:45:25 +00:00
jmiller c8ba0647d3 Merge pull request 'chore: merge dev into main' (#283) from dev into main
Deploy MokoGitea / deploy (push) Successful in 5m56s
2026-05-31 03:44:31 +00:00
jmiller d55da332cf Merge branch 'main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Auto Version Bump / Version Bump (push) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 32s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 5m29s
2026-05-31 03:44:16 +00:00
jmiller a04e237f17 Merge pull request 'feat(licenses): org settings, copyable keys, master keys' (#282) from feat/org-licensing into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Build & Release / Build & Release Pipeline (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
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Build & Release / Promote to RC (pull_request) Failing after 12s
PR RC Release / Build RC Release (pull_request) Successful in 28s
2026-05-31 03:44:06 +00:00
jmiller e7cc4c120f Merge branch 'dev' into feat/org-licensing
Universal: PR Check / Build RC Package (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
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m13s
2026-05-31 03:43:55 +00:00
jmiller aa54f3834e chore: sync updates.xml 05.13.00 from main [skip ci] 2026-05-31 03:38:14 +00:00
gitea-actions[bot] 4d93f23037 chore: update channels for 05.13.00 [skip ci] 2026-05-31 03:37:46 +00:00
gitea-actions[bot] d7e2ffd02b chore(release): build 05.13.00 [skip ci] 2026-05-31 03:37:12 +00:00
jmiller b9d81ca5c5 Merge pull request 'chore: merge dev into main — URL fix' (#281) from dev into main
Deploy MokoGitea / deploy (push) Successful in 4m34s
2026-05-31 03:36:12 +00:00
jmiller 59c62dc687 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 5s
Universal: PR Check / Branch Policy (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Universal: Auto Version Bump / Version Bump (push) Failing after 13s
Branch Cleanup / Delete merged branch (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 4m8s
2026-05-31 03:35:42 +00:00
jmiller b14ffa083e Merge pull request 'fix(ui): full domain URL in update feed fields' (#280) from fix/update-feed-urls into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: Build & Release / Build & Release Pipeline (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
Universal: PR Check / Validate PR (pull_request) Failing after 20s
Universal: Build & Release / Promote to RC (pull_request) Failing after 30s
PR RC Release / Build RC Release (pull_request) Successful in 50s
2026-05-31 03:34:56 +00:00
Jonathan Miller 2cc57bbbbc fix(ui): show full domain URL in update feed copy fields
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 3s
PR RC Release / Build RC Release (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Failing after 17s
Branch Cleanup / Delete merged branch (pull_request) Successful in 4s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m40s
Use .Repository.HTMLURL instead of AppSubUrl+RepoLink so the
copyable update feed URLs include the full domain name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 22:34:12 -05:00
jmiller 3cd7687c06 chore: sync updates.xml 05.12.00 from main [skip ci] 2026-05-31 03:32:48 +00:00
gitea-actions[bot] c3b2643b0c chore: update channels for 05.12.00 [skip ci] 2026-05-31 03:32:46 +00:00
gitea-actions[bot] 0159e567e2 chore(release): build 05.12.00 [skip ci] 2026-05-31 03:32:10 +00:00
jmiller f194b204b4 Merge pull request 'chore: merge dev into main — org licenses + master keys' (#279) from dev into main
Deploy MokoGitea / deploy (push) Successful in 5m50s
2026-05-31 03:31:24 +00:00
jmiller f118f084ce Merge branch 'main' into dev
Universal: PR Check / Build RC Package (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
Universal: Auto Version Bump / Version Bump (push) Failing after 8s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 35s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 5m30s
2026-05-31 03:31:09 +00:00
jmiller 2821c35326 Merge pull request 'feat(licenses): org licenses page + master keys + menu fixes' (#278) from feat/org-licensing into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: Build & Release / Build & Release Pipeline (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
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Universal: Build & Release / Promote to RC (pull_request) Failing after 17s
PR RC Release / Build RC Release (pull_request) Successful in 29s
2026-05-31 03:30:59 +00:00
Jonathan Miller 5b02cf188e feat(licenses): org-level licenses page, master keys, and menu fixes
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m13s
Universal: PR Check / Build RC Package (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
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Major licensing UI improvements:
- Org-level Licenses tab in org menu (visible to org members)
- Org-level Licenses page with full CRUD (packages, keys, revoke)
- Auto-created master key: when admin first visits Licenses page,
  a Master (Internal) package + key is auto-generated
- Master keys marked with orange "Master" badge in key list
- Revoking a master key auto-creates a new one on next visit
- Fixed "New Package" button toggle (was using tw-hidden class
  that didn't work, now uses style.display)
- IsRepoAdmin set as context data for template access
- Master keys have IsInternal=true, lifetime, all channels

Ref #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 22:30:33 -05:00
Jonathan Miller 689173ecab feat(licenses): auto-create master key for org/repo
When an admin first visits the Licenses page, a master license package
and key are automatically created:
- Master package: lifetime, unlimited, all channels, all repos
- Master key: IsInternal=true, never expires
- Raw key shown once with copy instructions
- If master key is revoked, a new one is created on next visit

The master key is always present — revoking it and revisiting the page
generates a fresh one.

Ref #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 22:26:24 -05:00
jmiller b2fe44fbc3 chore: sync updates.xml 05.11.00 from main [skip ci] 2026-05-31 03:17:54 +00:00
gitea-actions[bot] 0e89ef9944 chore: update channels for 05.11.00 [skip ci] 2026-05-31 03:17:53 +00:00
gitea-actions[bot] 522dadecf0 chore(release): build 05.11.00 [skip ci] 2026-05-31 03:17:24 +00:00
jmiller f1b9bb2f3d Merge pull request 'chore: merge dev into main — licenses tab fix v2' (#277) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m28s
2026-05-31 03:16:44 +00:00
jmiller 7bbaf218d5 Merge branch 'main' into dev
Universal: PR Check / Build RC Package (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
Universal: Auto Version Bump / Version Bump (push) Failing after 8s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 32s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m11s
2026-05-31 03:16:31 +00:00
jmiller 33a550f838 Merge pull request 'fix(ui): IsRepoAdmin for Licenses tab' (#276) from fix/licenses-tab-v2 into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Build RC Package (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
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 26s
2026-05-31 03:16:21 +00:00
Jonathan Miller e29ee5f91b fix(ui): set IsRepoAdmin context data for Licenses tab visibility
Universal: PR Check / Build RC Package (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 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m9s
The template couldn't call .Permission.IsAdmin() directly. Set
IsRepoAdmin as a context data variable so the template can use it.
Licenses tab now shows for repo admins even without packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 22:15:39 -05:00
jmiller 984a99188e chore: sync updates.xml 05.10.00 from main [skip ci] 2026-05-31 03:07:16 +00:00
gitea-actions[bot] 92fc77a6d1 chore: update channels for 05.10.00 [skip ci] 2026-05-31 03:07:15 +00:00
gitea-actions[bot] ea411e09be chore(release): build 05.10.00 [skip ci] 2026-05-31 03:06:36 +00:00
jmiller 9b141b39c5 Merge pull request 'chore: merge dev into main — licenses tab fix' (#275) from dev into main
Deploy MokoGitea / deploy (push) Successful in 4m35s
chore: merge dev into main — licenses tab fix (#275)
2026-05-31 03:05:51 +00:00
jmiller 85e4356fce Merge branch 'main' into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Universal: Auto Version Bump / Version Bump (push) Failing after 13s
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) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 34s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m13s
2026-05-31 03:05:36 +00:00
jmiller 1654181a9e Merge pull request 'fix(ui): show Licenses tab for admins always' (#274) from fix/licenses-tab-always-admin into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: Build & Release / Build & Release Pipeline (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 1s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Universal: Build & Release / Promote to RC (pull_request) Failing after 14s
PR RC Release / Build RC Release (pull_request) Successful in 28s
2026-05-31 03:05:25 +00:00
Jonathan Miller 282ef8f3e7 fix(ui): show Licenses tab for repo admins even without packages
Universal: PR Check / Build RC Package (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
PR RC Release / Build RC Release (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m14s
The Licenses tab was hidden when no packages existed, making it
impossible for admins to find the page to create their first package.
Now shows for repo admins always, and for everyone when packages exist.

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