Compare commits

...

50 Commits

Author SHA1 Message Date
gitea-actions[bot] bcfae6d370 chore(version): pre-release bump to 05.51.01-dev [skip ci] 2026-06-07 16:58:28 +00:00
Jonathan Miller 6bb6e2ffd8 chore: rename moko-platform to MokoPlatform + changelog for v1.26.1-moko.06.12
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 31s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m28s
Rename moko-platform references across locale, changelog, and README.
Add changelog entry for dependency scanner, CDN, and Joomla fix.
Closes #548
2026-06-07 11:57:26 -05:00
jmiller 90fb6169d0 feat: auto-detect version bump level from PR source branch [skip ci] 2026-06-07 16:50:22 +00:00
gitea-actions[bot] 5f6d25ff7b chore(release): build 05.51.00 [skip ci] 2026-06-07 16:40:35 +00:00
jmiller 9adcac546f Merge pull request 'release: dependency scanner + CDN release delivery' (#566) from dev into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 1m41s
2026-06-07 16:39:23 +00:00
jmiller b6b4d6f525 Merge pull request 'fix(licensing): hide require-key option for Joomla update servers' (#567) from fix/hide-joomla-require-key into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 3m23s
2026-06-07 16:39:09 +00:00
Jonathan Miller 74279c55e3 fix(licensing): hide require-key option for Joomla update servers
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m25s
Joomla's update system does not support license key authentication,
so hide the "Require license key for update feeds" checkbox when
the platform is set to Joomla or Joomla+Dolibarr.
2026-06-07 11:38:05 -05:00
jmiller 78803e60df Merge pull request 'feat(cdn): built-in CDN for release asset delivery' (#565) from feat/cdn-release-delivery into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Successful in 27s
PR RC Release / Build RC Release (pull_request) Failing after 29s
2026-06-07 16:12:41 +00:00
jmiller 75316bf80a Merge pull request 'feat(security): dependency vulnerability scanner' (#562) from feat/dependency-scanner into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-07 16:12:24 +00:00
Jonathan Miller 37d59e7b59 feat(cdn): built-in CDN for release asset delivery (#561)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 2m55s
Add CDN system that serves release assets via a dedicated hostname
(e.g., cdn.mokoconsulting.tech) with per-asset public/private toggles,
IP/referrer allowlists, and aggressive caching headers.

- Host-based routing intercepts CDN domain before auth middleware
- Per-attachment cdn_public flag controls CDN visibility
- Releases in an update stream are excluded from CDN (update server takes precedence)
- CORS, ETag, Cache-Control headers for downstream CDN compatibility
- IP/CIDR and referrer domain allowlists for abuse prevention
2026-06-07 11:07:30 -05:00
Jonathan Miller 18fc79fa0a feat(security): add dependency vulnerability scanner (#551)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 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
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m22s
Add dependency scanner module that parses manifest files (go.mod,
package.json, composer.json, requirements.txt) and checks dependencies
against the OSV.dev API for known CVEs. Implements the existing Scanner
interface and wires into the orchestrator for push-time scanning.
2026-06-07 10:32:04 -05:00
jmiller 931d685593 ci: auto pre-release on push to dev/alpha/beta/rc branches [skip ci] 2026-06-07 15:26:05 +00:00
Jonathan Miller 9121f1b36a Merge dev: final version
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 3m49s
2026-06-07 02:21:16 +00:00
jmiller f3ce51d629 Merge pull request 'chore: final version update' (#560) from chore/final-version into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-07 02:21:06 +00:00
Jonathan Miller 1f505b48c7 chore: update wiki version to v1.26.1-moko.06.11.01
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
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
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m49s
2026-06-06 21:20:37 -05:00
Jonathan Miller 4b07ccc578 Merge dev: manifest desc dedup fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Deploy MokoGitea / deploy (push) Failing after 39s
2026-06-07 02:08:43 +00:00
jmiller afda7abcbe Merge pull request 'fix(settings): remove duplicate description from manifest' (#559) from fix/manifest-desc-dedup into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-07 02:08:31 +00:00
Jonathan Miller 798d9c3ae0 fix(settings): remove duplicate description from manifest page
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m40s
Description is already managed in repo general settings. The manifest
now reads it from the repository object instead of a separate form field.
2026-06-06 21:07:39 -05:00
Jonathan Miller 4d42205cc8 Merge dev: wiki version update
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 32s
2026-06-07 01:57:22 +00:00
jmiller e0f22dd397 Merge pull request 'chore: wiki version update' (#558) from chore/wiki-version-update into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-07 01:57:10 +00:00
Jonathan Miller ecda05aa46 chore: update wiki version to v1.26.1-moko.06.11.00 and roadmap
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
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
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m26s
2026-06-06 20:56:25 -05:00
Jonathan Miller 3a492c5bd5 Merge dev: type settings, MCP SSE, npm workflow
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 29s
2026-06-07 00:53:54 +00:00
Jonathan Miller c947ebcb49 Merge dev: error pages login
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 3m47s
2026-06-06 23:26:52 +00:00
Jonathan Miller eef6292832 Merge dev: 403 OAuth login fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 28s
2026-06-06 23:16:50 +00:00
Jonathan Miller 27aeb19dda Merge dev: changelog + MCP update
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 4s
Deploy MokoGitea / deploy (push) Failing after 3m41s
2026-06-06 22:52:07 +00:00
Jonathan Miller d1b2fca784 Merge dev: wiki updates
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 3m53s
2026-06-06 22:27:01 +00:00
Jonathan Miller 971c5fc7a7 Merge dev: first-class type field + list badges
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 27s
2026-06-06 22:13:49 +00:00
Jonathan Miller cd36065464 Merge dev: dashboard badge fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 43s
2026-06-06 21:56:37 +00:00
Jonathan Miller 0debc72356 Merge dev: security tab
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 30s
2026-06-06 21:36:45 +00:00
Jonathan Miller 1ef6ef5fd4 Merge dev: security scanning platform (#508)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 2m58s
2026-06-06 21:25:52 +00:00
Jonathan Miller 62a44a3668 Merge dev: wiki folder fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 30s
2026-06-06 20:52:27 +00:00
Jonathan Miller 3c456dfe85 Merge dev: wiki dir fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 33s
2026-06-06 20:35:33 +00:00
Jonathan Miller 7b75ce9564 Merge dev: wiki type fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 30s
2026-06-06 20:29:05 +00:00
Jonathan Miller 3abd239397 Merge dev: wiki display fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 31s
2026-06-06 20:25:49 +00:00
Jonathan Miller 1e69927cec Merge dev: wiki slash fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 25s
2026-06-06 20:17:00 +00:00
Jonathan Miller c71e622e11 Merge dev: wiki folder navigation
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 37s
2026-06-06 20:10:33 +00:00
Jonathan Miller 2ba5e42113 Merge dev: wiki updates
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 28s
2026-06-06 19:50:43 +00:00
Jonathan Miller 7240deb822 Merge dev: closed issue permissions fix
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 25s
2026-06-06 19:21:58 +00:00
gitea-actions[bot] 727cff9eb8 chore(release): build 05.50.00 [skip ci] 2026-06-06 19:14:38 +00:00
jmiller 4c715d8424 Merge pull request 'release: v1.26.1-moko.06.07.02' (#529) from rc/v1.26.1-moko.06.07.02 into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 13m45s
2026-06-06 19:13:55 +00:00
gitea-actions[bot] c0acdd1f58 chore(release): build 05.49.00 [skip ci] 2026-06-06 18:43:15 +00:00
jmiller c73109e2e6 Merge pull request 'release: v1.26.1-moko.06.07.01' (#527) from rc/v1.26.1-moko.06.07.01 into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 33s
2026-06-06 18:42:28 +00:00
jmiller 3a159b7da6 Merge pull request 'release: v1.26.1-moko.06.07' (#525) from rc/v1.26.1-moko.06.07 into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 33s
2026-06-06 17:56:51 +00:00
jmiller 7c8b20b779 Merge pull request 'release: v1.26.1-moko.06.06.02' (#522) from rc/v1.26.1-moko.06.06.02 into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 34s
2026-06-06 17:41:13 +00:00
jmiller 4e8af85178 Merge pull request 'release: v1.26.1-moko.06.06.01' (#520) from rc/v1.26.1-moko.06.06.01 into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 27s
2026-06-06 17:30:28 +00:00
jmiller aa1a67c4cb Merge pull request 'release: v1.26.1-moko.06.06' (#518) from rc/v1.26.1-moko.06.06 into main
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 40s
2026-06-06 17:08:02 +00:00
Jonathan Miller 5642057c80 chore: resolve manifest conflict (use RC version)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 29s
2026-06-06 16:25:53 +00:00
Jonathan Miller 4dd27ccdb8 Merge release v1.26.1-moko.06.05.01 (#515) 2026-06-06 16:24:36 +00:00
gitea-actions[bot] 71a7ab04e5 chore(release): build 05.48.00 [skip ci] 2026-06-06 14:50:05 +00:00
jmiller d6dc7533ff Merge pull request 'release: v1.26.1-moko.06.05' (#511) from rc/v1.26.1-moko.06.05 into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 27s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
2026-06-06 14:49:11 +00:00
24 changed files with 1207 additions and 606 deletions
+2 -2
View File
@@ -3,8 +3,8 @@
<identity>
<name>MokoGitea</name>
<org>MokoConsulting</org>
<description>Moko fork of Gitea -- adding project board REST API endpoints and custom enhancements</description>
<version>05.47.00</version>
<description>Moko fork of Gitea adding project board REST API endpoints and custom enhancements</description>
<version>05.51.01</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+1 -324
View File
@@ -1,324 +1 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
# +========================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+
# | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream |
# | |
# +========================================================================+
name: "Universal: Build & Release"
on:
pull_request:
types: [opened, closed]
branches:
- main
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: false
type: choice
default: release
options:
- release
- promote-rc
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions:
contents: write
jobs:
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
promote-rc:
name: Promote to RC
runs-on: release
if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: Rename branch to rc
run: |
php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git
run: |
git fetch origin rc
git checkout rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
name: Build & Release Pipeline
runs-on: release
if: >-
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: |
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found — aborting release"
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "No conflict markers found"
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: "Publish stable release"
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability stable --bump minor --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Update release notes from CHANGELOG.md
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Stable release"
else
NOTES="Stable release"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
- name: "Step 10: Push main to GitHub mirror"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
git fetch origin main --depth=1
git push github origin/main:refs/heads/main --force 2>/dev/null \
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch and recreate dev from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD)
# Delete old version branch if it exists (same version re-release)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
# Create version/XX.YY.ZZ from main
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Post-release: Reset dev version"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
if: always()
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
placeholder
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 05.47.00
# VERSION: 05.51.01
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+245 -243
View File
@@ -1,243 +1,245 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
name: "Universal: Pre-Release"
on:
pull_request:
types: [closed]
branches:
- dev
pull_request_target:
types: [synchronize, opened, reopened]
branches:
- main
workflow_dispatch:
inputs:
stability:
description: 'Pre-release channel'
required: true
type: choice
options:
- development
- alpha
- beta
- release-candidate
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
runs-on: release
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') ||
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: Detect platform
id: platform
run: |
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
run: |
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
STABILITY="release-candidate"
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
case "$STABILITY" in
release-candidate) BUMP="minor" ;;
*) BUMP="patch" ;;
esac
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
# Set stability suffix and verify consistency
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
# Append suffix for output
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Create release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch dev --prerelease
- name: Update release notes from CHANGELOG.md
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
else
NOTES="Release ${VERSION}"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
- name: Build package and upload
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGitea license server
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
--token "${TOKEN}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release"
on:
push:
branches:
- dev
- alpha
- beta
- rc
workflow_dispatch:
inputs:
stability:
description: 'Pre-release channel'
required: true
type: choice
options:
- development
- alpha
- beta
- release-candidate
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release
if: >-
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.ref_name }}
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: Detect platform
id: platform
run: |
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
run: |
# Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then
case "${{ github.ref_name }}" in
rc) STABILITY="release-candidate" ;;
alpha) STABILITY="alpha" ;;
beta) STABILITY="beta" ;;
*) STABILITY="development" ;;
esac
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
case "$STABILITY" in
release-candidate) BUMP="minor" ;;
*) BUMP="patch" ;;
esac
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
# Set stability suffix and verify consistency
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
# Append suffix for output
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Create release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
else
NOTES="Release ${VERSION}"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
- name: Build package and upload
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGitea license server
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
--token "${TOKEN}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
+19 -23
View File
@@ -3,6 +3,24 @@
All notable changes to MokoGitea are documented here. Versions follow the format
`v{upstream}-moko.{major}.{minor}` (e.g. `v1.26.1-moko.06.03`).
## [v1.26.1-moko.06.12] - 2026-06-07
* FEATURES
* feat(security): dependency vulnerability scanner - parses go.mod, package.json, composer.json, requirements.txt and checks against OSV.dev API (#551)
* feat(cdn): built-in CDN for release asset delivery via cdn.mokoconsulting.tech with per-asset public/private toggles (#561)
* feat(cdn): IP/CIDR and referrer domain allowlists for CDN abuse prevention
* feat(cdn): releases in update streams excluded from CDN (update server takes precedence)
* FIXES
* fix(licensing): hide "Require license key" option for Joomla update servers (Joomla limitation)
* fix(settings): remove duplicate description from manifest page (#559)
* INFRASTRUCTURE
* chore: rename moko-platform to MokoPlatform across codebase (#548)
* CDN CNAME: cdn.mokoconsulting.tech with auto-TLS via Let's Encrypt
* Nginx reverse proxy for CDN hostname on production server
* DreamHost MCP server path and API key configured
## [v1.26.1-moko.06.10] - 2026-06-06
* FEATURES
@@ -38,7 +56,7 @@ All notable changes to MokoGitea are documented here. Versions follow the format
* INFRASTRUCTURE
* npm: @mokoconsulting/mokogitea-mcp@1.1.0 and @mokoconsulting/mokowaas-mcp@1.0.0
* MCP servers consolidated under moko-platform/mcp/servers/
* MCP servers consolidated under mokoplatform/mcp/servers/
* Remote MCP repos renamed to hyphens
* Wiki restructured into features/, api/, operations/ folders
* Swagger API docs enabled at /api/swagger
@@ -205,25 +223,3 @@ All notable changes to MokoGitea are documented here. Versions follow the format
* Reopened 9 closed issues lacking documented testing proof
* Created `pending: testing` label for features awaiting verification
* Established policy: issues must not be closed without documented testing proof
## [1.26.1](https://github.com/go-gitea/gitea/releases/tag/v1.26.1) - 2026-04-21
* BUGFIXES
* Add event.schedule context for schedule actions task (#37320) (#37348)
* Fix an issue where changing an organization's visibility caused problems when users had forked its repositories. (#37324) (#37344)
* Use modern "git update-index --cacheinfo" syntax to support more file names (#37338) (#37343)
* Fix URL related escaping for oauth2 (#37334) (#37340)
* When the requested arch rpm is missing fall back to noarch (#37236) (#37339)
* Fix actions concurrency groups cross-branch leak (#37311) (#37331)
* Fix bug when accessing user badges (#37321) (#37329)
* Fix AppFullLink (#37325) (#37328)
* Fix container auth for public instance (#37290) (#37294)
* Enhance GetActionWorkflow to support fallback references (#37189) (#37283)
* Fix vite manifest update masking build errors (#37279) (#37310)
* Fix Mermaid diagrams failing when node labels contain line breaks (#37296) (#37299)
* Use TriggerEvent instead of Event in workflow runs API response for scheduled runs (#37288) #37360
* Add URL to Learn more about blocking a user. (#37355) #37367
* Fix button layout shift when collapsing file tree in editor (#37363) #37375
* Fix org team assignee/reviewer lookups for team member permissions (#37365) #37391
* Fix repo init README EOL (#37388) #37399
* Fix: dump with default zip type produces uncompressed zip (#37401)#37402
+2 -2
View File
@@ -18,7 +18,7 @@ Custom Gitea fork with Project Board API
---
**Category:** Infrastructure | **Platform:** [moko-platform wiki](https://code.mokoconsulting.tech/MokoConsulting/moko-platform/wiki)
**Category:** Infrastructure | **Platform:** [MokoPlatform wiki](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki)
---
@@ -40,4 +40,4 @@ This project is licensed under the GNU General Public License v3.0 or later -- s
---
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://code.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://code.mokoconsulting.tech/MokoConsulting/MokoPlatform/wiki/Home)*
+1
View File
@@ -428,6 +428,7 @@ func prepareMigrationTasks() []*migration {
newMigration(348, "Add issue priority definitions table", v1_27.AddIssuePriorityDefTable),
newMigration(349, "Add security scanning tables", v1_27.AddSecurityScanningTables),
newMigration(350, "Add issue type definitions table", v1_27.AddIssueTypeDefTable),
newMigration(351, "Add CDN public flag to attachments", v1_27.AddAttachmentCDNPublic),
}
return preparedMigrations
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// AddAttachmentCDNPublic adds the cdn_public column to the attachment table.
func AddAttachmentCDNPublic(x *xorm.Engine) error {
type Attachment struct {
CDNPublic bool `xorm:"NOT NULL DEFAULT false 'cdn_public'"`
}
return x.Sync(new(Attachment))
}
+1
View File
@@ -31,6 +31,7 @@ type Attachment struct {
Name string
DownloadCount int64 `xorm:"DEFAULT 0"`
Size int64 `xorm:"DEFAULT 0"`
CDNPublic bool `xorm:"NOT NULL DEFAULT false 'cdn_public'"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
CustomDownloadURL string `xorm:"-"`
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
import "time"
// CDN holds configuration for the built-in CDN asset delivery system.
var CDN = struct {
Enabled bool
Domain string // e.g. "cdn.mokoconsulting.tech"
CacheTTL time.Duration // Cache-Control max-age for CDN responses
AllowedOrigins []string // CORS origins allowed to fetch CDN assets
AllowedIPs []string // IP/CIDR allowlist (empty = allow all)
AllowedDomains []string // Referrer domain allowlist (empty = allow all)
MaxFileSize int64 // max file size to serve (bytes)
}{
Enabled: false,
Domain: "",
CacheTTL: 24 * time.Hour,
MaxFileSize: 100 * 1024 * 1024, // 100MB
}
func loadCDNFrom(cfg ConfigProvider) {
sec := cfg.Section("cdn")
CDN.Enabled = sec.Key("ENABLED").MustBool(false)
CDN.Domain = sec.Key("DOMAIN").String()
CDN.CacheTTL = sec.Key("CACHE_TTL").MustDuration(CDN.CacheTTL)
CDN.MaxFileSize = sec.Key("MAX_FILE_SIZE").MustInt64(CDN.MaxFileSize)
CDN.AllowedOrigins = sec.Key("ALLOWED_ORIGINS").Strings(",")
CDN.AllowedIPs = sec.Key("ALLOWED_IPS").Strings(",")
CDN.AllowedDomains = sec.Key("ALLOWED_DOMAINS").Strings(",")
}
+1
View File
@@ -178,6 +178,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
loadOtherFrom(cfg)
loadUpdateCheckerFrom(cfg)
loadNtfyFrom(cfg)
loadCDNFrom(cfg)
loadLoginNotificationFrom(cfg)
return nil
}
+3 -1
View File
@@ -2734,7 +2734,7 @@
"repo.settings.support_url_help": "Shown when downloads are gated. Can point to your wiki, product page, or external support site.",
"repo.settings.custom_fields": "Custom Fields",
"repo.settings.manifest": "Manifest",
"repo.settings.manifest_desc": "Project identity, governance, and build settings from the moko-platform manifest. These are accessible via API for Actions workflows and the moko-platform CLI.",
"repo.settings.manifest_desc": "Project identity, governance, and build settings from the MokoPlatform manifest. These are accessible via API for Actions workflows and the MokoPlatform CLI.",
"repo.settings.manifest_identity": "Identity",
"repo.settings.manifest_name": "Project Name",
"repo.settings.manifest_org": "Organization",
@@ -2831,6 +2831,8 @@
"repo.release.message": "Describe this release",
"repo.release.prerelease_desc": "Mark as Pre-Release",
"repo.release.prerelease_helper": "Mark this release unsuitable for production use.",
"repo.release.cdn_public": "CDN",
"repo.release.cdn_public_tooltip": "Make this asset available via the CDN. Disabled when the release is assigned to an update stream.",
"repo.release.cancel": "Cancel",
"repo.release.publish": "Publish Release",
"repo.release.save_draft": "Save Draft",
+278
View File
@@ -0,0 +1,278 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"fmt"
"net"
"net/http"
"strings"
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/httplib"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
)
// CDNHandler serves release assets via the CDN hostname.
// URL format: /:owner/:repo/releases/:tag/:filename
// Only assets with cdn_public=true are served.
func CDNHandler(w http.ResponseWriter, req *http.Request) {
if !setting.CDN.Enabled {
http.NotFound(w, req)
return
}
if !cdnCheckIPAllowed(req) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
if !cdnCheckReferrerAllowed(req) {
http.Error(w, "Forbidden: referrer not allowed", http.StatusForbidden)
return
}
// Parse: /:owner/:repo/releases/:tag/:filename
urlPath := strings.TrimPrefix(req.URL.Path, "/")
parts := strings.SplitN(urlPath, "/", 6)
// Minimum: owner/repo/releases/tag/filename = 5 parts
if len(parts) < 5 || parts[2] != "releases" {
http.Error(w, "Not Found: expected /:owner/:repo/releases/:tag/:filename", http.StatusNotFound)
return
}
ownerName := parts[0]
repoName := parts[1]
tagName := parts[3]
fileName := parts[4]
// Allow filenames with slashes (parts[5] if present)
if len(parts) == 6 {
fileName = parts[4] + "/" + parts[5]
}
// Load repository
repo, err := repo_model.GetRepositoryByOwnerAndName(req.Context(), ownerName, repoName)
if err != nil {
if repo_model.IsErrRepoNotExist(err) {
http.NotFound(w, req)
} else {
log.Error("CDN: GetRepositoryByOwnerAndName %s/%s: %v", ownerName, repoName, err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
return
}
// Look up the release by tag
release, err := repo_model.GetRelease(req.Context(), repo.ID, tagName)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
http.NotFound(w, req)
} else {
log.Error("CDN: GetRelease %s/%s tag=%s: %v", ownerName, repoName, tagName, err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
return
}
// Don't serve draft releases via CDN
if release.IsDraft {
http.NotFound(w, req)
return
}
// If the release is assigned to an update stream, CDN is disabled -
// the update server handles distribution for streamed releases.
if stream := licenses_model.GetReleaseStream(req.Context(), release.ID); stream != "" {
http.Error(w, "Forbidden: release is served via update stream", http.StatusForbidden)
return
}
// Find the specific attachment by filename
attach, err := repo_model.GetAttachmentByReleaseIDFileName(req.Context(), release.ID, fileName)
if err != nil || attach == nil {
http.NotFound(w, req)
return
}
// Only serve assets marked as CDN public
if !attach.CDNPublic {
http.Error(w, "Forbidden: asset is not CDN-enabled", http.StatusForbidden)
return
}
// Check file size limit
if setting.CDN.MaxFileSize > 0 && attach.Size > setting.CDN.MaxFileSize {
http.Error(w, "File too large for CDN delivery", http.StatusRequestEntityTooLarge)
return
}
// CORS headers
if len(setting.CDN.AllowedOrigins) > 0 {
origin := req.Header.Get("Origin")
for _, allowed := range setting.CDN.AllowedOrigins {
if allowed == "*" || allowed == origin {
w.Header().Set("Access-Control-Allow-Origin", allowed)
break
}
}
} else {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS")
w.Header().Set("Access-Control-Max-Age", "86400")
if req.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
// ETag based on attachment UUID (immutable for same content)
etag := `"` + attach.UUID + `"`
w.Header().Set("Etag", etag)
// 304 Not Modified check
if inm := req.Header.Get("If-None-Match"); inm != "" {
for item := range strings.SplitSeq(inm, ",") {
item = strings.TrimPrefix(strings.TrimSpace(item), "W/")
if item == etag {
w.WriteHeader(http.StatusNotModified)
return
}
}
}
// Last-Modified
lastModified := attach.CreatedUnix.AsTimePtr()
if lastModified != nil {
w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat))
}
// CDN cache headers
cacheTTL := int(setting.CDN.CacheTTL.Seconds())
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, no-transform", cacheTTL))
// Increment download count
if err := attach.IncreaseDownloadCount(req.Context()); err != nil {
log.Error("CDN: IncreaseDownloadCount: %v", err)
}
// Try direct storage URL (S3/object storage)
if setting.Attachment.Storage.ServeDirect() {
u, err := storage.Attachments.ServeDirectURL(attach.RelativePath(), attach.Name, req.Method, nil)
if u != nil && err == nil {
http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
return
}
}
// Serve from local storage
fr, err := storage.Attachments.Open(attach.RelativePath())
if err != nil {
log.Error("CDN: storage.Open %s: %v", attach.RelativePath(), err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
defer fr.Close()
httplib.ServeUserContentByFile(req, w, fr, httplib.ServeHeaderOptions{
Filename: attach.Name,
CacheIsPublic: true,
CacheDuration: setting.CDN.CacheTTL,
})
}
// cdnCheckIPAllowed checks if the request IP is in the configured allowlist.
func cdnCheckIPAllowed(req *http.Request) bool {
if len(setting.CDN.AllowedIPs) == 0 {
return true
}
remoteIP := cdnGetRemoteIP(req)
if remoteIP == nil {
return false
}
for _, cidr := range setting.CDN.AllowedIPs {
cidr = strings.TrimSpace(cidr)
if cidr == "" {
continue
}
if !strings.Contains(cidr, "/") {
if remoteIP.Equal(net.ParseIP(cidr)) {
return true
}
continue
}
_, network, err := net.ParseCIDR(cidr)
if err != nil {
log.Warn("CDN: invalid CIDR in AllowedIPs: %s", cidr)
continue
}
if network.Contains(remoteIP) {
return true
}
}
return false
}
// cdnCheckReferrerAllowed checks if the request referrer domain is allowed.
func cdnCheckReferrerAllowed(req *http.Request) bool {
if len(setting.CDN.AllowedDomains) == 0 {
return true
}
referer := req.Header.Get("Referer")
if referer == "" {
return true // direct requests always allowed
}
for _, domain := range setting.CDN.AllowedDomains {
domain = strings.TrimSpace(strings.ToLower(domain))
if domain == "" {
continue
}
if domain == "*" {
return true
}
refLower := strings.ToLower(referer)
if strings.Contains(refLower, "://"+domain+"/") || strings.Contains(refLower, "://"+domain+":") ||
strings.HasSuffix(refLower, "://"+domain) {
return true
}
if strings.HasPrefix(domain, "*.") {
baseDomain := domain[2:]
if strings.Contains(refLower, "."+baseDomain+"/") || strings.Contains(refLower, "."+baseDomain+":") ||
strings.HasSuffix(refLower, "."+baseDomain) {
return true
}
}
}
return false
}
// cdnGetRemoteIP extracts the client IP, checking proxy headers.
func cdnGetRemoteIP(req *http.Request) net.IP {
if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
parts := strings.SplitN(xff, ",", 2)
if ip := net.ParseIP(strings.TrimSpace(parts[0])); ip != nil {
return ip
}
}
if xri := req.Header.Get("X-Real-IP"); xri != "" {
if ip := net.ParseIP(strings.TrimSpace(xri)); ip != nil {
return ip
}
}
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return net.ParseIP(req.RemoteAddr)
}
return net.ParseIP(host)
}
+26 -1
View File
@@ -596,7 +596,10 @@ func EditRelease(ctx *context.Context) {
ctx.Data["content"] = rel.Note
ctx.Data["prerelease"] = rel.IsPrerelease
ctx.Data["IsDraft"] = rel.IsDraft
ctx.Data["ReleaseStream"] = licenses_model.GetReleaseStream(ctx, rel.ID)
releaseStream := licenses_model.GetReleaseStream(ctx, rel.ID)
ctx.Data["ReleaseStream"] = releaseStream
ctx.Data["ReleaseHasStream"] = releaseStream != ""
ctx.Data["CDNEnabled"] = setting.CDN.Enabled
rel.Repo = ctx.Repo.Repository
if err := rel.LoadAttributes(ctx); err != nil {
@@ -683,6 +686,28 @@ func EditReleasePost(ctx *context.Context) {
} else {
_ = licenses_model.DeleteReleaseStream(ctx, rel.ID)
}
// Update per-asset CDN visibility flags.
if setting.CDN.Enabled {
const cdnPrefix = "attachment-cdn-"
cdnUUIDs := make(map[string]bool)
for k := range ctx.Req.Form {
if strings.HasPrefix(k, cdnPrefix) {
cdnUUIDs[k[len(cdnPrefix):]] = true
}
}
// Load all attachments for this release to update cdn_public
if err := repo_model.GetReleaseAttachments(ctx, rel); err == nil {
for _, attach := range rel.Attachments {
wantCDN := cdnUUIDs[attach.UUID]
if attach.CDNPublic != wantCDN {
attach.CDNPublic = wantCDN
_ = repo_model.UpdateAttachmentByUUID(ctx, attach, "cdn_public")
}
}
}
}
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
}
+1 -1
View File
@@ -88,7 +88,7 @@ func ManifestSettingsPost(ctx *context.Context) {
RepoID: ctx.Repo.Repository.ID,
Name: ctx.FormString("name"),
Org: ctx.FormString("org"),
Description: ctx.FormString("description"),
Description: ctx.Repo.Repository.Description,
Version: ctx.FormString("version"),
LicenseSPDX: ctx.FormString("license_spdx"),
LicenseName: ctx.FormString("license_name"),
+14
View File
@@ -260,6 +260,20 @@ func Routes() *web.Router {
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
routes.BeforeRouting(chi_middleware.GetHead)
// CDN hostname handler - intercepts requests on the CDN domain before any
// session/auth middleware runs, serving only CDN-public release assets.
if setting.CDN.Enabled && setting.CDN.Domain != "" {
routes.BeforeRouting(func(resp http.ResponseWriter, req *http.Request) {
host := req.Host
if idx := strings.Index(host, ":"); idx >= 0 {
host = host[:idx]
}
if strings.EqualFold(host, setting.CDN.Domain) {
repo.CDNHandler(resp, req)
}
})
}
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
routes.Methods("GET, HEAD", "/assets/site-manifest.json", misc.SiteManifest)
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", routing.MarkLogLevelTrace, public.AssetsCors(), public.FileHandlerFunc())
+541
View File
@@ -0,0 +1,541 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package security
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
security_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/security"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
)
// ──────────────────────────────────────────────────────────────────────
// Dependency manifest parsers
// ──────────────────────────────────────────────────────────────────────
// dependency represents a single package with version.
type dependency struct {
Name string
Version string
Ecosystem string // "Go", "npm", "PyPI", "Packagist"
FilePath string // which manifest file it came from
}
// manifestParser extracts dependencies from a file's contents.
type manifestParser struct {
FileName string
Ecosystem string
Parse func(content string, filePath string) []dependency
}
var manifestParsers = []manifestParser{
{"go.mod", "Go", parseGoMod},
{"package.json", "npm", parsePackageJSON},
{"composer.json", "Packagist", parseComposerJSON},
{"requirements.txt", "PyPI", parseRequirementsTxt},
}
// parseGoMod extracts dependencies from go.mod.
func parseGoMod(content, filePath string) []dependency {
var deps []dependency
inRequire := false
for _, line := range strings.Split(content, "\n") {
line = strings.TrimSpace(line)
if line == ")" {
inRequire = false
continue
}
if strings.HasPrefix(line, "require (") || strings.HasPrefix(line, "require(") {
inRequire = true
continue
}
if inRequire {
// Lines like: github.com/foo/bar v1.2.3
parts := strings.Fields(line)
if len(parts) >= 2 && !strings.HasPrefix(parts[0], "//") {
deps = append(deps, dependency{
Name: parts[0],
Version: parts[1],
Ecosystem: "Go",
FilePath: filePath,
})
}
continue
}
// Single-line require: require github.com/foo/bar v1.2.3
if strings.HasPrefix(line, "require ") && !strings.Contains(line, "(") {
parts := strings.Fields(line)
if len(parts) >= 3 {
deps = append(deps, dependency{
Name: parts[1],
Version: parts[2],
Ecosystem: "Go",
FilePath: filePath,
})
}
}
}
return deps
}
// parsePackageJSON extracts dependencies from package.json.
func parsePackageJSON(content, filePath string) []dependency {
var pkg struct {
Dependencies map[string]string `json:"dependencies"`
DevDependencies map[string]string `json:"devDependencies"`
}
if err := json.Unmarshal([]byte(content), &pkg); err != nil {
return nil
}
var deps []dependency
for name, version := range pkg.Dependencies {
deps = append(deps, dependency{
Name: name,
Version: cleanSemver(version),
Ecosystem: "npm",
FilePath: filePath,
})
}
for name, version := range pkg.DevDependencies {
deps = append(deps, dependency{
Name: name,
Version: cleanSemver(version),
Ecosystem: "npm",
FilePath: filePath,
})
}
return deps
}
// parseComposerJSON extracts dependencies from composer.json.
func parseComposerJSON(content, filePath string) []dependency {
var pkg struct {
Require map[string]string `json:"require"`
RequireDev map[string]string `json:"require-dev"`
}
if err := json.Unmarshal([]byte(content), &pkg); err != nil {
return nil
}
var deps []dependency
for name, version := range pkg.Require {
if name == "php" || strings.HasPrefix(name, "ext-") {
continue // skip platform requirements
}
deps = append(deps, dependency{
Name: name,
Version: cleanSemver(version),
Ecosystem: "Packagist",
FilePath: filePath,
})
}
for name, version := range pkg.RequireDev {
if name == "php" || strings.HasPrefix(name, "ext-") {
continue
}
deps = append(deps, dependency{
Name: name,
Version: cleanSemver(version),
Ecosystem: "Packagist",
FilePath: filePath,
})
}
return deps
}
// parseRequirementsTxt extracts dependencies from requirements.txt.
func parseRequirementsTxt(content, filePath string) []dependency {
var deps []dependency
for _, line := range strings.Split(content, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "-") {
continue
}
// Handle: package==1.0.0, package>=1.0.0, package~=1.0.0
for _, sep := range []string{"==", ">=", "~=", "<=", "!="} {
if idx := strings.Index(line, sep); idx > 0 {
name := strings.TrimSpace(line[:idx])
version := strings.TrimSpace(line[idx+len(sep):])
// Strip any trailing constraints like ",<2.0"
if ci := strings.Index(version, ","); ci > 0 {
version = version[:ci]
}
deps = append(deps, dependency{
Name: name,
Version: version,
Ecosystem: "PyPI",
FilePath: filePath,
})
break
}
}
}
return deps
}
// cleanSemver strips npm/composer range prefixes (^, ~, >=) to get a plain version.
func cleanSemver(v string) string {
v = strings.TrimSpace(v)
v = strings.TrimLeft(v, "^~>=<!")
v = strings.TrimSpace(v)
// If it has " || " or " - " (ranges), take the first version
if idx := strings.Index(v, " "); idx > 0 {
v = v[:idx]
}
return v
}
// ──────────────────────────────────────────────────────────────────────
// OSV.dev API client
// ──────────────────────────────────────────────────────────────────────
const osvBatchURL = "https://api.osv.dev/v1/querybatch"
const osvMaxBatch = 1000 // OSV batch limit
var osvClient = &http.Client{Timeout: 30 * time.Second}
// osvQuery is a single query in a batch request.
type osvQuery struct {
Package *osvPackage `json:"package"`
Version string `json:"version"`
}
type osvPackage struct {
Name string `json:"name"`
Ecosystem string `json:"ecosystem"`
}
// osvBatchRequest is the batch query body.
type osvBatchRequest struct {
Queries []osvQuery `json:"queries"`
}
// osvBatchResponse is the batch response.
type osvBatchResponse struct {
Results []osvResult `json:"results"`
}
type osvResult struct {
Vulns []osvVuln `json:"vulns"`
}
type osvVuln struct {
ID string `json:"id"`
Summary string `json:"summary"`
Details string `json:"details"`
Severity []osvSeverity `json:"severity"`
Aliases []string `json:"aliases"`
}
type osvSeverity struct {
Type string `json:"type"` // "CVSS_V3", "CVSS_V2"
Score string `json:"score"` // CVSS vector string
}
// queryOSV sends a batch of dependencies to OSV.dev and returns vulnerabilities.
func queryOSV(deps []dependency) (*osvBatchResponse, error) {
queries := make([]osvQuery, 0, len(deps))
for _, d := range deps {
if d.Version == "" || d.Version == "*" || d.Version == "latest" {
continue // can't query without a concrete version
}
queries = append(queries, osvQuery{
Package: &osvPackage{Name: d.Name, Ecosystem: d.Ecosystem},
Version: d.Version,
})
}
if len(queries) == 0 {
return &osvBatchResponse{}, nil
}
body, err := json.Marshal(osvBatchRequest{Queries: queries})
if err != nil {
return nil, fmt.Errorf("marshal OSV request: %w", err)
}
resp, err := osvClient.Post(osvBatchURL, "application/json", bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("OSV API request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return nil, fmt.Errorf("OSV API returned %d: %s", resp.StatusCode, string(respBody))
}
var result osvBatchResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("decode OSV response: %w", err)
}
return &result, nil
}
// ──────────────────────────────────────────────────────────────────────
// Severity mapping
// ──────────────────────────────────────────────────────────────────────
// mapCVSSSeverity converts a CVSS v3 base score to an AlertSeverity.
func mapCVSSSeverity(vulnSeverities []osvSeverity) security_model.AlertSeverity {
for _, s := range vulnSeverities {
if s.Type == "CVSS_V3" {
score := extractCVSSBaseScore(s.Score)
switch {
case score >= 9.0:
return security_model.SeverityCritical
case score >= 7.0:
return security_model.SeverityHigh
case score >= 4.0:
return security_model.SeverityMedium
case score > 0:
return security_model.SeverityLow
}
}
}
// No CVSS score available - default to medium
return security_model.SeverityMedium
}
// extractCVSSBaseScore parses the base score from a CVSS v3 vector string.
// Vector format: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
// We compute a simplified score from the vector metrics.
func extractCVSSBaseScore(vector string) float64 {
if vector == "" {
return 0
}
// CVSS v3 vectors encode severity in metrics. Use a simplified
// lookup based on the most impactful metrics.
parts := make(map[string]string)
for _, segment := range strings.Split(vector, "/") {
kv := strings.SplitN(segment, ":", 2)
if len(kv) == 2 {
parts[kv[0]] = kv[1]
}
}
// Simplified scoring based on key CVSS v3 metrics
var score float64
// Attack Vector (AV)
switch parts["AV"] {
case "N": // Network
score += 3.0
case "A": // Adjacent
score += 2.0
case "L": // Local
score += 1.0
case "P": // Physical
score += 0.5
}
// Attack Complexity (AC)
switch parts["AC"] {
case "L": // Low
score += 1.5
case "H": // High
score += 0.5
}
// Privileges Required (PR)
switch parts["PR"] {
case "N": // None
score += 1.5
case "L": // Low
score += 1.0
case "H": // High
score += 0.5
}
// Impact metrics (C/I/A)
for _, metric := range []string{"C", "I", "A"} {
switch parts[metric] {
case "H":
score += 1.2
case "L":
score += 0.5
}
}
// Cap at 10.0
if score > 10.0 {
score = 10.0
}
return score
}
// ──────────────────────────────────────────────────────────────────────
// DependencyScanner
// ──────────────────────────────────────────────────────────────────────
// DependencyScanner checks project dependencies against known vulnerabilities.
type DependencyScanner struct{}
// NewDependencyScanner creates a new dependency vulnerability scanner.
func NewDependencyScanner() *DependencyScanner {
return &DependencyScanner{}
}
func (s *DependencyScanner) Type() security_model.ScannerType {
return security_model.ScannerDependency
}
func (s *DependencyScanner) ScanCommit(commit *git.Commit) ([]Finding, error) {
return s.ScanTree(commit)
}
func (s *DependencyScanner) ScanTree(commit *git.Commit) ([]Finding, error) {
if commit == nil {
return nil, nil
}
// Step 1: Find and parse manifest files
entries, err := commit.ListEntriesRecursiveFast()
if err != nil {
return nil, fmt.Errorf("ListEntriesRecursiveFast: %w", err)
}
var allDeps []dependency
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
path := entry.Name()
baseName := path
if idx := strings.LastIndex(path, "/"); idx >= 0 {
baseName = path[idx+1:]
}
// Skip vendored/nested files
lower := strings.ToLower(path)
if strings.Contains(lower, "vendor/") || strings.Contains(lower, "node_modules/") ||
strings.Contains(lower, "testdata/") {
continue
}
for _, parser := range manifestParsers {
if baseName == parser.FileName {
reader, err := entry.Blob().DataAsync()
if err != nil {
log.Trace("DependencyScanner: skip %s: %v", path, err)
continue
}
content, err := io.ReadAll(io.LimitReader(reader, 5*1024*1024)) // 5MB limit
reader.Close()
if err != nil {
continue
}
deps := parser.Parse(string(content), path)
allDeps = append(allDeps, deps...)
break
}
}
}
if len(allDeps) == 0 {
return nil, nil
}
log.Info("DependencyScanner: found %d dependencies across manifest files", len(allDeps))
// Step 2: Query OSV in batches
var findings []Finding
for i := 0; i < len(allDeps); i += osvMaxBatch {
end := i + osvMaxBatch
if end > len(allDeps) {
end = len(allDeps)
}
batch := allDeps[i:end]
resp, err := queryOSV(batch)
if err != nil {
log.Error("DependencyScanner: OSV query failed: %v", err)
continue
}
// Step 3: Map results to findings
// OSV batch response indices correspond 1:1 with the query indices.
// But we may have skipped deps with empty versions, so build the
// queryable subset to align indices.
queryable := make([]dependency, 0, len(batch))
for _, d := range batch {
if d.Version != "" && d.Version != "*" && d.Version != "latest" {
queryable = append(queryable, d)
}
}
for j, result := range resp.Results {
if j >= len(queryable) {
break
}
dep := queryable[j]
for _, vuln := range result.Vulns {
severity := mapCVSSSeverity(vuln.Severity)
// Build CVE alias for rule ID (prefer CVE over GHSA)
ruleID := vuln.ID
for _, alias := range vuln.Aliases {
if strings.HasPrefix(alias, "CVE-") {
ruleID = alias
break
}
}
title := fmt.Sprintf("%s in %s@%s", ruleID, dep.Name, dep.Version)
description := vuln.Summary
if description == "" {
description = vuln.Details
}
// Truncate long descriptions
if len(description) > 500 {
description = description[:497] + "..."
}
// Metadata JSON
meta, _ := json.Marshal(map[string]string{
"vuln_id": vuln.ID,
"ecosystem": dep.Ecosystem,
"package": dep.Name,
"version": dep.Version,
})
fingerprint := fmt.Sprintf("%x", sha256.Sum256([]byte(vuln.ID+":"+dep.Name+":"+dep.Version)))
findings = append(findings, Finding{
Scanner: security_model.ScannerDependency,
Severity: severity,
RuleID: ruleID,
Title: title,
Description: description,
FilePath: dep.FilePath,
CommitSHA: commit.ID.String(),
Fingerprint: fingerprint[:32],
Metadata: string(meta),
})
}
}
}
return findings, nil
}
+3 -1
View File
@@ -32,8 +32,10 @@ func ScanOnPush(ctx context.Context, repo *repo_model.Repository, commit *git.Co
if cfg.SecretScanner {
scanners = append(scanners, NewSecretScanner())
}
if cfg.DependScanner {
scanners = append(scanners, NewDependencyScanner())
}
// Future scanners added here:
// if cfg.DependScanner { scanners = append(scanners, NewDependencyScanner()) }
// if cfg.CodeScanner { scanners = append(scanners, NewCodeScanner()) }
if len(scanners) == 0 {
@@ -19,6 +19,7 @@
<p class="help">{{ctx.Locale.Tr "org.settings.enable_licensing_help"}}</p>
</div>
{{if and (ne .StreamConfig.Platform "joomla") (ne .StreamConfig.Platform "both") (ne .StreamConfig.Platform "")}}
<div class="inline field">
<div class="ui checkbox">
<input name="require_key" type="checkbox" {{if .StreamConfig.RequireKey}}checked{{end}}>
@@ -26,6 +27,7 @@
</div>
<p class="help">{{ctx.Locale.Tr "org.settings.require_key_help"}}</p>
</div>
{{end}}
<div class="field">
<label>{{ctx.Locale.Tr "org.settings.feed_visibility"}}</label>
+6
View File
@@ -86,6 +86,12 @@
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}">
{{svg "octicon-info"}}
</span>
{{if $.CDNEnabled}}
<label class="tw-flex tw-items-center tw-gap-1 tw-ml-2" data-tooltip-content="{{ctx.Locale.Tr "repo.release.cdn_public_tooltip"}}">
<input type="checkbox" name="attachment-cdn-{{.UUID}}" {{if .CDNPublic}}checked{{end}} {{if $.ReleaseHasStream}}disabled{{end}}>
<span class="tw-text-text-light tw-text-xs">{{ctx.Locale.Tr "repo.release.cdn_public"}}</span>
</label>
{{end}}
</div>
<a class="ui mini compact red button" data-global-click="onReleaseEditAttachmentDelete" data-id="{{.ID}}" data-uuid="{{.UUID}}">
{{ctx.Locale.Tr "remove"}}
+3 -1
View File
@@ -31,13 +31,15 @@
<p class="help">{{ctx.Locale.Tr "repo.settings.update_platform_help"}}</p>
</div>
{{if and .RepoUpdateConfig (ne .RepoUpdateConfig.Platform "joomla") (ne .RepoUpdateConfig.Platform "both") (ne .RepoUpdateConfig.Platform "")}}
<div class="inline field">
<div class="ui checkbox">
<input name="require_update_key" type="checkbox" {{if and .RepoUpdateConfig .RepoUpdateConfig.RequireKey}}checked{{end}}>
<input name="require_update_key" type="checkbox" {{if .RepoUpdateConfig.RequireKey}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.require_update_key"}}</label>
</div>
<p class="help">{{ctx.Locale.Tr "repo.settings.require_update_key_help"}}</p>
</div>
{{end}}
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.settings.download_gating"}}</label>
-4
View File
@@ -19,10 +19,6 @@
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
</div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.manifest_description"}}</label>
<input name="description" value="{{.Manifest.Description}}" placeholder="Project description">
</div>
<div class="three fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.manifest_version"}}</label>
+1 -1
View File
@@ -7,7 +7,7 @@ Moko Consulting's custom fork of [Gitea](https://gitea.com), extending the self-
| **Language** | Go |
| **License** | MIT |
| **Upstream** | Gitea 1.26.1 |
| **Version** | v1.26.1-moko.06.10.00 |
| **Version** | v1.26.1-moko.06.11.01 |
| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) |
---
+8 -1
View File
@@ -1,6 +1,6 @@
# MokoGitea Roadmap
## Recently Completed (v1.26.1-moko.06.10)
## Recently Completed (v1.26.1-moko.06.11)
- First-class Type field (12 types) replacing labels and custom fields
- First-class Status field (13 statuses) with auto close/reopen
@@ -15,11 +15,18 @@
- MCP server published to npm (@mokoconsulting/mokogitea-mcp) with SSE transport
- Dashboard issue count badges fixed
- Status dropdown replaces close/reopen button
- Org settings page for Issue Types
- MCP SSE endpoint hosted at git.mokoconsulting.tech/mcp/
- npm auto-publish workflow on MCP source changes
- OAuth providers on 403/404 error pages
- All stale branches cleaned up (main + dev only)
## In Progress
- Rename moko-platform to MokoPlatform
- Granular role-based permissions for all features (#9)
- Wire moko-platform CLI to manifest API (#505)
- Bulk migrate remaining 41 flat wikis to folders
## Planned