Compare commits

...

117 Commits

Author SHA1 Message Date
jmiller 506e4a49d6 feat(metadata): admin-configurable platform dropdown options
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 29s
Generic: Project CI / Tests (pull_request) Successful in 29s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Universal: PR Check / Secret Scan (pull_request) Successful in 44s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The repo metadata 'platform' field was a hardcoded enum; changing the
taxonomy meant a code change + redeploy. Make it admin-configurable:

- modules/setting/metadata.go: new [metadata] PLATFORM_OPTIONS setting
  (default joomla,dolibarr,go,npm,generic), loaded in loadCommonSettingsFrom
- routers/web/admin/metadata.go + templates/admin/metadata.tmpl: new
  Admin -> Metadata page to edit the list (persisted to app.ini), with a
  nav entry and route
- routers/web/repo/setting/metadata.go + template: platform dropdown now
  reads from the setting; a repo's current value stays selectable even if
  removed from the list (no silent drop)

Closes #777
2026-07-13 17:27:03 -05:00
jmiller dd801ccce5 Merge pull request 'chore(sync): cascade main -> dev' (#781) from main into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m15s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 3m29s
2026-07-13 21:36:16 +00:00
jmiller b5d03ab769 Merge pull request 'chore(ci): move deploy workflows out of custom/ to root' (#780) from chore/deploy-out-of-custom into main
Generic: Standards Compliance / Secret Scanning (push) Has been cancelled
Generic: Standards Compliance / License Header Validation (push) Has been cancelled
Generic: Standards Compliance / Repository Structure Validation (push) Has been cancelled
Generic: Standards Compliance / Coding Standards Check (push) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (push) Has been cancelled
Generic: Standards Compliance / Workflow Configuration Check (push) Has been cancelled
Generic: Standards Compliance / Documentation Quality Check (push) Has been cancelled
Generic: Standards Compliance / README Completeness Check (push) Has been cancelled
Generic: Standards Compliance / Git Repository Hygiene (push) Has been cancelled
Generic: Standards Compliance / Script Integrity Validation (push) Has been cancelled
Generic: Standards Compliance / Line Length Check (push) Has been cancelled
Generic: Standards Compliance / File Naming Standards (push) Has been cancelled
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Has been cancelled
Generic: Standards Compliance / Code Complexity Analysis (push) Has been cancelled
Generic: Standards Compliance / Code Duplication Detection (push) Has been cancelled
Generic: Standards Compliance / Dead Code Detection (push) Has been cancelled
Generic: Standards Compliance / File Size Limits (push) Has been cancelled
Generic: Standards Compliance / Binary File Detection (push) Has been cancelled
Generic: Standards Compliance / TODO/FIXME Tracking (push) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Has been cancelled
Generic: Standards Compliance / Unused Dependencies Check (push) Has been cancelled
Generic: Standards Compliance / Broken Link Detection (push) Has been cancelled
Generic: Standards Compliance / API Documentation Coverage (push) Has been cancelled
Generic: Standards Compliance / Accessibility Check (push) Has been cancelled
Generic: Standards Compliance / Performance Metrics (push) Has been cancelled
Generic: Standards Compliance / Enterprise Readiness Check (push) Has been cancelled
Generic: Standards Compliance / Repository Health Check (push) Has been cancelled
Generic: Standards Compliance / Terraform Configuration Validation (push) Has been cancelled
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 1m2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Successful in 27s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 59s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Deploy MokoGitea / deploy (push) Successful in 4m53s
2026-07-13 21:27:47 +00:00
jmiller 78b7c9e4af chore(ci): move deploy workflows out of custom/ to root
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 29s
Generic: Project CI / Tests (pull_request) Successful in 29s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 47s
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Standards Compliance / Secret Scanning (pull_request) Failing after 8s
Generic: Standards Compliance / License Header Validation (pull_request) Successful in 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 55s
Generic: Standards Compliance / Repository Structure Validation (pull_request) Successful in 7s
Universal: PR Check / Require Docs Update (pull_request) Failing after 1m3s
Generic: Standards Compliance / Coding Standards Check (pull_request) Successful in 9s
Generic: Standards Compliance / Workflow Configuration Check (pull_request) Failing after 7s
Generic: Standards Compliance / Documentation Quality Check (pull_request) Successful in 6s
Generic: Standards Compliance / README Completeness Check (pull_request) Failing after 6s
Generic: Standards Compliance / Script Integrity Validation (pull_request) Successful in 8s
Generic: Standards Compliance / Line Length Check (pull_request) Successful in 16s
Generic: Standards Compliance / Version Consistency Check (pull_request) Successful in 46s
Generic: Standards Compliance / File Naming Standards (pull_request) Successful in 6s
Generic: Standards Compliance / Insecure Code Pattern Detection (pull_request) Successful in 7s
Generic: Standards Compliance / Git Repository Hygiene (pull_request) Successful in 42s
Generic: Standards Compliance / Dead Code Detection (pull_request) Successful in 9s
Generic: Standards Compliance / File Size Limits (pull_request) Successful in 6s
Generic: Standards Compliance / Code Complexity Analysis (pull_request) Successful in 49s
Generic: Standards Compliance / TODO/FIXME Tracking (pull_request) Successful in 6s
Generic: Standards Compliance / Code Duplication Detection (pull_request) Successful in 51s
Generic: Standards Compliance / Unused Dependencies Check (pull_request) Successful in 42s
Generic: Standards Compliance / Dependency Vulnerability Scanning (pull_request) Successful in 46s
Generic: Standards Compliance / API Documentation Coverage (pull_request) Successful in 6s
Generic: Standards Compliance / Broken Link Detection (pull_request) Successful in 8s
Generic: Standards Compliance / Binary File Detection (pull_request) Successful in 1m25s
Generic: Standards Compliance / Accessibility Check (pull_request) Successful in 6s
Generic: Standards Compliance / Performance Metrics (pull_request) Successful in 6s
Generic: Standards Compliance / Terraform Configuration Validation (pull_request) Successful in 11s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Standards Compliance / Enterprise Readiness Check (pull_request) Successful in 43s
Generic: Standards Compliance / Repository Health Check (pull_request) Successful in 43s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Generic: Standards Compliance / Compliance Summary (pull_request) Has been cancelled
Deploy workflows in go/npm repos live in the workflows root, not custom/
(mirrors npm repos' npm-publish.yml in root). The sync's curated set does
not include deploy-*, so root placement is not clobbered. Moved: deploy-dev.yml, deploy-mokogitea.yml, deploy-rc.yml
2026-07-13 21:27:36 +00:00
jmiller 56d33c2e3a Merge pull request 'chore(ci): move repo-specific workflows to workflows/custom/' (#779) from chore/move-custom-workflows-to-custom into main
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 53s
Generic: Standards Compliance / Secret Scanning (push) Has been cancelled
Generic: Standards Compliance / License Header Validation (push) Has been cancelled
Generic: Standards Compliance / Repository Structure Validation (push) Has been cancelled
Generic: Standards Compliance / Coding Standards Check (push) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (push) Has been cancelled
Generic: Standards Compliance / Workflow Configuration Check (push) Has been cancelled
Generic: Standards Compliance / Documentation Quality Check (push) Has been cancelled
Generic: Standards Compliance / README Completeness Check (push) Has been cancelled
Generic: Standards Compliance / Git Repository Hygiene (push) Has been cancelled
Generic: Standards Compliance / Script Integrity Validation (push) Has been cancelled
Generic: Standards Compliance / Line Length Check (push) Has been cancelled
Generic: Standards Compliance / File Naming Standards (push) Has been cancelled
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Has been cancelled
Generic: Standards Compliance / Code Complexity Analysis (push) Has been cancelled
Generic: Standards Compliance / Code Duplication Detection (push) Has been cancelled
Generic: Standards Compliance / Dead Code Detection (push) Has been cancelled
Generic: Standards Compliance / File Size Limits (push) Has been cancelled
Generic: Standards Compliance / Binary File Detection (push) Has been cancelled
Generic: Standards Compliance / TODO/FIXME Tracking (push) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Has been cancelled
Generic: Standards Compliance / Unused Dependencies Check (push) Has been cancelled
Generic: Standards Compliance / Broken Link Detection (push) Has been cancelled
Generic: Standards Compliance / API Documentation Coverage (push) Has been cancelled
Generic: Standards Compliance / Accessibility Check (push) Has been cancelled
Generic: Standards Compliance / Performance Metrics (push) Has been cancelled
Generic: Standards Compliance / Enterprise Readiness Check (push) Has been cancelled
Generic: Standards Compliance / Repository Health Check (push) Has been cancelled
Generic: Standards Compliance / Terraform Configuration Validation (push) Has been cancelled
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
Deploy MokoGitea / deploy (push) Successful in 4m1s
2026-07-13 21:19:36 +00:00
jmiller 385b96fda7 chore(ci): move repo-specific workflows to workflows/custom/
Generic: Project CI / Lint & Validate (pull_request) Successful in 34s
Generic: Project CI / Tests (pull_request) Successful in 30s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 1m17s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m26s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Standards Compliance / Secret Scanning (pull_request) Failing after 8s
Universal: PR Check / Require Docs Update (pull_request) Failing after 1m8s
Generic: Standards Compliance / License Header Validation (pull_request) Successful in 7s
Generic: Standards Compliance / Repository Structure Validation (pull_request) Successful in 8s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m14s
Generic: Standards Compliance / Coding Standards Check (pull_request) Successful in 8s
Generic: Standards Compliance / Documentation Quality Check (pull_request) Successful in 7s
Generic: Standards Compliance / Workflow Configuration Check (pull_request) Failing after 7s
Generic: Standards Compliance / README Completeness Check (pull_request) Failing after 6s
Generic: Standards Compliance / Script Integrity Validation (pull_request) Successful in 9s
Generic: Standards Compliance / Line Length Check (pull_request) Successful in 16s
Generic: Standards Compliance / File Naming Standards (pull_request) Successful in 6s
Generic: Standards Compliance / Version Consistency Check (pull_request) Successful in 50s
Generic: Standards Compliance / Insecure Code Pattern Detection (pull_request) Successful in 6s
Generic: Standards Compliance / Git Repository Hygiene (pull_request) Successful in 53s
Generic: Standards Compliance / Dead Code Detection (pull_request) Successful in 8s
Generic: Standards Compliance / File Size Limits (pull_request) Successful in 6s
Generic: Standards Compliance / Code Complexity Analysis (pull_request) Successful in 44s
Generic: Standards Compliance / Code Duplication Detection (pull_request) Successful in 43s
Generic: Standards Compliance / TODO/FIXME Tracking (pull_request) Successful in 6s
Generic: Standards Compliance / Dependency Vulnerability Scanning (pull_request) Successful in 43s
Generic: Standards Compliance / Unused Dependencies Check (pull_request) Successful in 42s
Generic: Standards Compliance / API Documentation Coverage (pull_request) Successful in 5s
Generic: Standards Compliance / Broken Link Detection (pull_request) Successful in 8s
Generic: Standards Compliance / Accessibility Check (pull_request) Successful in 5s
Generic: Standards Compliance / Performance Metrics (pull_request) Successful in 6s
Generic: Standards Compliance / Binary File Detection (pull_request) Successful in 1m22s
Generic: Standards Compliance / Terraform Configuration Validation (pull_request) Successful in 11s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Standards Compliance / Repository Health Check (pull_request) Successful in 44s
Generic: Standards Compliance / Enterprise Readiness Check (pull_request) Successful in 46s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Generic: Standards Compliance / Compliance Summary (pull_request) Has been cancelled
These are repo-specific (not platform-standard) and belong in custom/ so
the workflow sync never distributes/clobbers them. They still run here
(Gitea Actions executes custom/). Moved: deploy-rc.yml
2026-07-13 21:19:28 +00:00
jmiller fab2e2a2e2 Merge pull request 'chore(ci): remove template-only sync workflows from root' (#778) from chore/remove-template-sync-leaks into main
Cascade Main -> Dev / Cascade main -> dev (push) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
Generic: Standards Compliance / Secret Scanning (push) Has been cancelled
Generic: Standards Compliance / License Header Validation (push) Has been cancelled
Generic: Standards Compliance / Repository Structure Validation (push) Has been cancelled
Generic: Standards Compliance / Coding Standards Check (push) Has been cancelled
Generic: Standards Compliance / Version Consistency Check (push) Has been cancelled
Generic: Standards Compliance / Workflow Configuration Check (push) Has been cancelled
Generic: Standards Compliance / Documentation Quality Check (push) Has been cancelled
Generic: Standards Compliance / README Completeness Check (push) Has been cancelled
Generic: Standards Compliance / Git Repository Hygiene (push) Has been cancelled
Generic: Standards Compliance / Script Integrity Validation (push) Has been cancelled
Generic: Standards Compliance / Line Length Check (push) Has been cancelled
Generic: Standards Compliance / File Naming Standards (push) Has been cancelled
Generic: Standards Compliance / Insecure Code Pattern Detection (push) Has been cancelled
Generic: Standards Compliance / Code Complexity Analysis (push) Has been cancelled
Generic: Standards Compliance / Code Duplication Detection (push) Has been cancelled
Generic: Standards Compliance / Dead Code Detection (push) Has been cancelled
Generic: Standards Compliance / File Size Limits (push) Has been cancelled
Generic: Standards Compliance / Binary File Detection (push) Has been cancelled
Generic: Standards Compliance / TODO/FIXME Tracking (push) Has been cancelled
Generic: Standards Compliance / Dependency Vulnerability Scanning (push) Has been cancelled
Generic: Standards Compliance / Unused Dependencies Check (push) Has been cancelled
Generic: Standards Compliance / Broken Link Detection (push) Has been cancelled
Generic: Standards Compliance / API Documentation Coverage (push) Has been cancelled
Generic: Standards Compliance / Accessibility Check (push) Has been cancelled
Generic: Standards Compliance / Performance Metrics (push) Has been cancelled
Generic: Standards Compliance / Enterprise Readiness Check (push) Has been cancelled
Generic: Standards Compliance / Repository Health Check (push) Has been cancelled
Generic: Standards Compliance / Terraform Configuration Validation (push) Has been cancelled
Generic: Standards Compliance / Compliance Summary (push) Has been cancelled
2026-07-13 20:45:08 +00:00
jmiller c537c63e98 chore(ci): remove template-only sync workflows from root
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 29s
Generic: Project CI / Tests (pull_request) Successful in 28s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 45s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Standards Compliance / Secret Scanning (pull_request) Failing after 7s
Generic: Standards Compliance / License Header Validation (pull_request) Successful in 7s
Universal: PR Check / Require Docs Update (pull_request) Failing after 45s
Generic: Standards Compliance / Repository Structure Validation (pull_request) Successful in 6s
Universal: PR Check / Secret Scan (pull_request) Successful in 48s
Generic: Standards Compliance / Coding Standards Check (pull_request) Successful in 8s
Generic: Standards Compliance / Workflow Configuration Check (pull_request) Failing after 6s
Generic: Standards Compliance / Documentation Quality Check (pull_request) Successful in 5s
Generic: Standards Compliance / README Completeness Check (pull_request) Failing after 6s
Generic: Standards Compliance / Script Integrity Validation (pull_request) Successful in 6s
Generic: Standards Compliance / Line Length Check (pull_request) Successful in 13s
Generic: Standards Compliance / File Naming Standards (pull_request) Successful in 6s
Generic: Standards Compliance / Version Consistency Check (pull_request) Successful in 43s
Generic: Standards Compliance / Insecure Code Pattern Detection (pull_request) Successful in 6s
Generic: Standards Compliance / Git Repository Hygiene (pull_request) Successful in 37s
Generic: Standards Compliance / Dead Code Detection (pull_request) Successful in 7s
Generic: Standards Compliance / File Size Limits (pull_request) Successful in 6s
Generic: Standards Compliance / Code Complexity Analysis (pull_request) Successful in 41s
Generic: Standards Compliance / Code Duplication Detection (pull_request) Successful in 40s
Generic: Standards Compliance / TODO/FIXME Tracking (pull_request) Successful in 6s
Generic: Standards Compliance / Dependency Vulnerability Scanning (pull_request) Successful in 43s
Generic: Standards Compliance / Unused Dependencies Check (pull_request) Successful in 40s
Generic: Standards Compliance / API Documentation Coverage (pull_request) Successful in 6s
Generic: Standards Compliance / Broken Link Detection (pull_request) Successful in 8s
Generic: Standards Compliance / Binary File Detection (pull_request) Successful in 1m21s
Generic: Standards Compliance / Accessibility Check (pull_request) Successful in 6s
Generic: Standards Compliance / Performance Metrics (pull_request) Successful in 6s
Generic: Standards Compliance / Terraform Configuration Validation (pull_request) Successful in 10s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Standards Compliance / Enterprise Readiness Check (pull_request) Successful in 46s
Generic: Standards Compliance / Repository Health Check (pull_request) Successful in 46s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Generic: Standards Compliance / Compliance Summary (pull_request) Has been cancelled
sync-on-merge.yml and workflow-sync-trigger.yml are template-only (gated
if: Template-*), dead no-ops in this child repo, inherited at scaffold time.
sync-on-merge.yml is also broken. The real sync lives in the templates'
workflows/custom/.
2026-07-13 20:44:53 +00:00
jmiller 0fc4adf1a7 chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 19:51:27 +00:00
jmiller affba0a2cd chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 16:21:58 +00:00
jmiller 66017e1668 chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-13 16:21:51 +00:00
jmiller 0a66dacb86 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-13 16:21:45 +00:00
jmiller ae1aa3e21a chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 15:58:20 +00:00
jmiller f2dd391bca chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-13 15:48:35 +00:00
jmiller 485d6be31c chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-13 15:48:27 +00:00
jmiller 2d42b4e475 chore: sync sync-on-merge.yml from Template-Generic [skip ci] 2026-07-13 15:48:19 +00:00
jmiller b5ce7fadcc chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-13 15:48:11 +00:00
jmiller 4972cf71bc chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-13 15:48:03 +00:00
jmiller 9e8d4c43bb chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-13 15:47:55 +00:00
jmiller c0c121dfb5 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 15:47:47 +00:00
jmiller 5bc475e8f5 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-13 15:47:40 +00:00
jmiller 1f0fe7012d chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-13 15:47:31 +00:00
jmiller b902244949 chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-13 15:47:24 +00:00
jmiller c37ada7874 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-13 15:47:16 +00:00
jmiller 9496c7c42d chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-13 15:47:08 +00:00
jmiller a049052e5a chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-13 15:47:01 +00:00
jmiller fc66f8968f chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-13 15:46:55 +00:00
jmiller b0885f6da3 chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-13 15:46:48 +00:00
jmiller 1a2e33abb3 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-13 15:46:42 +00:00
jmiller 6aeb087613 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-13 15:46:35 +00:00
jmiller 94e6030e58 chore: sync standards-compliance.yml from Template-Generic [skip ci] 2026-07-13 15:28:50 +00:00
jmiller 02c2033caa chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-13 09:28:24 +00:00
jmiller 9edfeb6075 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-13 09:28:19 +00:00
jmiller ada91a9bc1 Merge pull request 'chore(sync): cascade main -> dev' (#776) from main into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m9s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 3m57s
2026-07-13 00:47:23 +00:00
jmiller 3ce6fd3e08 Merge pull request 'fix(branding): app icon follows the Nav Icon upload (#773)' (#774) from fix/app-icon-follows-nav-icon into main
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 3s
Generic: Project CI / Lint & Validate (pull_request) Successful in 28s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Generic: Project CI / Tests (pull_request) Successful in 29s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 41s
Deploy MokoGitea / deploy (push) Successful in 5m3s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 00:43:38 +00:00
jmiller dc14a3fcfc Merge remote-tracking branch 'origin/main' into fix/app-icon-follows-nav-icon
Generic: Project CI / Lint & Validate (pull_request) Successful in 33s
Generic: Project CI / Tests (pull_request) Successful in 39s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 1m41s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 1m10s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Failing after 43s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 30s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m54s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
# Conflicts:
#	CHANGELOG.md
2026-07-12 19:42:54 -05:00
jmiller f991f209d1 Merge pull request 'chore(sync): cascade main -> dev' (#775) from main into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m12s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 4m36s
2026-07-13 00:41:18 +00:00
jmiller 6093300fbb Merge pull request 'fix(metadata): derive org from org profile instead of storing it (#771)' (#772) from fix/metadata-org-derived-from-profile into main
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 5s
Generic: Project CI / Lint & Validate (pull_request) Successful in 31s
Generic: Project CI / Tests (pull_request) Successful in 31s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 47s
Deploy MokoGitea / deploy (push) Successful in 5m48s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-13 00:40:52 +00:00
jmiller 793e4885e8 ci: re-trigger checks after runner recovery
Generic: Project CI / Tests (pull_request) Successful in 30s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 31s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 55s
Universal: PR Check / Require Docs Update (pull_request) Failing after 34s
Universal: PR Check / Secret Scan (pull_request) Successful in 35s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m9s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
No code change. The prior run was canceled at 0s because all ubuntu-latest
runners were offline; this re-fires CI now that a runner is back. Ref #771.
2026-07-12 19:37:51 -05:00
jmiller f4be31183f refactor(branding): write each icon target independently on upload
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 32s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 24s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 34s
Generic: Project CI / Tests (pull_request) Successful in 34s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Require Docs Update (pull_request) Failing after 25s
Universal: PR Check / Secret Scan (pull_request) Successful in 26s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Rewind the uploaded stream between targets and write/close each file in
turn instead of a shared io.MultiWriter, so a failure writing a later
target (e.g. logo.png) can never leave an earlier one (logo-small.png)
truncated. Addresses review feedback on #774.
2026-07-12 19:02:14 -05:00
jmiller 3d0cb7d98c fix(branding): app icon follows the Nav Icon upload
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 38s
Universal: Build & Release / Promote to RC (pull_request) Failing after 9s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
The app icon (logo.png) is the PWA/web-manifest icon and the navbar
fallback, but had no Branding control, so it stayed the shipped default
even on a fully branded instance. Uploading the Nav Icon now also writes
logo.png (single io.MultiWriter pass), and resetting the Nav Icon reverts
both to the built-in default.

Closes #773
2026-07-12 18:45:11 -05:00
jmiller 7f436128fd fix(metadata): derive org from org profile instead of storing it
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 29s
Universal: Build & Release / Promote to RC (pull_request) Failing after 8s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Has been cancelled
Generic: Project CI / Tests (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Require Docs Update (pull_request) Has been cancelled
Universal: PR Check / Wiki Update Reminder (pull_request) Has been cancelled
Universal: PR Check / Secret Scan (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Generic: Repo Health / Access control (pull_request) Has been cancelled
Generic: Repo Health / Site Health (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
The repo metadata org field was a stored, editable value that snapshotted
the owner name and could drift when an organization was renamed. It is now
derived from the org profile (repository owner) on read and is read-only.

- models/repo/repo.go: add Repository.DerivedOrgName (owner FullName, falling
  back to the handle, then the denormalized OwnerName)
- models/repo/repo_manifest.go: drop the Org struct field
- models/migrations/v1_27/v369.go + migrations.go: migration #368 drops the
  repo_manifest.org column
- routers/api/v1/repo/manifest.go: derive org in the response, ignore it on write
- routers/web/repo/setting/metadata.go + templates: show org read-only (derived)
- services/updateserver/joomla.go: use DerivedOrgName for the feed maintainer

Closes #771
2026-07-12 18:35:31 -05:00
jmiller 010d3dcbcf chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-06 17:04:06 +00:00
jmiller 8242e5713a fix(ci): robust prod deploy workflow (#758) [skip ci]
Land deploy-mokogitea.yml robustness fix on main. [skip ci] — workflow-file-only change; no rebuild/redeploy needed (prod already live on stable-289 from #733).
2026-07-06 15:57:46 +00:00
jmiller a7220ddb2b fix(ci): robust prod deploy — drop dev-health gate, no tier-clobbering sed, rm -f before recreate
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 42s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 36s
Generic: Project CI / Tests (pull_request) Successful in 36s
Universal: PR Check / Secret Scan (pull_request) Successful in 56s
Universal: PR Check / Require Docs Update (pull_request) Failing after 1m11s
PR RC Release / Build RC Release (pull_request) Successful in 1m13s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
The prod deploy (deploy-mokogitea.yml, on push to main) failed the #733 release and
silently corrupted the shared compose. Three fixes:
- Removed the 'Verify dev environment is healthy' gate: it curled git.dev and exit 1
  on failure, so a dev blip false-negatives the PROD deploy (wrong-tier gate; the
  dev->rc->main pipeline + RC env is the real gate).
- Replaced 'sed s|mokogitea:...|:$TAG|' (matched ALL mokogitea service lines and
  clobbered the dev+rc ${MOKOGITEA_*_TAG} env-vars with the prod tag) with the
  env-var pattern: env $TAG_ENV=$TAG docker compose up -- drives only the target
  service, no sed.
- Added 'docker rm -f $CONTAINER' + '-p gitea-dev' + '--force-recreate' before the
  compose up, fixing the 'Container name /mokogitea already in use' conflict.

Aligns deploy-mokogitea.yml with the fixed deploy-dev/deploy-rc pattern. Host compose
was separately restored to env-var form (the bad run had clobbered dev+rc tags).
Long-term: cut over Template-Go#5 deploy-prod.yml. Refs #733, #752.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-06 10:55:58 -05:00
jmiller 10760463f8 Merge pull request 'chore(sync): cascade main -> dev' (#757) from main into dev
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 5m45s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
2026-07-06 07:38:07 +00:00
jmiller bc3bb0f778 Merge pull request 'Release: org-wide governance series (#727) — dev → main' (#733) from dev into main
Sync Workflows to Repos / sync (push) Has been skipped
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 6s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
PR RC Release / Build RC Release (pull_request) Successful in 5s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 17s
Generic: Project CI / Lint & Validate (pull_request) Successful in 44s
Generic: Project CI / Tests (pull_request) Successful in 58s
Universal: PR Check / Secret Scan (pull_request) Successful in 2m54s
Deploy MokoGitea / deploy (push) Failing after 5m51s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-06 07:37:35 +00:00
jmiller d955bac72b chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-06 03:49:59 +00:00
jmiller afc470f513 chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-07-06 03:49:52 +00:00
jmiller 6505840839 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-06 03:49:44 +00:00
jmiller 7d98098a87 Merge pull request 'fix(ci): make Tests an independent job (work around Gitea needs-chain scheduler stall)' (#756) from fix/ci-generic-scheduler into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 49s
Generic: Project CI / Tests (pull_request) Successful in 51s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 1m29s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m37s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 12s
Universal: PR Check / Require Docs Update (pull_request) Successful in 1m7s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m14s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 12m25s
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m18s
2026-07-06 03:42:40 +00:00
jmiller 28b9d94658 fix(ci): make Tests an independent job to work around Gitea needs-chain scheduler stall
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 58s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 32s
Generic: Project CI / Tests (pull_request) Successful in 33s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: PR Check / Secret Scan (pull_request) Successful in 57s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The Gitea Actions scheduler on the current build does not offer the dependent
2nd job of a needs-chain to runners: the Tests job (needs: lint) reaches
'waiting' but is never claimed even with idle, label-matching runners, then is
reaped by ABANDONED_JOB_TIMEOUT. Verified via dispatch on idle runners (run
34933: lint claimed+success, Tests waiting/runner_id 0, never scheduled).

Drop 'needs: lint' + the always() gate; guard template repos directly so Tests
runs as an independent, schedulable job.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 22:39:40 -05:00
jmiller 8d8ecf176a Merge pull request 'fix(tests): repair tests/integration compile errors (upstream API/import drift)' (#755) from fix/integration-tests-compile-wt into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m31s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Require Docs Update (pull_request) Successful in 4m17s
PR RC Release / Build RC Release (pull_request) Successful in 4m19s
Universal: PR Check / Secret Scan (pull_request) Successful in 4m19s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 5m55s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-06 03:14:18 +00:00
jmiller 7203628004 fix(tests): repair tests/integration compile errors from upstream API/import drift
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
Generic: Project CI / Lint & Validate (pull_request) Successful in 40s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m27s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m22s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Two compile errors blocked the whole tests/integration package (prereq for the
layered test pipeline, Template-Go#4, and the clean go-compile owed by #733):

- api_packages_composer_test.go: composer.PackageMetadataResponse{} (value) used
  where *PackageMetadataResponse (pointer) is required -> take the address.
- oauth_avatar_test.go: 10 stale upstream 'code.gitea.io/gitea/' imports (never
  updated after the fork module rename) -> 'code.mokoconsulting.tech/MokoConsulting/MokoGitea/'.

go vet ./tests/integration/... now exits 0 (compiles clean).

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 22:11:44 -05:00
jmiller 36a824be1d Merge pull request 'chore(manifest): remove deprecated .mokogitea/manifest.xml parser; mokoplatform→mokocli' (#754) from feature/752-chore-deploy-rebuild-redeploy-for-moko-p into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 4s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 20s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 44s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m51s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Require Docs Update (pull_request) Successful in 4m48s
PR RC Release / Build RC Release (pull_request) Successful in 4m51s
Universal: PR Check / Secret Scan (pull_request) Successful in 4m46s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 23m32s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-06 02:38:10 +00:00
jmiller fcf33d35df chore(api): remove /manifest backward-compat alias route (use /metadata)
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 14s
Universal: Auto Version Bump / Version Bump (push) Successful in 25s
Generic: Project CI / Lint & Validate (pull_request) Successful in 51s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m21s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 3s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The /manifest GET+PUT routes were backward-compat aliases for the canonical
/metadata routes. Completing the manifest->metadata migration (#752): drop the
alias so /metadata is the single path. Handlers (GetRepoMetadata/UpdateRepoMetadata)
are unchanged. The MCP already calls /metadata (mcp-mokogitea-api v1.4.2,
gitea_metadata_get/update); the stale gitea_manifest_* tools only exist in older
deployed MCP builds and drop out on redeploy.

Refs #752

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 21:07:05 -05:00
jmiller 358606e235 chore(manifest): remove deprecated .mokogitea/manifest.xml parser; mokoplatform->mokocli
Universal: Auto Version Bump / Version Bump (push) Successful in 14s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 44s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m38s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The .mokogitea/manifest.xml file is deprecated - the first-class RepoMetadata DB
fields (set via the /manifest API) are the authoritative repository metadata (see
issue #752). Remove the legacy XML auto-sync path rather than teaching it the
renamed mokocli root element:

- delete services/repository/manifest_sync.go (manifestXML parser)
- drop the SyncMetadataFromCommit call on default-branch push (push.go)
- rebrand remaining mokoplatform->mokocli refs (repo_manifest.go, v347.go comment,
  locale manifest_desc)

The RepoMetadata model, /manifest API, settings UI, and update-feed generation
(first-class fields) are unchanged. gofmt-normalized repo_manifest.go struct tags.

Refs #752

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 21:00:28 -05:00
jmiller c74173bb86 ci(deploy-rc): add workflow_dispatch for isolated manual tests
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 21s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 51s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 20s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m57s
PR RC Release / Build RC Release (pull_request) Successful in 5m58s
Universal: PR Check / Require Docs Update (pull_request) Successful in 6m4s
Universal: PR Check / Secret Scan (pull_request) Successful in 6m4s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 7m28s
Deploy (RC) / Build & Deploy to RC (push) Successful in 10m18s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Lets deploy-rc be run on demand (from a ref carrying current source) to verify
the RC pipeline end-to-end without a full rc promotion + pre-release.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 19:15:55 -05:00
jmiller b2447da3dd Merge pull request 'feat(ci): deploy-rc.yml — auto-deploy rc branch to the new RC environment' (#751) from feat/deploy-rc-workflow into dev
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m53s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 5m27s
Universal: PR Check / Require Docs Update (pull_request) Successful in 5m29s
Universal: PR Check / Secret Scan (pull_request) Successful in 5m30s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 10m14s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-06 00:03:55 +00:00
jmiller 7be712571c refactor(ci): tier-scope deploy-rc variables (RC_SSH_*, RC_REGISTRY*, RC_IMAGE)
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 47s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m36s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Shared DEPLOY_HOST/PORT/USER assumed all tiers live on one host. Scope every
deploy variable to the rc tier (mirrors the org's DEV_SSH_* convention) so a repo
inheriting this template can host rc/dev/prod on separate machines independently.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 19:01:38 -05:00
jmiller 6634b049ab fix(ci): deploy-rc reuses existing DEPLOY_SSH_KEY + MOKOGITEA_TOKEN secrets
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Generic: Project CI / Lint & Validate (pull_request) Successful in 52s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m47s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The registry token is the org-wide MOKOGITEA_TOKEN secret (already configured,
used by deploy-dev/deploy-mokogitea), not a new DEPLOY_REGISTRY_TOKEN. Reuse the
existing secret names so no new secret values are needed; only the non-sensitive
tier variables get set per-repo.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 18:54:27 -05:00
jmiller 5c62cefcd3 refactor(ci): deploy-rc.yml -> root workflows, fully parameterized via repo vars/secrets
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 7s
Generic: Project CI / Lint & Validate (pull_request) Successful in 33s
Universal: PR Check / Secret Scan (pull_request) Successful in 53s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Moved from custom/ to the root .mokogitea/workflows/ (synced/template-owned area).
- All deployment config now comes from repo Actions variables (DEPLOY_HOST/PORT/USER,
  DEPLOY_REGISTRY/IMAGE, RC_CONTAINER/COMPOSE_PROJECT/COMPOSE_DIR/SOURCE_DIR/TAG_ENV/
  HEALTH_URL) and secrets (DEPLOY_SSH_KEY, DEPLOY_REGISTRY_TOKEN) — nothing hardcoded,
  so it works across Go server repos. Clone URL derived from github.server_url/repository.
- To become Template-Go-owned (Template-Go#3).

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 18:37:33 -05:00
jmiller f2ca569906 feat(ci): deploy-rc.yml — auto-deploy the rc branch to the RC environment
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 43s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m6s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Adds a custom deploy workflow that builds and deploys the release candidate
to rc.git.mokoconsulting.tech (mokogitea-rc container, port 3110, DB
mokogitea_rc) on push to the rc branch — the branch promote-rc creates when
a PR to main is opened. Mirrors the fixed deploy-dev.yml: env-var image tag
(MOKOGITEA_RC_TAG) driving the shared compose file (no sed), rm -f before
force-recreate, quoted-heredoc remote expansion, health check + external
verify. Gives a real release-candidate environment to validate before prod.

Intended to move to Template-Go as the canonical synced source once validated
(kept in custom/ for now so it isn't overwritten by workflow sync).

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 18:31:51 -05:00
jmiller b9301a8f31 Merge pull request 'feat: org branch protection per-user (username/email) allowlists + actions-bot toggle (#727)' (#750) from feature/org-branch-protection-user-allowlists into dev
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 53s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 14s
PR RC Release / Build RC Release (pull_request) Successful in 4m28s
Universal: PR Check / Require Docs Update (pull_request) Successful in 4m57s
Universal: PR Check / Secret Scan (pull_request) Successful in 4m55s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 2m51s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 15m28s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been skipped
2026-07-05 22:54:30 +00:00
jmiller cac06c2ac7 feat: org branch protection per-user allowlists + Actions-bot toggle (#727)
Universal: Auto Version Bump / Version Bump (push) Successful in 12s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Successful in 14s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m3s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m42s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Extend org-level branch protection to support per-user allowlists (resolved
from username OR email) and an "allow Actions bot" toggle, alongside the
existing team allowlists, for all five categories (push, merge, force-push,
delete, approvals).

- models/git/org_protected_branch.go: add WhitelistUserIDs, MergeWhitelistUserIDs,
  ForcePushAllowlistUserIDs, DeleteAllowlistUserIDs, ApprovalsWhitelistUserIDs
  ([]int64) plus WhitelistActionsUser, MergeWhitelistActionsUser,
  ForcePushAllowlistActionsUser, DeleteAllowlistActionsUser (bool); copy all 9
  into ProtectedBranch in ToProtectedBranch().
- models/migrations/v1_27/v368.go: migration 367 adds the 9 columns.
- modules/structs/org_branch.go: add *Usernames []string and *ActionsUser bool
  to Create/Edit options and the response, matching repo-level json names.
- routers/api/v1/org/branch_protection.go: resolveUserIDs (username then email,
  dedupe, 422 on unknown); wire into Create + Edit and toAPIOrgBranchProtection.
- models/git/protected_branch_merge.go: add mergeAllowFlag and merge the four
  Actions-user flags most-restrictively (org can now express them); deploy-key
  flags stay repo-only pass-through.
- models/git/protected_branch_merge_test.go: mergeAllowFlag truth table, user-ID
  intersection, deploy-key pass-through.
- regenerate swagger v1 + openapi3 specs.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 17:48:47 -05:00
jmiller 6c0c2c3f1f Merge pull request 'chore(sync): cascade main -> dev' (#748) from main into dev
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m8s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 1m38s
Universal: PR Check / Wiki Update Reminder (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Successful in 17s
Generic: Repo Health / Access control (pull_request) Successful in 5s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 1m56s
Universal: PR Check / Require Docs Update (pull_request) Successful in 3m36s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m39s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 14m4s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-05 22:03:31 +00:00
jmiller 4db6f03efd Merge fix/rebrand-wiki-docs: normalize in-repo wiki branding
Cascade Main -> Dev / Cascade main -> dev (push) Successful in 13s
Universal: PR Check / Require Docs Update (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Wiki Update Reminder (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 16s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 51s
Universal: PR Check / Secret Scan (pull_request) Successful in 3m59s
Deploy MokoGitea / deploy (push) Failing after 14m2s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-05 22:01:50 +00:00
jmiller bdac121b70 docs(wiki): normalize in-repo wiki branding (MokoGitea/mokocli/MokoStandards->org wiki/MokoSuite)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m50s
2026-07-05 22:01:47 +00:00
jmiller 80a4c68063 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-05 21:46:31 +00:00
jmiller 5c0bf5f214 chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-05 21:46:18 +00:00
jmiller e2f8d5ce9b chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-05 21:46:08 +00:00
jmiller 1d5427f2c2 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-05 21:45:58 +00:00
jmiller 9d949b6294 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-05 21:45:45 +00:00
jmiller 515e8fcdea chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-05 21:45:34 +00:00
jmiller 519966015b chore: sync notify.yml from Template-Generic [skip ci] 2026-07-05 21:45:24 +00:00
jmiller 2677caafd6 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-07-05 21:45:14 +00:00
jmiller 66148f76fa chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-05 21:45:05 +00:00
jmiller 7a4be6ab63 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-05 21:44:56 +00:00
jmiller c26ceaea87 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-05 21:44:50 +00:00
jmiller 8ba2dbf3e3 chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-05 21:42:28 +00:00
jmiller 44b25099ac chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-05 21:42:21 +00:00
jmiller 838d1f0d28 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-05 21:42:15 +00:00
jmiller 6e0db30b3e chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-05 21:42:09 +00:00
jmiller e2343c8fb4 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-07-05 21:09:03 +00:00
jmiller 8e045c60fc chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-05 21:08:49 +00:00
jmiller d3a0c7534b chore: sync version-set.yml from Template-Generic [skip ci] 2026-07-05 20:43:30 +00:00
jmiller 7f0da28988 chore: sync sync-on-merge.yml from Template-Generic [skip ci] 2026-07-05 20:43:14 +00:00
jmiller 6cd1d10617 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-07-05 20:42:59 +00:00
jmiller 5c8056f15e chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-07-05 20:42:42 +00:00
jmiller ffaa3662e6 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-07-05 20:42:24 +00:00
jmiller 1c462f9d49 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-07-05 20:42:05 +00:00
jmiller 8fbf97ac7e chore: sync gitleaks.yml from Template-Generic [skip ci] 2026-07-05 20:41:46 +00:00
jmiller b3448f4d62 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-07-05 20:41:21 +00:00
jmiller f72300a03f chore: sync cascade-dev.yml from Template-Generic [skip ci] 2026-07-05 20:40:53 +00:00
jmiller 08a43ec718 chore: sync branch-cleanup.yml from Template-Generic [skip ci] 2026-07-05 20:40:04 +00:00
jmiller bf2b299f87 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-07-05 20:39:44 +00:00
jmiller 89aa805967 chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-07-05 20:39:24 +00:00
jmiller 3efbab985b Merge pull request 'fix(locale): duplicate en-US key crashes boot under jsonv2 (#696/#728)' (#743) from fix/locale-dup-key-jsonv2 into dev
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Generic: Project CI / Lint & Validate (pull_request) Successful in 52s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 19s
PR RC Release / Build RC Release (pull_request) Successful in 4m20s
Universal: PR Check / Secret Scan (pull_request) Successful in 4m21s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 10m14s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been skipped
2026-07-05 20:32:07 +00:00
jmiller 98b1ed2f7b fix(locale): duplicate en-US key crashes server boot under jsonv2
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 41s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 47s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m39s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The branch-protection delete feature (#696/#728) added a second
"repo.settings.event_delete" entry ("Branch Deletion") to locale_en-US.json,
reusing the existing webhook-event key (value "Delete"). The old JSON decoder
silently kept the last value; Go 1.26's jsonv2 decoder rejects duplicate
object keys, so InitLocales fails ("duplicate object member name
repo.settings.event_delete") and the server crash-loops at startup. Like the
code-scanner regexp panic, this only surfaces on a fresh boot, which is why it
shipped unnoticed.

Give the branch-protection section header its own key
"repo.settings.protect_branch_deletion" and point protected_branch.tmpl at it,
so the webhook "Delete" label and the branch-protection "Branch Deletion"
header both render correctly and the JSON has no duplicate. Verified: no
duplicate keys remain in any options/locale/*.json.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 15:21:31 -05:00
jmiller ccfc9a604b Merge pull request 'fix(security): code scanner RE2 lookahead panics server at startup (#552)' (#742) from fix/code-scanner-re2-panic into dev
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Repo Health / Access control (pull_request) Successful in 3s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m4s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m31s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 4m7s
Universal: PR Check / Secret Scan (pull_request) Successful in 4m5s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 7m9s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-05 20:07:45 +00:00
jmiller 2f119fbd95 fix(security): code scanner panics at startup on RE2-incompatible regexp
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 16s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 44s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m29s
Universal: PR Check / Secret Scan (pull_request) Successful in 3m49s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The "deserialize-yaml-py" rule in services/security/code_scanner.go used a
negative lookahead `(?!\s*#)` in regexp.MustCompile. Go's regexp engine is
RE2, which has no lookahead/lookbehind, so MustCompile panics during the
package init() — crash-looping the entire server at startup. `go build` and
`go vet` do not execute init(), and CI never boots the binary, so this
shipped to main via #552 undetected; the running instances survived only
because they predate that image.

Replace the pattern with an RE2-safe equivalent `(?i)yaml\.load\s*\(`, which
matches the rule's stated intent (flag yaml.load() without SafeLoader,
CWE-502). Add a regression test that forces the package init and asserts
every DefaultCodeRules pattern compiled, so a future RE2-incompatible
pattern fails in CI here instead of on a live deploy.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 15:06:57 -05:00
jmiller 7f229ba01c Merge pull request 'fix: org-governance release review findings + dev deploy targeting (#727, #733)' (#741) from fix/org-governance-review into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 40s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m24s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
PR RC Release / Build RC Release (pull_request) Successful in 1m32s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m43s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 6m22s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-05 19:51:06 +00:00
jmiller e98fca780e fix: address org-governance release review (#727, #733) + dev deploy targeting
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 50s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Universal: PR Check / Secret Scan (pull_request) Successful in 59s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Code-review findings on the org-governance release:

- Fail closed on org-rule lookup error: getFirstMatchProtectedBranchRule
  swallowed FindOrgBranchRuleForBranch errors (returned nil,nil), silently
  dropping the org floor and falling back to the repo rule on a transient DB
  error. Propagate the error so the org rule stays enforced.

- Stop the org rule locking out deploy-key and Actions-bot pushes:
  OrgProtectedBranch is team-only, so mergeMostRestrictive was ANDing the
  repo's WhitelistDeployKeys / WhitelistActionsUser (and the force-push,
  delete and merge counterparts) against the org side's always-false zero
  value, blocking every deploy-key and Actions push in any org with a
  matching branch rule. Carry those org-unmanaged fields through from the
  repo rule unchanged.

- Org push-policy max-file-size now inspects only the pushed delta
  (diff-tree + cat-file --batch-check) instead of the full tip tree via
  ls-tree, so a pre-existing oversized file can no longer permanently block
  unrelated pushes. New branches (no base commit) still scan the full tree.

Dev deploy targeting:

- deploy-dev.yml drove the dev container image via `sed` on the SHARED
  compose file, but the pattern matched the *prod* service line
  (container_name: mokogitea) — leaving the dev service pinned to a stale
  image (so every "green" deploy recreated old code) while corrupting the
  prod image pin. Drive the dev service image from ${MOKOGITEA_DEV_TAG}
  instead; the env-var only affects the dev service.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 14:46:01 -05:00
jmiller 7a4dc5e809 Merge pull request 'docs(api): OpenAPI spec + README/CHANGELOG for org-governance (#727, #738)' (#739) from feat/org-governance-openapi into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 59s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 1m8s
PR RC Release / Build RC Release (pull_request) Successful in 2m46s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 4m25s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-05 15:19:08 +00:00
jmiller 8c63b00953 Merge pull request 'fix(ci): recreate dev container to avoid name conflict on deploy' (#740) from fix/deploy-dev-container-conflict into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 14s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 51s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m15s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 1m20s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m19s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Successful in 7m29s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-05 15:18:53 +00:00
jmiller 6b81922c47 fix(ci): recreate dev container to avoid name conflict on deploy
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m0s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 9s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Generic: Project CI / Lint & Validate (pull_request) Successful in 34s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m11s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
After the tag fix (#737) the dev deploy builds and pushes the image
fine but fails at `docker compose up -d` with:
  Conflict. The container name "/mokogitea-dev" is already in use

The dev service uses a fixed container_name, and the symlinked
/opt/gitea-dev path makes compose's derived project name unstable, so
an existing container is not recognized as the project's and `up`
tries to create rather than recreate. Remove any lingering
fixed-name container first, pin the compose project name, and force a
fresh recreate so migrations run against the new image.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 10:17:08 -05:00
jmiller 93365cdd95 docs(api): swagger annotations + response models for org-governance endpoints (#727, #738)
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 41s
Universal: PR Check / Secret Scan (pull_request) Successful in 59s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Annotate the four previously undocumented org-governance API handlers
(tag_protection, push_policy, repo_defaults, email_domain) with
swagger:operation blocks, and register the swagger:response models the
branch_protection operations already referenced. Register the org
option DTOs in the parameterBodies hack so their definitions are
emitted.

Also fix pre-existing spec-generation blockers surfaced once the spec
became regenerable: a stray comment glued to the repoUpdateManifest
swagger block (broke YAML parsing), missing owner/repo path params on
the manifest operations, a Manifest response registration, and missing
definitions for EditAccessTokenOption, the IssueBulk* options, and the
Issue{Priority,Status,Type}Def types. Regenerated v1_json.tmpl and
v1_openapi3_json.tmpl; spec now validates cleanly against Swagger 2.0.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 01:58:08 -05:00
jmiller 81ea2fcb05 docs: README org-governance feature + CHANGELOG CI fixes (#727)
Add an Org Governance entry to the README key-features list (org-wide
branch/tag protection, push policy, repo defaults, email-domain
allowlist) and record the recent build/CI fixes (#734, #735, #736,
#737) under CHANGELOG [Unreleased].

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 01:28:36 -05:00
jmiller 2713c49aec Merge pull request 'fix(ci): pass TAG/REGISTRY_TOKEN into remote shell in dev deploy' (#737) from fix/deploy-dev-var-expansion into dev
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m13s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2m48s
Universal: PR Check / Secret Scan (pull_request) Successful in 2m49s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 4m55s
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-05 06:15:01 +00:00
jmiller 3917bf6a29 fix(ci): pass TAG/REGISTRY_TOKEN into remote shell in dev deploy
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Successful in 12s
Generic: Project CI / Lint & Validate (pull_request) Successful in 37s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m7s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m12s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
The dev deploy step used an unquoted SSH heredoc and referenced
runner-side values as \$TAG / \$REGISTRY_TOKEN, deferring their
expansion to the remote shell where those names are unset. The
Docker build tag collapsed to "mokogitea:" and every dev deploy
failed with `invalid tag ... invalid reference format` before any
migration or server boot could run.

Inject TAG and REGISTRY_TOKEN as an env prefix on the ssh command
(`TAG='...' REGISTRY_TOKEN='...' bash -s`) and switch to a quoted
heredoc so every $var expands in exactly one place: the remote host.
Also fixes HEALTH_FMT (was defined on the runner but referenced
remotely) and adds an explicit empty-TAG guard so a future
regression fails loudly instead of building an untagged image.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 01:08:32 -05:00
jmiller 89ed32e961 Merge pull request 'fix: repair unit-test compile + vet failures (partial integration cleanup)' (#736) from fix/vet-test-suite-blockers into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m7s
PR RC Release / Build RC Release (pull_request) Successful in 1m20s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m20s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 2m33s
Universal: PR Check / Secret Scan (pull_request) Successful in 2m31s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
2026-07-05 05:58:38 +00:00
jmiller 948e7bcd21 fix: partial repair of tests/integration compile errors (license test)
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 55s
Universal: PR Check / Validate PR (pull_request) Successful in 12s
Generic: Project CI / Lint & Validate (pull_request) Successful in 41s
Universal: PR Check / Secret Scan (pull_request) Successful in 57s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
api_license_keys_test.go used the outdated NewRequestWithBody signature
(passing []byte where io.Reader is now required) — wrapped the string bodies in
strings.NewReader. Note: tests/integration remains broadly pre-existing-broken
across multiple other fork-added files (api_packages_composer type mismatch,
etc.); those are a separate dedicated cleanup, not part of #727.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 00:27:30 -05:00
jmiller 5d797431f0 fix: repair pre-existing test-suite compile/vet failures
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m15s
`go vet ./...` (finally runnable with a local Go toolchain) surfaced three
pre-existing failures that prevented the whole test tree from compiling — which
is very likely why the "Project CI / Tests" job never went green. None relate to
#727; all pre-existing on main.

- modules/util/util_test.go: CryptoRandomInt/String/Bytes now return (value,
  error); the tests used single-value assignment. Updated to capture + assert
  the error (and dropped a now-redundant `var err error`).
- tests/integration/auth_oauth2_test.go: `newFakeOIDCServer` was declared twice
  with different signatures (redeclaration = build failure). Renamed the
  config-struct variant to `newFakeOIDCServerWithConfig` and updated its caller;
  the (sub, oid) variant keeps the original name for its caller.
- routers/web/repo/issue_comment.go: removed a redundant `&& statusIDStr != ""`
  duplicate condition (vet: redundant and).

Verified: `go vet ./modules/util` clean; full `go vet ./...` re-run.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 00:23:04 -05:00
jmiller 63f773aa56 Merge pull request 'fix: repair build (renamed org-visibility helper) + gofmt' (#735) from fix/compile-hasorgvisible-and-gofmt into dev
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 18s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m3s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m28s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m28s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 1m40s
Deploy MokoGitea (Dev) / Build & Deploy to Dev (push) Failing after 2m44s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
2026-07-05 05:07:58 +00:00
jmiller 125eefc650 fix: repair build (renamed org-visibility helper) + gofmt
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Successful in 19s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m8s
Generic: Project CI / Lint & Validate (pull_request) Successful in 1m10s
Universal: PR Check / Secret Scan (pull_request) Successful in 2m43s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Two pre-existing issues surfaced when the org-governance series was compiled
locally with a real Go toolchain (go1.26.3) for the first time:

- routers/api/v1/api.go:519 called organization.HasOrgOrUserVisible, which no
  longer exists — it was renamed to IsOwnerVisibleToDoer (models/organization/
  org.go:548, identical signature). This one missed call site meant the whole
  routers/api/v1 package (and therefore the server binary) failed `go build`.
  With the rename, `go build ./...` is clean.
- gofmt: api.go (a mis-indented commented-out /projects route block) and
  release.go (import sort: repo before updateserver) were gofmt-dirty. Fixed
  with gofmt -w on the two files this change already touches.

Not part of #727, but blocks building/releasing the fork; found while validating
the dev -> main promotion (#733).

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
2026-07-05 00:06:48 -05:00
jmiller 056eb7d3c4 chore: sync notify.yml from Template-Generic [skip ci] 2026-07-05 00:05:09 +00:00
jmiller 0492448ab5 chore: sync cleanup.yml from Template-Generic [skip ci] 2026-07-05 00:04:56 +00:00
77 changed files with 9935 additions and 566 deletions
+6 -5
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/auto-bump.yml
# VERSION: 09.02.00
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
@@ -34,7 +34,8 @@ jobs:
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip bump]') &&
!startsWith(github.event.head_commit.message, 'Merge pull request')
!startsWith(github.event.head_commit.message, 'Merge pull request') &&
!startsWith(github.event.repository.name, 'Template-')
steps:
- name: Checkout
@@ -43,7 +44,7 @@ jobs:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup mokocli tools
- name: Setup MokoCLI tools
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
+69 -46
View File
@@ -3,18 +3,18 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/auto-release.yml
# VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from manifest.xml
# BRIEF: Universal build & release detects platform from metadata API
#
# +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +=======================================================================+
# | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | Reads metadata API (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages |
@@ -81,7 +81,7 @@ jobs:
fetch-depth: 1
submodules: recursive
- name: Setup mokocli tools
- name: Setup MokoCLI tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -95,7 +95,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet
@@ -115,20 +115,17 @@ jobs:
SRC_SHA=$(printf '%s' "$SRC_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin)['commit']['id'])" 2>/dev/null || true)
[ -n "$SRC_SHA" ] || { echo "::error::Could not resolve HEAD of ${FROM}"; exit 1; }
# Point rc at the source commit. If rc already exists (a protected branch that
# cannot be deleted), force-update its ref in place instead of delete+recreate:
# deleting a protected branch fails, which then makes the recreate return HTTP 409.
if curl -sf -o /dev/null -H "$AUTH" "${API_BASE}/branches/rc"; then
echo "rc exists - force-updating to ${FROM} (${SRC_SHA})"
curl -sf -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
"${API_BASE}/git/refs/heads/rc" -d "{\"sha\":\"${SRC_SHA}\",\"force\":true}" \
|| { echo "::error::Failed to force-update rc (CI token needs force-push on the protected rc branch)"; exit 1; }
else
echo "Creating rc from ${FROM}"
curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \
"${API_BASE}/branches" -d "{\"new_branch_name\":\"rc\",\"old_branch_name\":\"${FROM}\"}" \
|| { echo "::error::Failed to create rc from ${FROM}"; exit 1; }
fi
# Point rc at the source commit via git push. Gitea's git/refs PATCH API
# returns HTTP 405 on ANY protected branch (force or not, even for a user in
# the force-push allowlist), so it cannot move a protected rc. git push honors
# the push + force-push allowlists and creates rc if it is absent.
PUSH_URL="https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@${MOKOGITEA_URL#https://}/${GITEA_ORG}/${GITEA_REPO}.git"
git config --global user.name "mokogitea-actions[bot]"
git config --global user.email "actions@mokoconsulting.tech"
git fetch --no-tags "$PUSH_URL" "${FROM}"
git push --force "$PUSH_URL" "FETCH_HEAD:refs/heads/rc" \
|| { echo "::error::Failed to point rc at ${FROM} (${SRC_SHA}) via git push"; exit 1; }
echo "rc set to ${FROM} (${SRC_SHA})"
# Repoint the PR at rc, then delete the old source branch (non-fatal).
if [ -n "$PR" ]; then
@@ -142,8 +139,8 @@ jobs:
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 config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
@@ -214,8 +211,8 @@ jobs:
- 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 config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-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
@@ -231,7 +228,7 @@ jobs:
fi
echo "No conflict markers found"
- name: Setup mokocli tools
- name: Setup MokoCLI tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -246,7 +243,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/mokocli
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet
@@ -377,13 +374,47 @@ jobs:
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
python3 -c "
import sys
import sys, re
version, date = sys.argv[1], sys.argv[2]
content = open('CHANGELOG.md').read()
old = '## [Unreleased]'
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
content = content.replace(old, new, 1)
open('CHANGELOG.md', 'w').write(content)
lines = open('CHANGELOG.md').read().split('\n')
h2 = re.compile(r'^##\s+\[([^\]]+)\]')
header, sections, cur = [], [], None
for ln in lines:
m = h2.match(ln)
if m:
if cur: sections.append(cur)
cur = {'label': m.group(1).strip(), 'head': ln, 'body': []}
elif cur is None:
header.append(ln)
else:
cur['body'].append(ln)
if cur: sections.append(cur)
def nonempty(b): return any(x.strip() for x in b)
def trim(b):
b = b[:]
while b and not b[0].strip(): b.pop(0)
while b and not b[-1].strip(): b.pop()
return b
unreleased, order, bykey = [], [], {}
for s in sections:
key = s['label'].lower()
if key == 'unreleased':
if nonempty(s['body']): unreleased += s['body']
continue
if not nonempty(s['body']): continue # drop blank release sections
if key in bykey: bykey[key]['body'] += [''] + s['body'] # merge duplicate heading (never drop content)
else: bykey[key] = s; order.append(key)
rest, seen = [bykey[k] for k in order], set(order)
out = []
htxt = '\n'.join(header).rstrip()
if htxt: out += [htxt, '']
out += ['## [Unreleased]', '']
promote = bool(unreleased) and version.lower() not in seen
if unreleased and not promote: out += trim(unreleased) + ['']
if promote: out += ['## [%s] --- %s' % (version, date), ''] + trim(unreleased) + ['']
for s in rest: out += [s['head'], ''] + trim(s['body']) + ['']
res = re.sub(r'\n{3,}', '\n\n', '\n'.join(out)).rstrip('\n') + '\n'
open('CHANGELOG.md', 'w').write(res)
" "$VERSION" "$DATE"
git add CHANGELOG.md
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
@@ -426,7 +457,7 @@ jobs:
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch and recreate dev from main"
- name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
@@ -438,17 +469,9 @@ jobs:
"${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
# dev is reset from main by the dedicated "Cascade Main -> Dev" workflow
# (cascade-dev.yml), which runs after this release completes.
echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
+3 -3
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/branch-cleanup.yml
# VERSION: 01.00.00
# BRIEF: Delete feature branches after PR merge
+187 -7
View File
@@ -1,10 +1,190 @@
# DISABLED — auto-release Step 11 recreates dev from main after every release.
# Cascade-dev is redundant and causes version conflicts when both main and dev
# have different version numbers in templateDetails.xml / manifest.xml.
name: "Cascade Main → Dev (DISABLED)"
on: workflow_dispatch
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Cascade
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/cascade-dev.yml
# VERSION: 02.01.00
# BRIEF: Cascade main -> dev; auto-merge clean, auto-resolve VERSION-stamp-only conflicts, else notify
name: "Cascade Main -> Dev"
on:
push:
branches:
- main
# Daily safety net: catches drift even when main only received [skip ci] pushes
# (which never fire the push trigger above). Off-round minute to avoid a fleet-wide spike.
schedule:
- cron: '23 7 * * *'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
env:
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
# ntfy destination is configured via repo or org variables (org vars are inherited).
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'gitea-releases' }}
jobs:
noop:
cascade:
name: Cascade main -> dev
runs-on: ubuntu-latest
steps:
- run: echo "Cascade disabled — auto-release handles dev recreation"
- name: Checkout (full history for merge/resolve)
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Cascade main -> dev (auto-resolve version stamps, else notify)
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -uo pipefail
API="${MOKOGITEA_URL}/api/v1/repos/${REPO}"
AUTH="Authorization: token ${TOKEN}"
jqget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
# 0. dev must exist
if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then
echo "No dev branch - nothing to cascade."; exit 0
fi
# 1. is main ahead of dev?
AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0)
if [ "${AHEAD:-0}" -eq 0 ]; then
echo "dev already up to date with main."; exit 0
fi
echo "main is ${AHEAD} commit(s) ahead of dev."
# 2. reuse an open main->dev PR, else create one
PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "")
if [ -z "$PR" ]; then
RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \
-d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges when conflict-free, auto-resolves VERSION-stamp-only conflicts, otherwise left open for manual resolution."}')
PR=$(printf '%s' "$RESP" | jqget number)
if [ -z "$PR" ]; then
echo "::warning::Could not open cascade PR: $RESP"; exit 0
fi
echo "Opened cascade PR #${PR}"
else
echo "Reusing open cascade PR #${PR}"
fi
notify() {
curl -sS \
-H "Title: ${REPO}: dev cascade needs manual merge" \
-H "Tags: warning,twisted_rightwards_arrows" \
-H "Priority: high" \
-H "Click: ${MOKOGITEA_URL}/${REPO}/pulls/${PR}" \
-d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
}
# 3. wait for MokoGitea to compute mergeability (conflict detection)
MERGEABLE=""
for _ in 1 2 3 4 5 6; do
MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqget mergeable)
case "$MERGEABLE" in True|False) break ;; esac
sleep 3
done
echo "mergeable=${MERGEABLE}"
# 4a. conflict-free -> merge via API (existing behaviour)
if [ "$MERGEABLE" = "True" ]; then
CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \
-X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}')
if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then
echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)."
exit 0
fi
echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)"
notify "could not be auto-merged (HTTP ${CODE})."
exit 0
fi
# 4b. conflicts -> try to auto-resolve if they are ONLY VERSION-stamp lines.
echo "PR not cleanly mergeable; checking whether conflicts are VERSION-stamp-only..."
git config user.name "MokoGitea Cascade"
git config user.email "actions@mokoconsulting.tech"
git fetch --quiet origin main dev
git checkout -B dev origin/dev
if git merge --no-ff --no-commit origin/main >/dev/null 2>&1; then
# Became clean at git level (e.g. mergeability was still computing) -> commit + push.
git commit -m "chore(sync): cascade main -> dev [skip ci]" >/dev/null
git push origin dev
echo "Cascade merged cleanly at git level and pushed to dev."
exit 0
fi
CONFLICTS=$(git diff --name-only --diff-filter=U)
echo "Conflicted files:"; echo "${CONFLICTS}"
# A conflict is "stamp-only" when every line inside every conflict block matches
# a version-stamp pattern (VERSION: header, <version> element, or CHANGELOG title).
is_stamp_only() {
awk '
/^<<<<<<< / { inc=1; next }
inc && /^=======$/ { next }
/^>>>>>>> / { inc=0; next }
inc { if ($0 !~ /(VERSION:|<version>|# Changelog)/) { bad=1 } }
END { exit(bad ? 1 : 0) }
' "$1"
}
# Resolve a stamp-only file by keeping dev (ours) for the conflicting lines,
# preserving all auto-merged content around them.
keep_ours() {
awk '
/^<<<<<<< / { inc=1; side="ours"; next }
inc && /^=======$/ { side="theirs"; next }
/^>>>>>>> / { inc=0; next }
{ if (!inc) { print; next } if (side=="ours") print }
' "$1" > "$1.resolved" && mv "$1.resolved" "$1"
}
ALL_STAMP=1
for f in ${CONFLICTS}; do
if ! is_stamp_only "$f"; then
echo "::notice::$f has non-stamp conflicts -> manual resolution required."
ALL_STAMP=0; break
fi
done
if [ "$ALL_STAMP" != "1" ]; then
git merge --abort || true
notify "has non-version-stamp conflicts and cannot be auto-resolved."
exit 0
fi
echo "All conflicts are VERSION-stamp-only; resolving in favour of dev."
for f in ${CONFLICTS}; do
keep_ours "$f"
git add "$f"
done
# Best-effort: normalise stamps to dev's version if mokocli is available.
if [ -f /opt/mokocli/cli/version_check.php ]; then
php /opt/mokocli/cli/version_check.php --fix || true
git add -A
fi
git commit -m "chore(sync): cascade main -> dev (auto-resolved version stamps) [skip ci]" >/dev/null
git push origin dev
echo "Cascade auto-resolved and pushed to dev."
# Close the now-redundant PR (its changes are in dev) with an explanatory comment.
curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \
-d '{"body":"Auto-resolved VERSION-stamp-only conflicts and pushed the merge to dev. Closing."}' >/dev/null || true
curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \
-d '{"state":"closed"}' >/dev/null || true
+7 -6
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.CI
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/ci-generic.yml
# VERSION: 01.00.00
@@ -131,10 +131,11 @@ jobs:
test:
name: Tests
runs-on: ubuntu-latest
needs: lint
# Run only when lint succeeded; always() forces evaluation so a skipped
# lint (e.g. template repos) skips this job cleanly instead of hanging.
if: ${{ always() && needs.lint.result == 'success' }}
# Independent job (no `needs: lint`): the MokoGitea Actions scheduler does not
# offer the dependent 2nd job of a needs-chain to runners, so it stalls in
# "waiting" and is reaped by ABANDONED_JOB_TIMEOUT. Guard template repos
# directly (same condition lint uses) instead of gating on lint's result.
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Checkout
+5 -5
View File
@@ -3,12 +3,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
# BRIEF: Reusable workflow — creates/updates a MokoGitea issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
@@ -52,7 +52,7 @@ jobs:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure
+4 -4
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Maintenance
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/cleanup.yml
# VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
@@ -50,7 +50,7 @@ jobs:
for BRANCH in $BRANCHES; do
# Skip protected branches
case "$BRANCH" in
main|master|develop|release/*|hotfix/*) continue ;;
main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
esac
# Check if branch is merged into main
@@ -52,51 +52,69 @@ jobs:
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
run: |
HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}'
# Inject runner-side values (TAG, REGISTRY_TOKEN) into the remote shell's
# environment via a command prefix, then use a *quoted* heredoc so every
# $var below expands in exactly one place: the remote dev host. This avoids
# the local-vs-remote expansion trap that previously left TAG empty.
ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} \
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} bash -s <<DEPLOY_EOF
${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} \
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
set -e
echo 'SSH connected to dev environment'
if [ -z "$TAG" ]; then
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
exit 1
fi
HEALTH_FMT='{{.State.Health.Status}}'
echo 'Cleaning Docker build cache...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Pulling source...'
SOURCE_DIR=/opt/gitea-dev/source
if [ ! -d \$SOURCE_DIR/.git ]; then
git clone -b dev https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git \$SOURCE_DIR
if [ ! -d "$SOURCE_DIR/.git" ]; then
git clone -b dev https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git "$SOURCE_DIR"
fi
cd \$SOURCE_DIR
cd "$SOURCE_DIR"
git remote set-url origin https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git 2>/dev/null || true
git fetch origin dev
git reset --hard origin/dev
echo 'Building Docker image...'
echo "Building Docker image: ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG"
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:\$TAG \
--tag "${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG" \
-f Dockerfile .
echo 'Pushing to registry...'
echo '\$REGISTRY_TOKEN' | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:\$TAG
echo "$REGISTRY_TOKEN" | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin
docker push "${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG"
echo 'Restarting dev container...'
cd /opt/gitea-dev
sed -i "s|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:\$TAG|" docker-compose.yml
docker compose up -d mokogitea-dev
# The dev service in the SHARED compose file reads its image tag from
# ${MOKOGITEA_DEV_TAG}. Drive it from the freshly built tag instead of
# rewriting the file with sed: the old sed pattern matched the *prod*
# service line (container_name: mokogitea) and left the dev service pinned
# to a stale image, so every dev deploy recreated old code while silently
# corrupting the prod image pin. The env-var only affects the dev service.
# Remove any lingering fixed-name container first so the recreate can't hit
# a name conflict, pin the project name for determinism, then force-recreate.
docker rm -f mokogitea-dev 2>/dev/null || true
MOKOGITEA_DEV_TAG="$TAG" docker compose -p gitea-dev up -d --force-recreate mokogitea-dev
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format='\$HEALTH_FMT' mokogitea-dev 2>/dev/null | grep -q healthy; then
if docker inspect --format="$HEALTH_FMT" mokogitea-dev 2>/dev/null | grep -q healthy; then
echo 'Dev container healthy!'
exit 0
fi
echo "Waiting... (attempt \$i/8)"
echo "Waiting... (attempt $i/8)"
done
echo 'Health check failed'
docker logs mokogitea-dev --tail 20
@@ -39,17 +39,10 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Verify dev environment is healthy
run: |
echo "Checking git.dev.mokoconsulting.tech health..."
if curl -sf --max-time 10 https://git.dev.mokoconsulting.tech/api/healthz; then
echo " Dev environment is healthy — proceeding with production deploy"
else
echo "::error::Dev environment is NOT healthy — blocking production deploy"
echo "Deploy to dev first (push to dev branch) and verify it passes before merging to main."
exit 1
fi
# NOTE: removed a "Verify dev environment is healthy" gate that curled
# git.dev and `exit 1` on failure — gating a PROD deploy on a live DEV
# health check is a cross-tier false-negative (a transient dev blip blocked
# prod). Pre-prod validation is the dev -> rc -> main pipeline + RC env.
- name: Checkout source (for version detection)
uses: actions/checkout@v4
with:
@@ -66,17 +59,19 @@ jobs:
ENV="${{ github.event.inputs.environment }}"
fi
if [ "$ENV" = "production" ]; then
echo "compose_dir=/opt/gitea" >> $GITHUB_OUTPUT
echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT
echo "container=mokogitea" >> $GITHUB_OUTPUT
echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT
echo "branch=main" >> $GITHUB_OUTPUT
echo "tag=$VERSION" >> $GITHUB_OUTPUT
echo "tag_env=MOKOGITEA_TAG" >> $GITHUB_OUTPUT
else
echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT
echo "container=mokogitea-dev" >> $GITHUB_OUTPUT
echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT
echo "branch=dev" >> $GITHUB_OUTPUT
echo "tag=$VERSION-dev" >> $GITHUB_OUTPUT
echo "tag_env=MOKOGITEA_DEV_TAG" >> $GITHUB_OUTPUT
fi
- name: Write deploy key
@@ -95,6 +90,7 @@ jobs:
SOURCE_DIR: ${{ steps.config.outputs.source_dir }}
COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }}
CONTAINER: ${{ steps.config.outputs.container }}
TAG_ENV: ${{ steps.config.outputs.tag_env }}
run: |
HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}'
IMAGE_FMT='Image: ${{ '{{' }}.Config.Image${{ '}}' }}'
@@ -134,8 +130,12 @@ jobs:
echo 'Restarting container...'
cd $COMPOSE_DIR
sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:$TAG|' docker-compose.yml
docker compose up -d $CONTAINER
# Drive ONLY the target service's tag via its compose env-var (no sed —
# a blanket 'sed s|mokogitea:...|' clobbered the dev + rc service tags too).
# Remove any lingering fixed-name container first (avoids the "name already
# in use" conflict), then force-recreate under the shared compose project.
docker rm -f $CONTAINER 2>/dev/null || true
env $TAG_ENV="$TAG" docker compose -p gitea-dev up -d --force-recreate $CONTAINER
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
+137
View File
@@ -0,0 +1,137 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# BRIEF: Build and deploy to the RC environment on push to the rc branch.
# Portable across Go server repos: ALL deployment config comes from repo
# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded.
# The rc branch is created by promote-rc when a PR to main opens.
# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3.
#
# Required repo VARIABLES (all tier-scoped so each environment is independent —
# a repo may host its rc/dev/prod tiers on different machines):
# RC_SSH_HOST, RC_SSH_PORT, RC_SSH_USERNAME - SSH deploy target for the rc tier
# RC_REGISTRY, RC_REGISTRY_USER, RC_IMAGE - container registry + login user + image
# RC_CONTAINER - compose service/container to recreate
# RC_COMPOSE_PROJECT - docker compose -p project name
# RC_COMPOSE_DIR - dir containing docker-compose.yml on host
# RC_SOURCE_DIR - build source checkout on host
# RC_TAG_ENV - compose env-var name that pins the image tag
# RC_HEALTH_URL - external URL to verify after deploy
# Required SECRETS (already configured org-wide; reused, not re-set):
# DEPLOY_SSH_KEY - deploy private key (repo/org secret)
# MOKOGITEA_TOKEN - registry/API token (org secret)
name: Deploy (RC)
on:
push:
branches:
- rc
# Manual trigger for isolated end-to-end tests without a full RC promotion.
# Runs on the ref it is dispatched from; that ref must carry current source
# (>= the RC database migration version) or the rebuilt image will refuse the
# newer DB. Dispatch from `rc` once `rc` is current.
workflow_dispatch:
concurrency:
group: deploy-rc
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
deploy-rc:
name: "Build & Deploy to RC"
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine version
id: config
run: |
VERSION=$(git describe --tags --always 2>/dev/null || echo "rc-$(git rev-parse --short HEAD)")
echo "tag=${VERSION}-rc" >> $GITHUB_OUTPUT
echo "Version: ${VERSION}-rc"
- name: Write deploy key
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
- name: Build and deploy to RC via SSH
env:
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
TAG: ${{ steps.config.outputs.tag }}
run: |
# Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell
# via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the
# remote. Repo variables (vars.*) are substituted inline by Actions before ssh.
ssh -i ~/.ssh/deploy_key -p ${{ vars.RC_SSH_PORT }} \
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
${{ vars.RC_SSH_USERNAME }}@${{ vars.RC_SSH_HOST }} \
"TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF'
set -e
echo 'SSH connected to RC environment'
if [ -z "$TAG" ]; then
echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2
exit 1
fi
HEALTH_FMT='{{.State.Health.Status}}'
echo 'Cleaning Docker build cache...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Pulling source...'
SOURCE_DIR='${{ vars.RC_SOURCE_DIR }}'
if [ ! -d "$SOURCE_DIR/.git" ]; then
git clone -b rc ${{ github.server_url }}/${{ github.repository }}.git "$SOURCE_DIR"
fi
cd "$SOURCE_DIR"
git remote set-url origin ${{ github.server_url }}/${{ github.repository }}.git 2>/dev/null || true
git fetch origin rc
git reset --hard origin/rc
echo "Building image: ${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG"
docker build --no-cache --build-arg GOFLAGS='-p 1' \
--tag "${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG" \
-f Dockerfile .
echo 'Pushing to registry...'
echo "$REGISTRY_TOKEN" | docker login ${{ vars.RC_REGISTRY }} -u ${{ vars.RC_REGISTRY_USER }} --password-stdin
docker push "${{ vars.RC_REGISTRY }}/${{ vars.RC_IMAGE }}:$TAG"
echo 'Restarting RC container...'
cd '${{ vars.RC_COMPOSE_DIR }}'
# Drive the rc service image tag via its compose env-var (no sed on the shared
# file); remove any lingering fixed-name container first, then force-recreate.
docker rm -f '${{ vars.RC_CONTAINER }}' 2>/dev/null || true
${{ vars.RC_TAG_ENV }}="$TAG" docker compose -p '${{ vars.RC_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.RC_CONTAINER }}'
echo 'Health check...'
for i in 1 2 3 4 5 6 7 8; do
sleep 15
if docker inspect --format="$HEALTH_FMT" '${{ vars.RC_CONTAINER }}' 2>/dev/null | grep -q healthy; then
echo 'RC container healthy!'
exit 0
fi
echo "Waiting... (attempt $i/8)"
done
echo 'Health check failed'
docker logs '${{ vars.RC_CONTAINER }}' --tail 20
exit 1
DEPLOY_EOF
- name: Verify RC instance
run: |
sleep 5
curl -sf "${{ vars.RC_HEALTH_URL }}" && echo " RC API healthy"
+4 -4
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/gitleaks.yml.template
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Security
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/gitleaks.yml
# VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
#
+2 -2
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Automation
# VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened
+10 -10
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Notifications
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/notify.yml
# VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure
@@ -15,9 +15,9 @@ name: "Universal: Notifications"
on:
workflow_run:
workflows:
- "Joomla Build & Release"
- "Joomla Extension CI"
- "Deploy"
- "Universal: Build & Release"
- "Joomla: Extension CI"
- "Generic: Project CI"
types:
- completed
@@ -46,13 +46,13 @@ jobs:
WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}"
curl -sS \
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
-H "Title: ${REPO} released" \
-H "Tags: white_check_mark,package" \
-H "Priority: default" \
-H "Click: ${URL}" \
-d "${WORKFLOW} completed successfully." \
"${NTFY_URL}/${NTFY_TOPIC}"
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
- name: Notify on failure
if: github.event.workflow_run.conclusion == 'failure'
@@ -61,10 +61,10 @@ jobs:
WORKFLOW="${{ github.event.workflow_run.name }}"
URL="${{ github.event.workflow_run.html_url }}"
curl -sS \
curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \
-H "Title: ${REPO} workflow failed" \
-H "Tags: x,warning" \
-H "Priority: high" \
-H "Click: ${URL}" \
-d "${WORKFLOW} failed. Check the run for details." \
"${NTFY_URL}/${NTFY_TOPIC}"
"${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)"
+85 -10
View File
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/pr-check.yml.template
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/pr-check.yml
# VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge
@@ -97,6 +97,80 @@ jobs:
echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
# ── Docs Update Gate (main PRs) ─────────────────────────────────────────
require-docs:
name: Require Docs Update
runs-on: ubuntu-latest
# Enforce only on PRs merging into main: README.md + CHANGELOG.md must both be updated.
if: ${{ github.base_ref == 'main' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Require README.md and CHANGELOG.md in the PR diff
run: |
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
CHANGED="$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || true)"
if [ -z "$CHANGED" ]; then
git fetch -q origin "${{ github.base_ref }}" 2>/dev/null || true
CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" 2>/dev/null || true)"
fi
echo "Changed files in PR:"
echo "$CHANGED"
MISSING=""
echo "$CHANGED" | grep -qxE 'README\.md' || MISSING="README.md"
echo "$CHANGED" | grep -qxE 'CHANGELOG\.md' || MISSING="${MISSING:+$MISSING, }CHANGELOG.md"
if [ -n "$MISSING" ]; then
echo "::error::PRs into main must update: ${MISSING}"
{
echo "## Docs Update Required"
echo ""
echo "PRs merging into \`main\` must update both **README.md** and **CHANGELOG.md**."
echo ""
echo "Not updated in this PR: **${MISSING}**"
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "Docs update present (README.md + CHANGELOG.md)"
echo "## Docs Update: Passed" >> "$GITHUB_STEP_SUMMARY"
# ── Wiki Update Reminder (main PRs, non-blocking) ───────────────────────
wiki-reminder:
name: Wiki Update Reminder
runs-on: ubuntu-latest
if: ${{ github.base_ref == 'main' }}
steps:
- name: Remind to update the wiki
env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
run: |
set -uo pipefail
{
echo "## Wiki Update Reminder"
echo ""
echo "Docs are **wiki-first** at MokoConsulting. If this change affects behavior, usage, configuration, or standards, update the repo wiki:"
echo ""
echo "- ${SERVER}/${REPO}/wiki"
echo ""
echo "_Non-blocking reminder._"
} >> "$GITHUB_STEP_SUMMARY"
# Post a single PR comment (idempotent via hidden marker); best-effort, never fails.
API="${SERVER}/api/v1/repos/${REPO}/issues/${PR}/comments"
if [ -n "${TOKEN:-}" ] && [ -n "${PR:-}" ]; then
existing="$(curl -sf -H "Authorization: token ${TOKEN}" "$API" 2>/dev/null | grep -c 'wiki-reminder' || true)"
if [ "${existing:-0}" -eq 0 ]; then
curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -X POST "$API" \
-d '{"body":"<!-- wiki-reminder -->\n\n**Wiki reminder:** docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. _(non-blocking)_"}' >/dev/null 2>&1 || true
fi
fi
echo "Wiki reminder emitted (non-blocking)."
# ── Secret Scanning ──────────────────────────────────────────────────
gitleaks:
name: Secret Scan
@@ -136,7 +210,7 @@ jobs:
- 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)
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --exclude-dir='.git' --exclude-dir='.mokogitea' --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 in source files"
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
@@ -150,7 +224,7 @@ jobs:
- name: Detect platform
id: platform
run: |
# Platform comes from the MokoGitea metadata API (public GET); manifest.xml is no longer used.
# Platform comes from the MokoGitea metadata API (public GET).
API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata"
PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)"
[ -z "$PLATFORM" ] && PLATFORM="generic"
@@ -184,8 +258,9 @@ jobs:
while IFS= read -r -d '' file; do
# Skip vendor, node_modules, and index.html stub files
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
# Check first 10 lines for JEXEC or JPATH guard
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
# Scan the whole file for the JEXEC/JPATH guard: it is placed after
# the SPDX/file-header docblock, which commonly runs past 20 lines.
if ! grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]" "$file"; then
echo "::error file=${file}::Missing JEXEC guard: ${file}"
ERRORS=$((ERRORS + 1))
fi
@@ -276,7 +351,7 @@ jobs:
joomla)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla manifest found (WaaS site)"
echo "::warning::No Joomla manifest found (MokoSuite site)"
exit 0
fi
echo "Manifest: ${MANIFEST}"
@@ -289,7 +364,7 @@ jobs:
# Block legacy raw/branch update server URLs on MokoGitea
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the MokoGitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS"
exit 1
fi
+16 -10
View File
@@ -3,11 +3,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.02.00
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/pre-release.yml
# VERSION: 05.02.01
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release"
@@ -69,7 +69,7 @@ jobs:
run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup mokocli tools
- name: Setup MokoCLI tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
@@ -156,13 +156,19 @@ jobs:
fi
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "mokogitea-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
# Push the bump commit, but do NOT fail the release if the target branch
# is protected and the release identity is not on the push allowlist.
# The build proceeds from the in-tree bumped version regardless; if the
# push is rejected, the next run simply re-bumps from the same base.
if ! git push origin HEAD 2>&1; then
echo "::warning::Version-bump commit could not be pushed (protected branch?). Building from in-tree version ${VERSION} anyway."
fi
}
# Auto-detect element via manifest_element.php
@@ -274,4 +280,4 @@ jobs:
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
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
+5 -4
View File
@@ -3,9 +3,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/rc-revert.yml
# VERSION: 09.23.00
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
@@ -25,7 +25,8 @@ jobs:
runs-on: ubuntu-latest
if: >-
github.event.pull_request.merged == false &&
startsWith(github.event.pull_request.head.ref, 'rc/')
startsWith(github.event.pull_request.head.ref, 'rc/') &&
!startsWith(github.event.repository.name, 'Template-')
steps:
- name: Rename branch
+5 -5
View File
@@ -6,10 +6,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Validation
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/joomla/repo_health.yml.template
# DEFGROUP: MokoGitea.Workflow
# INGROUP: MokoCLI.Validation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
# PATH: /.mokogitea/workflows/repo-health.yml
# VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
# ============================================================================
@@ -88,7 +88,7 @@ jobs:
# Hardcoded authorized users — always allowed
case "$ACTOR" in
jmiller|gitea-actions[bot])
jmiller|mokogitea-actions[bot])
ALLOWED=true
PERMISSION=admin
METHOD="hardcoded allowlist"
File diff suppressed because it is too large Load Diff
-31
View File
@@ -1,31 +0,0 @@
name: Sync Workflows to Repos
on:
push:
branches:
- main
paths:
- '.mokogitea/workflows/**'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout mokocli
uses: actions/checkout@v4
with:
repository: MokoConsulting/mokocli
token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Setup PHP
uses: https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/raw/branch/main/actions/setup-php@v1
with:
php-version: '8.1'
- name: Install dependencies
run: composer install --no-dev --no-interaction
- name: Sync workflows to generic repos
run: php automation/bulk_sync.php --platform generic --org MokoConsulting --workflows-only --auto-merge --token "${{ secrets.MOKOGITEA_TOKEN }}"
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
+3 -2
View File
@@ -3,8 +3,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# DEFGROUP: MokoGitea.Workflow.Template
# INGROUP: MokoCLI.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
# VERSION: 01.00.00
@@ -34,6 +34,7 @@ jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
steps:
- name: Validate version format
@@ -1,81 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
# VERSION: 01.01.00
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
name: "Universal: Workflow Sync Trigger"
on:
workflow_dispatch:
pull_request:
types: [closed]
branches:
- main
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
sync:
name: Sync workflows to live repos
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]'))
steps:
- name: Determine platform from repo name
id: platform
run: |
REPO="${{ github.event.repository.name }}"
case "$REPO" in
Template-Joomla) PLATFORM="joomla" ;;
Template-Dolibarr) PLATFORM="dolibarr" ;;
Template-Go) PLATFORM="go" ;;
Template-MCP) PLATFORM="mcp" ;;
Template-Generic) PLATFORM="" ;;
*) PLATFORM="" ;;
esac
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
echo "Platform: ${PLATFORM:-all}"
- name: Clone mokocli
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
- name: Install PHP
run: |
if ! command -v php &> /dev/null; then
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
fi
- name: Install dependencies
run: |
cd /tmp/mokocli
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
- name: Run workflow sync
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
ARGS="--token ${MOKOGITEA_TOKEN}"
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
ARGS="${ARGS} --phase repos"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ -n "$PLATFORM" ]; then
ARGS="${ARGS} --platform-filter ${PLATFORM}"
fi
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
+7
View File
@@ -3,6 +3,7 @@
## [Unreleased]
### Added
- Metadata platform options are now **admin-configurable** instead of hardcoded: a new **Admin → Metadata** page edits the allowed `platform` values (persisted to `app.ini` `[metadata] PLATFORM_OPTIONS`, default `joomla,dolibarr,go,npm,generic`), and the repo Settings → Metadata platform dropdown reads from it. Changing the taxonomy no longer needs a code change/redeploy. A repo's existing platform value stays selectable even if later removed from the list (#777)
- Org branch protection: repositories now show the inherited organization rules read-only in their Branch Protection settings, with an expandable detail (direct push, force-push, branch deletion, merge restrictions, required approvals, status checks, protected files, and whitelisted teams) — like GitHub surfaces org rulesets in a repo (#727)
- Org branch protection: org-level rules can now also protect against branch deletion (`enable_delete` + delete allowlist teams), mirroring the per-repo delete allowlist (#727)
- Org-level tag protection: protect tag patterns org-wide (e.g. `v*`) with a team allowlist, layered on top of each repo's own protected tags — a tag is controllable only if allowed at both levels (fail-closed). API at `/orgs/{org}/tag_protections`; enforced at the git push/delete hook and the release create/delete paths; shown read-only in the repo Tag settings (#727)
@@ -63,6 +64,12 @@
- Cherry-pick upstream v1.26.4: walk git log context error handling — regression fix (#38185)
### Fixed
- Repo metadata `org` is now **derived from the org profile** (the repository owner) instead of being stored/editable, so it can never drift when an organization is renamed. The `org` column is dropped from `repo_manifest` (migration #368) and the field is derived on read via `Repository.DerivedOrgName` (owner display name, falling back to the handle) across the metadata API, the Settings → Metadata page (now read-only), and the Joomla update-server feed. The API `PUT` and MCP `metadata_update` now ignore `org` (read-only, like the already-derived `display_name`) (#771)
- Admin Branding: uploading a custom **Nav Icon** now also sets the **app icon** (`logo.png`, the PWA / web-manifest icon and navbar fallback), so a branded instance shows its own installable app icon instead of the shipped default; resetting the Nav Icon reverts both (#773)
- Fork server binary now compiles: `routers/api/v1/api.go` called `organization.HasOrgOrUserVisible`, which had been renamed to `IsOwnerVisibleToDoer`; the one missed call site broke `go build` of the entire `routers/api/v1` package (CI's Lint & Validate does not run a full build, so it went unnoticed) (#735)
- Dev deploy workflow: the build/deploy step referenced runner-side values as `\$TAG` / `\$REGISTRY_TOKEN` inside an unquoted SSH heredoc, deferring expansion to the remote shell where those names are unset — the Docker tag collapsed to an empty `mokogitea:` and every dev deploy failed with `invalid reference format`. Runner values are now injected via an ssh env-prefix and the heredoc is quoted so each `$var` expands in exactly one place (#737)
- Repaired unit-test compile and `go vet` failures: `CryptoRandomInt/String/Bytes` now return two values (updated `modules/util/util_test.go`), removed a redundant `&&` condition in `issue_comment.go`, and cleaned up isolated integration-test compile errors (#736)
- Removed a stray `package-lock.json` (13.9k lines) that a `git add -A` had accidentally swept into the org-push-policy branch (#734)
- Org-level branch protection now **layers** with per-repo rules instead of being ignored whenever a repo rule exists. When both an org rule and a repo rule match a branch, the effective rule is the most-restrictive (fail-closed) combination — the org rule is a mandatory floor a repo cannot weaken: allow flags AND'd, gate/require/block flags OR'd, required approvals max'd, status checks and protected-file patterns unioned, whitelists intersected. Previously a repo rule shadowed the org rule entirely at the enforcement choke point (`GetFirstMatchProtectedBranchRule`), letting a repo opt out of org protection (#727)
- Org Teams page: list now renders — the handler wrote `ctx.Data["OrgListTeams"]` but the template reads `.Teams`, so the page showed header/nav but no teams (#720)
- Issue type: now editable after creation for users with issue write permission — the sidebar gated editing on a `FieldEditFlags` data key that was never populated (always read-only); now uses `HasIssuesOrPullsWritePermission` like the priority field (#721)
+2 -1
View File
@@ -1,6 +1,6 @@
# MokoGitea
Custom Gitea fork with enhanced wiki system, DLID licensing, issue statuses, cascade merge, security scanning, org metadata, CI standardization, and project board API.
Custom Gitea fork with enhanced wiki system, DLID licensing, issue statuses, cascade merge, security scanning, org-level governance, org metadata, CI standardization, and project board API.
![Language](https://img.shields.io/badge/Go-00ADD8?style=flat-square&logo=go&logoColor=white) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green?style=flat-square)
@@ -17,6 +17,7 @@ Custom Gitea fork with enhanced wiki system, DLID licensing, issue statuses, cas
- **Default Org Teams** -- auto-create Developers, Reviewers, and CI/CD teams on org creation
- **Org Metadata** -- per-repo metadata API (public GET, admin PUT), platform detection for versioning
- **Branch Protection** -- delete allowlist for protected branches (per-user/team/deploy-key)
- **Org Governance** -- organization-wide rules that layer onto every repository: branch protection as a most-restrictive floor a repo cannot weaken, tag protection (team allowlist), push policy (branch/tag naming, mandatory secret-block, max file size, blocked paths), repository defaults (force-private, PR merge settings), and member email-domain allowlists
- **Project Board API** -- REST endpoints for project columns and cards
- **CI Infrastructure** -- reusable workflows, centralized ci-issue-reporter, standardized MOKOGITEA_TOKEN naming
- **Dev Deploy Gate** -- builds deploy to dev environment first, production checks dev health
+21 -2
View File
@@ -28,19 +28,28 @@ type OrgProtectedBranch struct {
CanPush bool `xorm:"NOT NULL DEFAULT false"`
EnableWhitelist bool `xorm:"NOT NULL DEFAULT false"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
CanForcePush bool `xorm:"NOT NULL DEFAULT false"`
EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false"`
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
CanDelete bool `xorm:"NOT NULL DEFAULT false"`
EnableDeleteAllowlist bool `xorm:"NOT NULL DEFAULT false"`
DeleteAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
DeleteAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
DeleteAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
StatusCheckContexts []string `xorm:"JSON TEXT"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
@@ -84,8 +93,9 @@ func (o *OrgProtectedBranch) Match(branchName string) bool {
}
// ToProtectedBranch converts an org-level rule to a ProtectedBranch for use
// in the standard enforcement path. Fields that are user-scoped (WhitelistUserIDs etc.)
// are left empty because org rules only reference teams.
// in the standard enforcement path. Both team-scoped and user-scoped allowlists
// (plus the Actions-bot toggles) are carried across; deploy-key allowances remain
// repo-only because an org rule cannot express them.
func (o *OrgProtectedBranch) ToProtectedBranch() *ProtectedBranch {
return &ProtectedBranch{
ID: o.ID,
@@ -94,19 +104,28 @@ func (o *OrgProtectedBranch) ToProtectedBranch() *ProtectedBranch {
CanPush: o.CanPush,
EnableWhitelist: o.EnableWhitelist,
WhitelistTeamIDs: o.WhitelistTeamIDs,
WhitelistUserIDs: o.WhitelistUserIDs,
WhitelistActionsUser: o.WhitelistActionsUser,
EnableMergeWhitelist: o.EnableMergeWhitelist,
MergeWhitelistTeamIDs: o.MergeWhitelistTeamIDs,
MergeWhitelistUserIDs: o.MergeWhitelistUserIDs,
MergeWhitelistActionsUser: o.MergeWhitelistActionsUser,
CanForcePush: o.CanForcePush,
EnableForcePushAllowlist: o.EnableForcePushAllowlist,
ForcePushAllowlistTeamIDs: o.ForcePushAllowlistTeamIDs,
ForcePushAllowlistUserIDs: o.ForcePushAllowlistUserIDs,
ForcePushAllowlistActionsUser: o.ForcePushAllowlistActionsUser,
CanDelete: o.CanDelete,
EnableDeleteAllowlist: o.EnableDeleteAllowlist,
DeleteAllowlistTeamIDs: o.DeleteAllowlistTeamIDs,
DeleteAllowlistUserIDs: o.DeleteAllowlistUserIDs,
DeleteAllowlistActionsUser: o.DeleteAllowlistActionsUser,
EnableStatusCheck: o.EnableStatusCheck,
StatusCheckContexts: o.StatusCheckContexts,
RequiredApprovals: o.RequiredApprovals,
EnableApprovalsWhitelist: o.EnableApprovalsWhitelist,
ApprovalsWhitelistTeamIDs: o.ApprovalsWhitelistTeamIDs,
ApprovalsWhitelistUserIDs: o.ApprovalsWhitelistUserIDs,
BlockOnRejectedReviews: o.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: o.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: o.BlockOnOutdatedBranch,
+3 -1
View File
@@ -133,8 +133,10 @@ func getFirstMatchOrgProtectedBranchRule(ctx context.Context, repoID int64, bran
orgRule, err := FindOrgBranchRuleForBranch(ctx, owner.ID, branchName)
if err != nil {
// Fail closed: propagate the error so callers keep the org floor in force
// rather than silently falling back to the repo rule on a transient error.
log.Error("FindOrgBranchRuleForBranch: %v", err)
return nil, nil
return nil, err
}
if orgRule == nil {
return nil, nil
+31 -7
View File
@@ -26,27 +26,32 @@ func mergeMostRestrictive(repoRule, orgRule *ProtectedBranch) *ProtectedBranch {
eff.CanPush = repoRule.CanPush && orgRule.CanPush
eff.EnableWhitelist, eff.WhitelistUserIDs = mergeAllowlist(repoRule.EnableWhitelist, repoRule.WhitelistUserIDs, orgRule.EnableWhitelist, orgRule.WhitelistUserIDs)
_, eff.WhitelistTeamIDs = mergeAllowlist(repoRule.EnableWhitelist, repoRule.WhitelistTeamIDs, orgRule.EnableWhitelist, orgRule.WhitelistTeamIDs)
eff.WhitelistDeployKeys = repoRule.WhitelistDeployKeys && orgRule.WhitelistDeployKeys
eff.WhitelistActionsUser = repoRule.WhitelistActionsUser && orgRule.WhitelistActionsUser
// Deploy-key allowances are still not expressible in an OrgProtectedBranch, so the
// org rule imposes no constraint on them; carry the repo value through unchanged.
// ANDing against the org side's always-false zero value would silently lock out
// every deploy-key push in any org that has a matching branch rule (see #727 review).
// The Actions-bot toggle IS now expressible org-side, so merge it most-restrictively.
eff.WhitelistDeployKeys = repoRule.WhitelistDeployKeys
eff.WhitelistActionsUser = mergeAllowFlag(repoRule.EnableWhitelist, repoRule.WhitelistActionsUser, orgRule.EnableWhitelist, orgRule.WhitelistActionsUser)
// Force push.
eff.CanForcePush = repoRule.CanForcePush && orgRule.CanForcePush
eff.EnableForcePushAllowlist, eff.ForcePushAllowlistUserIDs = mergeAllowlist(repoRule.EnableForcePushAllowlist, repoRule.ForcePushAllowlistUserIDs, orgRule.EnableForcePushAllowlist, orgRule.ForcePushAllowlistUserIDs)
_, eff.ForcePushAllowlistTeamIDs = mergeAllowlist(repoRule.EnableForcePushAllowlist, repoRule.ForcePushAllowlistTeamIDs, orgRule.EnableForcePushAllowlist, orgRule.ForcePushAllowlistTeamIDs)
eff.ForcePushAllowlistDeployKeys = repoRule.ForcePushAllowlistDeployKeys && orgRule.ForcePushAllowlistDeployKeys
eff.ForcePushAllowlistActionsUser = repoRule.ForcePushAllowlistActionsUser && orgRule.ForcePushAllowlistActionsUser
eff.ForcePushAllowlistDeployKeys = repoRule.ForcePushAllowlistDeployKeys
eff.ForcePushAllowlistActionsUser = mergeAllowFlag(repoRule.EnableForcePushAllowlist, repoRule.ForcePushAllowlistActionsUser, orgRule.EnableForcePushAllowlist, orgRule.ForcePushAllowlistActionsUser)
// Delete.
eff.CanDelete = repoRule.CanDelete && orgRule.CanDelete
eff.EnableDeleteAllowlist, eff.DeleteAllowlistUserIDs = mergeAllowlist(repoRule.EnableDeleteAllowlist, repoRule.DeleteAllowlistUserIDs, orgRule.EnableDeleteAllowlist, orgRule.DeleteAllowlistUserIDs)
_, eff.DeleteAllowlistTeamIDs = mergeAllowlist(repoRule.EnableDeleteAllowlist, repoRule.DeleteAllowlistTeamIDs, orgRule.EnableDeleteAllowlist, orgRule.DeleteAllowlistTeamIDs)
eff.DeleteAllowlistDeployKeys = repoRule.DeleteAllowlistDeployKeys && orgRule.DeleteAllowlistDeployKeys
eff.DeleteAllowlistActionsUser = repoRule.DeleteAllowlistActionsUser && orgRule.DeleteAllowlistActionsUser
eff.DeleteAllowlistDeployKeys = repoRule.DeleteAllowlistDeployKeys
eff.DeleteAllowlistActionsUser = mergeAllowFlag(repoRule.EnableDeleteAllowlist, repoRule.DeleteAllowlistActionsUser, orgRule.EnableDeleteAllowlist, orgRule.DeleteAllowlistActionsUser)
// Merge whitelist.
eff.EnableMergeWhitelist, eff.MergeWhitelistUserIDs = mergeAllowlist(repoRule.EnableMergeWhitelist, repoRule.MergeWhitelistUserIDs, orgRule.EnableMergeWhitelist, orgRule.MergeWhitelistUserIDs)
_, eff.MergeWhitelistTeamIDs = mergeAllowlist(repoRule.EnableMergeWhitelist, repoRule.MergeWhitelistTeamIDs, orgRule.EnableMergeWhitelist, orgRule.MergeWhitelistTeamIDs)
eff.MergeWhitelistActionsUser = repoRule.MergeWhitelistActionsUser && orgRule.MergeWhitelistActionsUser
eff.MergeWhitelistActionsUser = mergeAllowFlag(repoRule.EnableMergeWhitelist, repoRule.MergeWhitelistActionsUser, orgRule.EnableMergeWhitelist, orgRule.MergeWhitelistActionsUser)
// Status checks.
eff.EnableStatusCheck = repoRule.EnableStatusCheck || orgRule.EnableStatusCheck
@@ -89,6 +94,25 @@ func mergeAllowlist(aEnabled bool, aIDs []int64, bEnabled bool, bIDs []int64) (b
}
}
// mergeAllowFlag combines two "allow the Actions bot" toggles under most-restrictive
// semantics, mirroring mergeAllowlist. A toggle only grants access when its Enable flag
// is set; a disabled allowlist means "everyone", so it imposes no constraint. Therefore:
// if both allowlists are enabled the bot is allowed only when BOTH sides allow it; if
// only one is enabled that side's flag governs; if neither is enabled the flag is
// irrelevant (fall back to the repo/a value).
func mergeAllowFlag(aEnabled, aFlag, bEnabled, bFlag bool) bool {
switch {
case aEnabled && bEnabled:
return aFlag && bFlag
case aEnabled:
return aFlag
case bEnabled:
return bFlag
default:
return aFlag
}
}
func intersectInt64(a, b []int64) []int64 {
if len(a) == 0 || len(b) == 0 {
return nil
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package git
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMergeAllowFlag(t *testing.T) {
cases := []struct {
name string
aEnabled bool
aFlag bool
bEnabled bool
bFlag bool
expected bool
}{
// Both allowlists enabled: bot allowed only when BOTH allow it.
{"both enabled, both allow", true, true, true, true, true},
{"both enabled, a denies", true, false, true, true, false},
{"both enabled, b denies", true, true, true, false, false},
{"both enabled, both deny", true, false, true, false, false},
// Only one side enabled: that side governs.
{"only a enabled, a allows", true, true, false, false, true},
{"only a enabled, a denies", true, false, false, true, false},
{"only b enabled, b allows", false, false, true, true, true},
{"only b enabled, b denies", false, true, true, false, false},
// Neither enabled: flag irrelevant, falls back to a's flag.
{"neither enabled, a true", false, true, false, false, true},
{"neither enabled, a false", false, false, false, true, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
assert.Equal(t, c.expected, mergeAllowFlag(c.aEnabled, c.aFlag, c.bEnabled, c.bFlag))
})
}
}
func TestMergeMostRestrictiveUserIDsIntersect(t *testing.T) {
repoRule := &ProtectedBranch{
EnableWhitelist: true,
WhitelistUserIDs: []int64{1, 2, 3},
// Deploy keys are repo-only and must always pass through unchanged.
WhitelistDeployKeys: true,
ForcePushAllowlistDeployKeys: true,
DeleteAllowlistDeployKeys: true,
}
orgRule := &ProtectedBranch{
EnableWhitelist: true,
WhitelistUserIDs: []int64{2, 3, 4},
// Org rule cannot express deploy keys; these zero values must NOT override repo.
}
eff := mergeMostRestrictive(repoRule, orgRule)
// User IDs are intersected when both allowlists are enabled.
assert.True(t, eff.EnableWhitelist)
assert.ElementsMatch(t, []int64{2, 3}, eff.WhitelistUserIDs)
// Deploy-key allowances still pass through from the repo rule.
assert.True(t, eff.WhitelistDeployKeys)
assert.True(t, eff.ForcePushAllowlistDeployKeys)
assert.True(t, eff.DeleteAllowlistDeployKeys)
}
func TestMergeMostRestrictiveActionsUserFlags(t *testing.T) {
// Both sides enable their allowlist and both allow the bot -> allowed.
repoRule := &ProtectedBranch{
EnableWhitelist: true,
WhitelistActionsUser: true,
EnableForcePushAllowlist: true,
// force-push: org denies -> effective deny.
ForcePushAllowlistActionsUser: true,
EnableDeleteAllowlist: true,
DeleteAllowlistActionsUser: true,
// merge: repo allowlist disabled, org enabled+denies -> org governs (deny).
EnableMergeWhitelist: false,
MergeWhitelistActionsUser: true,
}
orgRule := &ProtectedBranch{
EnableWhitelist: true,
WhitelistActionsUser: true,
EnableForcePushAllowlist: true,
ForcePushAllowlistActionsUser: false,
EnableDeleteAllowlist: true,
DeleteAllowlistActionsUser: true,
EnableMergeWhitelist: true,
MergeWhitelistActionsUser: false,
}
eff := mergeMostRestrictive(repoRule, orgRule)
assert.True(t, eff.WhitelistActionsUser, "push: both allow")
assert.False(t, eff.ForcePushAllowlistActionsUser, "force-push: org denies")
assert.True(t, eff.DeleteAllowlistActionsUser, "delete: both allow")
assert.False(t, eff.MergeWhitelistActionsUser, "merge: org enabled and denies")
}
+2
View File
@@ -444,6 +444,8 @@ func prepareMigrationTasks() []*migration {
newMigration(364, "Add org push policy table", v1_27.AddOrgPushPolicyTable),
newMigration(365, "Add org repo defaults table", v1_27.AddOrgRepoDefaultsTable),
newMigration(366, "Add org email domain policy table", v1_27.AddOrgEmailDomainPolicyTable),
newMigration(367, "Add user allowlists to org protected branch", v1_27.AddUserAllowlistsToOrgProtectedBranch),
newMigration(368, "Drop org from repo manifest (derived from org profile)", v1_27.DropOrgFromRepoManifest),
}
return preparedMigrations
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
)
// AddRepoManifestTable creates the repo_manifest table for storing
// mokoplatform manifest settings per repository.
// mokocli manifest settings per repository.
func AddRepoManifestTable(x *xorm.Engine) error {
type RepoManifest struct {
ID int64 `xorm:"pk autoincr"`
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// AddUserAllowlistsToOrgProtectedBranch adds per-user allowlist columns (username or
// email resolved to user IDs) plus the "allow Actions bot" toggles to org-level branch
// protection rules for the push, merge, force-push and delete categories, mirroring the
// per-repo user allowlists so an org rule can also grant access to individual users and
// the Actions bot. See issue #727.
func AddUserAllowlistsToOrgProtectedBranch(x *xorm.Engine) error {
type OrgProtectedBranch struct {
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
DeleteAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
DeleteAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
}
return x.Sync(new(OrgProtectedBranch))
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/migrations/base"
"xorm.io/xorm"
)
// DropOrgFromRepoManifest removes the org column from repo_manifest. The
// organization is now derived from the org profile (repository owner) on read
// rather than stored, so it never drifts on rename. See issue #771.
func DropOrgFromRepoManifest(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()
return base.DropTableColumns(sess, "repo_manifest", "org")
}
+17
View File
@@ -486,6 +486,23 @@ func (repo *Repository) LoadOwner(ctx context.Context) (err error) {
return err
}
// DerivedOrgName returns the organization name derived from the org profile
// (the repository owner). It prefers the owner's display name (FullName) and
// falls back to the owner handle (Name / OwnerName). The organization is always
// derived from the profile rather than stored, so it never drifts on rename.
func (repo *Repository) DerivedOrgName(ctx context.Context) string {
if err := repo.LoadOwner(ctx); err != nil || repo.Owner == nil {
return repo.OwnerName
}
if repo.Owner.FullName != "" {
return repo.Owner.FullName
}
if repo.Owner.Name != "" {
return repo.Owner.Name
}
return repo.OwnerName
}
// MustOwner always returns a valid *user_model.User object to avoid
// conceptually impossible error handling.
// It creates a fake object that contains error details
+23 -21
View File
@@ -15,50 +15,52 @@ func init() {
db.RegisterModel(new(RepoMetadata))
}
// RepoMetadata stores mokoplatform metadata settings for a repository.
// These fields correspond to the .mokogitea/manifest.xml schema and are
// exposed via API for use by Actions workflows and the mokoplatform CLI.
// RepoMetadata stores mokocli metadata settings for a repository.
// These first-class fields are the authoritative repository metadata,
// exposed via API for use by Actions workflows and mokocli.
type RepoMetadata struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"`
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"`
// identity section
Name string `xorm:"TEXT 'name'"` // project name
Org string `xorm:"TEXT 'org'"` // organization name
Description string `xorm:"TEXT 'description'"` // project description
// NOTE: organization is intentionally NOT stored here. It is derived from
// the org profile (repository owner) on read via Repository.DerivedOrgName,
// so it can never drift from the owner on rename. See issue #771.
Name string `xorm:"TEXT 'name'"` // project name
Description string `xorm:"TEXT 'description'"` // project description
LicenseSPDX string `xorm:"VARCHAR(50) 'license_spdx'"` // SPDX identifier, e.g. "GPL-3.0-or-later"
LicenseName string `xorm:"TEXT 'license_name'"` // human-readable license name
LicenseName string `xorm:"TEXT 'license_name'"` // human-readable license name
// governance section
Platform string `xorm:"VARCHAR(50) 'platform'"` // go, php, node, python, etc.
StandardsVersion string `xorm:"VARCHAR(20) 'standards_version'"` // mokoplatform standards version
StandardsSource string `xorm:"TEXT 'standards_source'"` // URL to standards repo
StandardsVersion string `xorm:"VARCHAR(20) 'standards_version'"` // mokocli standards version
StandardsSource string `xorm:"TEXT 'standards_source'"` // URL to standards repo
// versioning
VersionPrefix string `xorm:"TEXT 'version_prefix'"` // tag prefix stripped for version display, e.g. "v1.26.1-moko."
ElementName string `xorm:"TEXT 'element_name'"` // full element name override, e.g. "pkg_mokowaas" (auto-constructed if empty)
VersionPrefix string `xorm:"TEXT 'version_prefix'"` // tag prefix stripped for version display, e.g. "v1.26.1-moko."
ElementName string `xorm:"TEXT 'element_name'"` // full element name override, e.g. "pkg_mokowaas" (auto-constructed if empty)
// distribution metadata (used by update server feed generation)
Maintainer string `xorm:"TEXT 'maintainer'"` // maintainer/author name
MaintainerURL string `xorm:"TEXT 'maintainer_url'"` // maintainer website
InfoURL string `xorm:"TEXT 'info_url'"` // extension info/product page URL
TargetVersion string `xorm:"TEXT 'target_version'"` // target platform version regex, e.g. "(5|6)\..*"
Maintainer string `xorm:"TEXT 'maintainer'"` // maintainer/author name
MaintainerURL string `xorm:"TEXT 'maintainer_url'"` // maintainer website
InfoURL string `xorm:"TEXT 'info_url'"` // extension info/product page URL
TargetVersion string `xorm:"TEXT 'target_version'"` // target platform version regex, e.g. "(5|6)\..*"
PHPMinimum string `xorm:"VARCHAR(20) 'php_minimum'"` // minimum PHP version, e.g. "8.1"
// build section
Language string `xorm:"VARCHAR(50) 'language'"` // Go, PHP, TypeScript, etc.
Language string `xorm:"VARCHAR(50) 'language'"` // Go, PHP, TypeScript, etc.
ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library, file
EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path
EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path
// deploy section
DeployHost string `xorm:"VARCHAR(255) 'deploy_host'"` // SSH host for deploy
DeployPort string `xorm:"VARCHAR(10) 'deploy_port'"` // SSH port (default 2918)
DeployUser string `xorm:"VARCHAR(100) 'deploy_user'"` // SSH user
DeployPath string `xorm:"TEXT 'deploy_path'"` // remote path for source/compose
DeployPath string `xorm:"TEXT 'deploy_path'"` // remote path for source/compose
DockerImage string `xorm:"VARCHAR(255) 'docker_image'"` // e.g. mokoconsulting/mokogitea
DockerRegistry string `xorm:"VARCHAR(255) 'docker_registry'"` // e.g. git.mokoconsulting.tech
ContainerName string `xorm:"VARCHAR(100) 'container_name'"` // Docker container name
HealthURL string `xorm:"TEXT 'health_url'"` // health check URL after deploy
HealthURL string `xorm:"TEXT 'health_url'"` // health check URL after deploy
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
// Metadata holds instance-level settings for the repository metadata feature.
var Metadata = struct {
// PlatformOptions is the admin-configurable list of allowed values for the
// repo metadata "platform" field. Editable via Admin -> Metadata (persisted
// to app.ini [metadata] PLATFORM_OPTIONS) so changing the taxonomy needs no
// code change or redeploy. See issue #777.
PlatformOptions []string
}{
PlatformOptions: []string{"joomla", "dolibarr", "go", "npm", "generic"},
}
func loadMetadataFrom(rootCfg ConfigProvider) {
sec := rootCfg.Section("metadata")
if opts := sec.Key("PLATFORM_OPTIONS").Strings(","); len(opts) > 0 {
Metadata.PlatformOptions = opts
}
}
+1
View File
@@ -168,6 +168,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
}
loadUIFrom(cfg)
loadAdminFrom(cfg)
loadMetadataFrom(cfg)
loadAPIFrom(cfg)
loadMetricsFrom(cfg)
loadCamoFrom(cfg)
+27
View File
@@ -14,19 +14,28 @@ type OrgBranchProtection struct {
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistActionsUser bool `json:"push_whitelist_actions_user"`
EnableForcePush bool `json:"enable_force_push"`
EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"`
ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"`
ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"`
ForcePushAllowlistActionsUser bool `json:"force_push_allowlist_actions_user"`
EnableDelete bool `json:"enable_delete"`
EnableDeleteAllowlist bool `json:"enable_delete_allowlist"`
DeleteAllowlistTeams []string `json:"delete_allowlist_teams"`
DeleteAllowlistUsernames []string `json:"delete_allowlist_usernames"`
DeleteAllowlistActionsUser bool `json:"delete_allowlist_actions_user"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistActionsUser bool `json:"merge_whitelist_actions_user"`
EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
@@ -49,19 +58,28 @@ type CreateOrgBranchProtectionOption struct {
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistActionsUser bool `json:"push_whitelist_actions_user"`
EnableForcePush bool `json:"enable_force_push"`
EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"`
ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"`
ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"`
ForcePushAllowlistActionsUser bool `json:"force_push_allowlist_actions_user"`
EnableDelete bool `json:"enable_delete"`
EnableDeleteAllowlist bool `json:"enable_delete_allowlist"`
DeleteAllowlistTeams []string `json:"delete_allowlist_teams"`
DeleteAllowlistUsernames []string `json:"delete_allowlist_usernames"`
DeleteAllowlistActionsUser bool `json:"delete_allowlist_actions_user"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistActionsUser bool `json:"merge_whitelist_actions_user"`
EnableStatusCheck bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals int64 `json:"required_approvals"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
@@ -79,19 +97,28 @@ type EditOrgBranchProtectionOption struct {
EnablePush *bool `json:"enable_push"`
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistActionsUser *bool `json:"push_whitelist_actions_user"`
EnableForcePush *bool `json:"enable_force_push"`
EnableForcePushAllowlist *bool `json:"enable_force_push_allowlist"`
ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"`
ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"`
ForcePushAllowlistActionsUser *bool `json:"force_push_allowlist_actions_user"`
EnableDelete *bool `json:"enable_delete"`
EnableDeleteAllowlist *bool `json:"enable_delete_allowlist"`
DeleteAllowlistTeams []string `json:"delete_allowlist_teams"`
DeleteAllowlistUsernames []string `json:"delete_allowlist_usernames"`
DeleteAllowlistActionsUser *bool `json:"delete_allowlist_actions_user"`
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistActionsUser *bool `json:"merge_whitelist_actions_user"`
EnableStatusCheck *bool `json:"enable_status_check"`
StatusCheckContexts []string `json:"status_check_contexts"`
RequiredApprovals *int64 `json:"required_approvals"`
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
+18 -10
View File
@@ -86,31 +86,35 @@ func Test_NormalizeEOL(t *testing.T) {
}
func Test_RandomInt(t *testing.T) {
randInt := CryptoRandomInt(255)
randInt, err := CryptoRandomInt(255)
assert.NoError(t, err)
assert.GreaterOrEqual(t, randInt, int64(0))
assert.LessOrEqual(t, randInt, int64(255))
}
func Test_RandomString(t *testing.T) {
str1 := CryptoRandomString(32)
var err error
str1, err := CryptoRandomString(32)
assert.NoError(t, err)
matches, err := regexp.MatchString(`^[a-zA-Z0-9]{32}$`, str1)
assert.NoError(t, err)
assert.True(t, matches)
str2 := CryptoRandomString(32)
str2, err := CryptoRandomString(32)
assert.NoError(t, err)
matches, err = regexp.MatchString(`^[a-zA-Z0-9]{32}$`, str1)
assert.NoError(t, err)
assert.True(t, matches)
assert.NotEqual(t, str1, str2)
str3 := CryptoRandomString(256)
str3, err := CryptoRandomString(256)
assert.NoError(t, err)
matches, err = regexp.MatchString(`^[a-zA-Z0-9]{256}$`, str3)
assert.NoError(t, err)
assert.True(t, matches)
str4 := CryptoRandomString(256)
str4, err := CryptoRandomString(256)
assert.NoError(t, err)
matches, err = regexp.MatchString(`^[a-zA-Z0-9]{256}$`, str4)
assert.NoError(t, err)
assert.True(t, matches)
@@ -119,15 +123,19 @@ func Test_RandomString(t *testing.T) {
}
func Test_RandomBytes(t *testing.T) {
bytes1 := CryptoRandomBytes(32)
bytes1, err := CryptoRandomBytes(32)
assert.NoError(t, err)
bytes2 := CryptoRandomBytes(32)
bytes2, err := CryptoRandomBytes(32)
assert.NoError(t, err)
assert.NotEqual(t, bytes1, bytes2)
bytes3 := CryptoRandomBytes(256)
bytes3, err := CryptoRandomBytes(256)
assert.NoError(t, err)
bytes4 := CryptoRandomBytes(256)
bytes4, err := CryptoRandomBytes(256)
assert.NoError(t, err)
assert.NotEqual(t, bytes3, bytes4)
}
+2 -2
View File
@@ -2464,7 +2464,7 @@
"repo.settings.protect_force_push_allowlist_teams": "Allowlisted teams for force pushing:",
"repo.settings.protect_force_push_allowlist_deploy_keys": "Allowlist deploy keys with push access to force push.",
"repo.settings.protect_force_push_allowlist_actions_user": "Allowlist actions bot user to force push.",
"repo.settings.event_delete": "Branch Deletion",
"repo.settings.protect_branch_deletion": "Branch Deletion",
"repo.settings.protect_disable_delete": "Disable Deletion",
"repo.settings.protect_disable_delete_desc": "This branch cannot be deleted.",
"repo.settings.protect_enable_delete_all": "Enable Deletion",
@@ -2772,7 +2772,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 MokoPlatform manifest. These are accessible via API for Actions workflows and the MokoPlatform CLI.",
"repo.settings.manifest_desc": "Project identity, governance, and build settings for the repository. These are accessible via API for Actions workflows and MokoCLI.",
"repo.settings.manifest_identity": "Identity",
"repo.settings.manifest_name": "Project Name",
"repo.settings.manifest_element_name": "Element Name",
+24 -26
View File
@@ -516,7 +516,7 @@ func reqOrgVisible() func(ctx *context.APIContext) {
ctx.APIErrorInternal(errors.New("reqOrgVisible: unprepared context"))
return
}
if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) {
if !organization.IsOwnerVisibleToDoer(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) {
ctx.APIErrorNotFound()
return
}
@@ -1543,8 +1543,6 @@ func Routes() *web.Router {
}, reqAnyRepoReader())
m.Get("/metadata", repo.GetRepoMetadata)
m.Put("/metadata", reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
m.Get("/manifest", repo.GetRepoMetadata) // backward compat
m.Put("/manifest", reqToken(), reqAdmin(), repo.UpdateRepoMetadata)
// MokoGitea badge engine
m.Get("/badge/{type}.svg", repo.GetRepoBadge)
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
@@ -1698,29 +1696,29 @@ func Routes() *web.Router {
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
})
// m.Group("/projects", func() {
// m.Combo("").Get(repo.ListProjects).
// Post(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.CreateProjectOption{}), repo.CreateProject)
// m.Group("/{id}", func() {
// m.Combo("").Get(repo.GetProject).
// Patch(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.EditProjectOption{}), repo.EditProject).
// Delete(reqToken(), reqRepoWriter(unit.TypeProjects), repo.DeleteProject)
// m.Post("/{action}", reqToken(), reqRepoWriter(unit.TypeProjects), repo.ChangeProjectStatus)
// m.Group("/columns", func() {
// m.Combo("").Get(repo.ListProjectColumns).
// Post(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.CreateProjectColumnOption{}), repo.CreateProjectColumn)
// m.Group("/{columnId}", func() {
// m.Delete("", reqToken(), reqRepoWriter(unit.TypeProjects), repo.DeleteProjectColumn)
// m.Combo("/issues").Get(repo.ListProjectColumnIssues).
// Post(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.AddProjectColumnIssueOption{}), repo.AddIssueToColumn)
// })
// })
// m.Group("/issues/{issueId}", func() {
// m.Patch("/move", reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.MoveProjectColumnIssueOption{}), repo.MoveIssueOnColumn)
// m.Delete("", reqToken(), reqRepoWriter(unit.TypeProjects), repo.RemoveIssueFromProject)
// })
// })
// })
// m.Group("/projects", func() {
// m.Combo("").Get(repo.ListProjects).
// Post(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.CreateProjectOption{}), repo.CreateProject)
// m.Group("/{id}", func() {
// m.Combo("").Get(repo.GetProject).
// Patch(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.EditProjectOption{}), repo.EditProject).
// Delete(reqToken(), reqRepoWriter(unit.TypeProjects), repo.DeleteProject)
// m.Post("/{action}", reqToken(), reqRepoWriter(unit.TypeProjects), repo.ChangeProjectStatus)
// m.Group("/columns", func() {
// m.Combo("").Get(repo.ListProjectColumns).
// Post(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.CreateProjectColumnOption{}), repo.CreateProjectColumn)
// m.Group("/{columnId}", func() {
// m.Delete("", reqToken(), reqRepoWriter(unit.TypeProjects), repo.DeleteProjectColumn)
// m.Combo("/issues").Get(repo.ListProjectColumnIssues).
// Post(reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.AddProjectColumnIssueOption{}), repo.AddIssueToColumn)
// })
// })
// m.Group("/issues/{issueId}", func() {
// m.Patch("/move", reqToken(), reqRepoWriter(unit.TypeProjects), bind(api.MoveProjectColumnIssueOption{}), repo.MoveIssueOnColumn)
// m.Delete("", reqToken(), reqRepoWriter(unit.TypeProjects), repo.RemoveIssueFromProject)
// })
// })
// })
// Repo custom fields (repo-scoped key-value metadata)
m.Group("/custom-fields", func() {
m.Get("", repo.GetRepoCustomFields)
+133
View File
@@ -8,6 +8,7 @@ import (
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
@@ -36,6 +37,18 @@ func toAPIOrgBranchProtection(ctx *context.APIContext, rule *git_model.OrgProtec
return names
}
resolveUserNames := func(ids []int64) []string {
names := make([]string, 0, len(ids))
for _, id := range ids {
u, err := user_model.GetUserByID(ctx, id)
if err != nil {
continue
}
names = append(names, u.Name)
}
return names
}
return &api.OrgBranchProtection{
ID: rule.ID,
OrgID: rule.OrgID,
@@ -44,19 +57,28 @@ func toAPIOrgBranchProtection(ctx *context.APIContext, rule *git_model.OrgProtec
EnablePush: rule.CanPush,
EnablePushWhitelist: rule.EnableWhitelist,
PushWhitelistTeams: resolveTeamNames(rule.WhitelistTeamIDs),
PushWhitelistUsernames: resolveUserNames(rule.WhitelistUserIDs),
PushWhitelistActionsUser: rule.WhitelistActionsUser,
EnableForcePush: rule.CanForcePush,
EnableForcePushAllowlist: rule.EnableForcePushAllowlist,
ForcePushAllowlistTeams: resolveTeamNames(rule.ForcePushAllowlistTeamIDs),
ForcePushAllowlistUsernames: resolveUserNames(rule.ForcePushAllowlistUserIDs),
ForcePushAllowlistActionsUser: rule.ForcePushAllowlistActionsUser,
EnableDelete: rule.CanDelete,
EnableDeleteAllowlist: rule.EnableDeleteAllowlist,
DeleteAllowlistTeams: resolveTeamNames(rule.DeleteAllowlistTeamIDs),
DeleteAllowlistUsernames: resolveUserNames(rule.DeleteAllowlistUserIDs),
DeleteAllowlistActionsUser: rule.DeleteAllowlistActionsUser,
EnableMergeWhitelist: rule.EnableMergeWhitelist,
MergeWhitelistTeams: resolveTeamNames(rule.MergeWhitelistTeamIDs),
MergeWhitelistUsernames: resolveUserNames(rule.MergeWhitelistUserIDs),
MergeWhitelistActionsUser: rule.MergeWhitelistActionsUser,
EnableStatusCheck: rule.EnableStatusCheck,
StatusCheckContexts: rule.StatusCheckContexts,
RequiredApprovals: rule.RequiredApprovals,
EnableApprovalsWhitelist: rule.EnableApprovalsWhitelist,
ApprovalsWhitelistTeams: resolveTeamNames(rule.ApprovalsWhitelistTeamIDs),
ApprovalsWhitelistUsernames: resolveUserNames(rule.ApprovalsWhitelistUserIDs),
BlockOnRejectedReviews: rule.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: rule.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: rule.BlockOnOutdatedBranch,
@@ -88,6 +110,41 @@ func resolveTeamIDs(ctx *context.APIContext, orgID int64, names []string) ([]int
return ids, true
}
// resolveUserIDs converts a list of user identifiers (each a username or an email
// address) to deduped user IDs, returning 422 on the first unknown identifier. Each
// entry is looked up by username first, then falls back to an activated email address.
func resolveUserIDs(ctx *context.APIContext, names []string) ([]int64, bool) {
if len(names) == 0 {
return nil, true
}
seen := make(map[int64]struct{}, len(names))
ids := make([]int64, 0, len(names))
for _, name := range names {
u, err := user_model.GetUserByName(ctx, name)
if err != nil {
if !user_model.IsErrUserNotExist(err) {
ctx.APIErrorInternal(err)
return nil, false
}
u, err = user_model.GetUserByEmail(ctx, name)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.APIError(http.StatusUnprocessableEntity, "unknown user: "+name)
} else {
ctx.APIErrorInternal(err)
}
return nil, false
}
}
if _, dup := seen[u.ID]; dup {
continue
}
seen[u.ID] = struct{}{}
ids = append(ids, u.ID)
}
return ids, true
}
// ListOrgBranchProtections list org-level branch protection rules
func ListOrgBranchProtections(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/branch_protections organization orgListBranchProtections
@@ -218,6 +275,26 @@ func CreateOrgBranchProtection(ctx *context.APIContext) {
if !ok {
return
}
pushUsers, ok := resolveUserIDs(ctx, form.PushWhitelistUsernames)
if !ok {
return
}
forcePushUsers, ok := resolveUserIDs(ctx, form.ForcePushAllowlistUsernames)
if !ok {
return
}
mergeUsers, ok := resolveUserIDs(ctx, form.MergeWhitelistUsernames)
if !ok {
return
}
approvalsUsers, ok := resolveUserIDs(ctx, form.ApprovalsWhitelistUsernames)
if !ok {
return
}
deleteUsers, ok := resolveUserIDs(ctx, form.DeleteAllowlistUsernames)
if !ok {
return
}
rule := &git_model.OrgProtectedBranch{
OrgID: orgID,
@@ -226,19 +303,28 @@ func CreateOrgBranchProtection(ctx *context.APIContext) {
CanPush: form.EnablePush,
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
WhitelistTeamIDs: pushTeams,
WhitelistUserIDs: pushUsers,
WhitelistActionsUser: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistActionsUser,
CanForcePush: form.EnablePush && form.EnableForcePush,
EnableForcePushAllowlist: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist,
ForcePushAllowlistTeamIDs: forcePushTeams,
ForcePushAllowlistUserIDs: forcePushUsers,
ForcePushAllowlistActionsUser: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist && form.ForcePushAllowlistActionsUser,
CanDelete: form.EnableDelete,
EnableDeleteAllowlist: form.EnableDelete && form.EnableDeleteAllowlist,
DeleteAllowlistTeamIDs: deleteTeams,
DeleteAllowlistUserIDs: deleteUsers,
DeleteAllowlistActionsUser: form.EnableDelete && form.EnableDeleteAllowlist && form.DeleteAllowlistActionsUser,
EnableMergeWhitelist: form.EnableMergeWhitelist,
MergeWhitelistTeamIDs: mergeTeams,
MergeWhitelistUserIDs: mergeUsers,
MergeWhitelistActionsUser: form.EnableMergeWhitelist && form.MergeWhitelistActionsUser,
EnableStatusCheck: form.EnableStatusCheck,
StatusCheckContexts: form.StatusCheckContexts,
RequiredApprovals: form.RequiredApprovals,
EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
ApprovalsWhitelistTeamIDs: approvalsTeams,
ApprovalsWhitelistUserIDs: approvalsUsers,
BlockOnRejectedReviews: form.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
@@ -320,6 +406,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.WhitelistTeamIDs = ids
}
if form.PushWhitelistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.PushWhitelistUsernames)
if !ok {
return
}
rule.WhitelistUserIDs = ids
}
if form.PushWhitelistActionsUser != nil {
rule.WhitelistActionsUser = *form.PushWhitelistActionsUser
}
if form.EnableForcePush != nil {
rule.CanForcePush = *form.EnableForcePush
}
@@ -333,6 +429,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.ForcePushAllowlistTeamIDs = ids
}
if form.ForcePushAllowlistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.ForcePushAllowlistUsernames)
if !ok {
return
}
rule.ForcePushAllowlistUserIDs = ids
}
if form.ForcePushAllowlistActionsUser != nil {
rule.ForcePushAllowlistActionsUser = *form.ForcePushAllowlistActionsUser
}
if form.EnableDelete != nil {
rule.CanDelete = *form.EnableDelete
}
@@ -346,6 +452,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.DeleteAllowlistTeamIDs = ids
}
if form.DeleteAllowlistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.DeleteAllowlistUsernames)
if !ok {
return
}
rule.DeleteAllowlistUserIDs = ids
}
if form.DeleteAllowlistActionsUser != nil {
rule.DeleteAllowlistActionsUser = *form.DeleteAllowlistActionsUser
}
if form.EnableMergeWhitelist != nil {
rule.EnableMergeWhitelist = *form.EnableMergeWhitelist
}
@@ -356,6 +472,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.MergeWhitelistTeamIDs = ids
}
if form.MergeWhitelistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.MergeWhitelistUsernames)
if !ok {
return
}
rule.MergeWhitelistUserIDs = ids
}
if form.MergeWhitelistActionsUser != nil {
rule.MergeWhitelistActionsUser = *form.MergeWhitelistActionsUser
}
if form.EnableStatusCheck != nil {
rule.EnableStatusCheck = *form.EnableStatusCheck
}
@@ -375,6 +501,13 @@ func EditOrgBranchProtection(ctx *context.APIContext) {
}
rule.ApprovalsWhitelistTeamIDs = ids
}
if form.ApprovalsWhitelistUsernames != nil {
ids, ok := resolveUserIDs(ctx, form.ApprovalsWhitelistUsernames)
if !ok {
return
}
rule.ApprovalsWhitelistUserIDs = ids
}
if form.BlockOnRejectedReviews != nil {
rule.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
}
+57
View File
@@ -24,6 +24,23 @@ func toAPIOrgEmailDomainPolicy(policy *git_model.OrgEmailDomainPolicy, orgID int
// GetOrgEmailDomainPolicy get the organization's email domain policy
func GetOrgEmailDomainPolicy(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/email_domain_policy organization orgGetEmailDomainPolicy
// ---
// summary: Get the organization's email domain policy
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/OrgEmailDomainPolicy"
// "404":
// "$ref": "#/responses/notFound"
orgID := ctx.Org.Organization.ID
policy, err := git_model.GetOrgEmailDomainPolicy(ctx, orgID)
if err != nil {
@@ -35,6 +52,31 @@ func GetOrgEmailDomainPolicy(ctx *context.APIContext) {
// EditOrgEmailDomainPolicy create or update the organization's email domain policy
func EditOrgEmailDomainPolicy(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/email_domain_policy organization orgEditEmailDomainPolicy
// ---
// summary: Create or update the organization's email domain policy. Only fields that are set will be changed
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditOrgEmailDomainPolicyOption"
// responses:
// "200":
// "$ref": "#/responses/OrgEmailDomainPolicy"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditOrgEmailDomainPolicyOption)
orgID := ctx.Org.Organization.ID
@@ -59,6 +101,21 @@ func EditOrgEmailDomainPolicy(ctx *context.APIContext) {
// DeleteOrgEmailDomainPolicy remove the organization's email domain policy
func DeleteOrgEmailDomainPolicy(ctx *context.APIContext) {
// swagger:operation DELETE /orgs/{org}/email_domain_policy organization orgDeleteEmailDomainPolicy
// ---
// summary: Remove the organization's email domain policy
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
if err := git_model.DeleteOrgEmailDomainPolicy(ctx, ctx.Org.Organization.ID); err != nil {
ctx.APIErrorInternal(err)
return
+57
View File
@@ -32,6 +32,23 @@ func toAPIOrgPushPolicy(policy *git_model.OrgPushPolicy, orgID int64) *api.OrgPu
// GetOrgPushPolicy get the organization's push policy
func GetOrgPushPolicy(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/push_policy organization orgGetPushPolicy
// ---
// summary: Get the organization's push policy
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/OrgPushPolicy"
// "404":
// "$ref": "#/responses/notFound"
orgID := ctx.Org.Organization.ID
policy, err := git_model.GetOrgPushPolicy(ctx, orgID)
if err != nil {
@@ -43,6 +60,31 @@ func GetOrgPushPolicy(ctx *context.APIContext) {
// EditOrgPushPolicy create or update the organization's push policy
func EditOrgPushPolicy(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/push_policy organization orgEditPushPolicy
// ---
// summary: Create or update the organization's push policy. Only fields that are set will be changed
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditOrgPushPolicyOption"
// responses:
// "200":
// "$ref": "#/responses/OrgPushPolicy"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditOrgPushPolicyOption)
orgID := ctx.Org.Organization.ID
@@ -80,6 +122,21 @@ func EditOrgPushPolicy(ctx *context.APIContext) {
// DeleteOrgPushPolicy remove the organization's push policy
func DeleteOrgPushPolicy(ctx *context.APIContext) {
// swagger:operation DELETE /orgs/{org}/push_policy organization orgDeletePushPolicy
// ---
// summary: Remove the organization's push policy
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
if err := git_model.DeleteOrgPushPolicy(ctx, ctx.Org.Organization.ID); err != nil {
ctx.APIErrorInternal(err)
return
+57
View File
@@ -42,6 +42,23 @@ func toAPIOrgRepoDefaults(d *git_model.OrgRepoDefaults, orgID int64) *api.OrgRep
// GetOrgRepoDefaults get the organization's default repository settings
func GetOrgRepoDefaults(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/repo_defaults organization orgGetRepoDefaults
// ---
// summary: Get the organization's default repository settings
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/OrgRepoDefaults"
// "404":
// "$ref": "#/responses/notFound"
orgID := ctx.Org.Organization.ID
defaults, err := git_model.GetOrgRepoDefaults(ctx, orgID)
if err != nil {
@@ -53,6 +70,31 @@ func GetOrgRepoDefaults(ctx *context.APIContext) {
// EditOrgRepoDefaults create or update the organization's default repository settings
func EditOrgRepoDefaults(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/repo_defaults organization orgEditRepoDefaults
// ---
// summary: Create or update the organization's default repository settings. Only fields that are set will be changed
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditOrgRepoDefaultsOption"
// responses:
// "200":
// "$ref": "#/responses/OrgRepoDefaults"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditOrgRepoDefaultsOption)
orgID := ctx.Org.Organization.ID
@@ -109,6 +151,21 @@ func EditOrgRepoDefaults(ctx *context.APIContext) {
// DeleteOrgRepoDefaults remove the organization's default repository settings
func DeleteOrgRepoDefaults(ctx *context.APIContext) {
// swagger:operation DELETE /orgs/{org}/repo_defaults organization orgDeleteRepoDefaults
// ---
// summary: Remove the organization's default repository settings
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
if err := git_model.DeleteOrgRepoDefaults(ctx, ctx.Org.Organization.ID); err != nil {
ctx.APIErrorInternal(err)
return
+119
View File
@@ -41,6 +41,23 @@ func toAPIOrgTagProtection(ctx *context.APIContext, rule *git_model.OrgProtected
// ListOrgTagProtections list org-level tag protection rules
func ListOrgTagProtections(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/tag_protections organization orgListTagProtections
// ---
// summary: List an organization's tag protection rules
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/OrgTagProtectionList"
// "404":
// "$ref": "#/responses/notFound"
rules, err := git_model.FindOrgProtectedTags(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.APIErrorInternal(err)
@@ -55,6 +72,29 @@ func ListOrgTagProtections(ctx *context.APIContext) {
// GetOrgTagProtection get a specific org-level tag protection rule
func GetOrgTagProtection(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/tag_protections/{id} organization orgGetTagProtection
// ---
// summary: Get a specific org-level tag protection rule
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: id
// in: path
// description: id of the tag protection rule
// type: integer
// format: int64
// required: true
// responses:
// "200":
// "$ref": "#/responses/OrgTagProtection"
// "404":
// "$ref": "#/responses/notFound"
rule, err := git_model.GetOrgProtectedTagByID(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("id"))
if err != nil {
ctx.APIErrorInternal(err)
@@ -69,6 +109,33 @@ func GetOrgTagProtection(ctx *context.APIContext) {
// CreateOrgTagProtection create an org-level tag protection rule
func CreateOrgTagProtection(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/tag_protections organization orgCreateTagProtection
// ---
// summary: Create an org-level tag protection rule
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreateOrgTagProtectionOption"
// responses:
// "201":
// "$ref": "#/responses/OrgTagProtection"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateOrgTagProtectionOption)
orgID := ctx.Org.Organization.ID
@@ -101,6 +168,37 @@ func CreateOrgTagProtection(ctx *context.APIContext) {
// EditOrgTagProtection edit an org-level tag protection rule
func EditOrgTagProtection(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/tag_protections/{id} organization orgEditTagProtection
// ---
// summary: Edit an org-level tag protection rule. Only fields that are set will be changed
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: id
// in: path
// description: id of the tag protection rule
// type: integer
// format: int64
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditOrgTagProtectionOption"
// responses:
// "200":
// "$ref": "#/responses/OrgTagProtection"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditOrgTagProtectionOption)
orgID := ctx.Org.Organization.ID
@@ -134,6 +232,27 @@ func EditOrgTagProtection(ctx *context.APIContext) {
// DeleteOrgTagProtection delete an org-level tag protection rule
func DeleteOrgTagProtection(ctx *context.APIContext) {
// swagger:operation DELETE /orgs/{org}/tag_protections/{id} organization orgDeleteTagProtection
// ---
// summary: Delete an org-level tag protection rule
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: id
// in: path
// description: id of the tag protection rule
// type: integer
// format: int64
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/notFound"
orgID := ctx.Org.Organization.ID
rule, err := git_model.GetOrgProtectedTagByID(ctx, orgID, ctx.PathParamInt64("id"))
if err != nil {
+43 -13
View File
@@ -14,7 +14,7 @@ import (
// apiMetadata is the JSON representation of a repo manifest.
type apiMetadata struct {
Name string `json:"name"`
Org string `json:"org"`
Org string `json:"org"` // read-only, derived from the org profile (repo owner)
Description string `json:"description"`
LicenseSPDX string `json:"license_spdx"`
LicenseName string `json:"license_name"`
@@ -30,7 +30,7 @@ type apiMetadata struct {
TargetVersion string `json:"target_version"`
PHPMinimum string `json:"php_minimum"`
Language string `json:"language"`
ExtensionType string `json:"extension_type"`
ExtensionType string `json:"extension_type"`
EntryPoint string `json:"entry_point"`
// deploy
@@ -44,6 +44,13 @@ type apiMetadata struct {
HealthURL string `json:"health_url,omitempty"`
}
// Manifest
// swagger:response Manifest
type swaggerResponseManifest struct {
// in:body
Body apiMetadata `json:"body"`
}
// GetRepoMetadata returns the manifest settings for a repository.
func GetRepoMetadata(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/manifest repository repoGetManifest
@@ -51,6 +58,17 @@ func GetRepoMetadata(ctx *context.APIContext) {
// summary: Get repo manifest settings
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Manifest"
@@ -65,15 +83,15 @@ func GetRepoMetadata(ctx *context.APIContext) {
// Return defaults from repo metadata.
ctx.JSON(http.StatusOK, &apiMetadata{
Name: ctx.Repo.Repository.Name,
Org: ctx.Repo.Repository.OwnerName,
Org: ctx.Repo.Repository.DerivedOrgName(ctx),
Description: ctx.Repo.Repository.Description,
})
return
}
ctx.JSON(http.StatusOK, &apiMetadata{
Name: m.Name,
Org: m.Org,
Description: m.Description,
Name: m.Name,
Org: ctx.Repo.Repository.DerivedOrgName(ctx),
Description: m.Description,
LicenseSPDX: m.LicenseSPDX,
LicenseName: m.LicenseName,
@@ -89,7 +107,7 @@ func GetRepoMetadata(ctx *context.APIContext) {
TargetVersion: m.TargetVersion,
PHPMinimum: m.PHPMinimum,
Language: m.Language,
ExtensionType: m.ExtensionType,
ExtensionType: m.ExtensionType,
EntryPoint: m.EntryPoint,
DeployHost: m.DeployHost,
DeployPort: m.DeployPort,
@@ -111,9 +129,21 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Manifest"
// Decode into a map to detect which fields were actually sent.
var raw map[string]any
if err := json.NewDecoder(ctx.Req.Body).Decode(&raw); err != nil {
@@ -127,7 +157,6 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
m = &repo_model.RepoMetadata{
RepoID: ctx.Repo.Repository.ID,
Name: ctx.Repo.Repository.Name,
Org: ctx.Repo.Repository.OwnerName,
Description: ctx.Repo.Repository.Description,
}
}
@@ -141,7 +170,8 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
}
}
setStr("name", &m.Name)
setStr("org", &m.Org)
// org is intentionally ignored: it is derived from the org profile
// (repository owner) on read, not stored. See issue #771.
setStr("description", &m.Description)
setStr("license_spdx", &m.LicenseSPDX)
setStr("license_name", &m.LicenseName)
@@ -173,9 +203,9 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
}
ctx.JSON(http.StatusOK, &apiMetadata{
Name: m.Name,
Org: m.Org,
Description: m.Description,
Name: m.Name,
Org: ctx.Repo.Repository.DerivedOrgName(ctx),
Description: m.Description,
LicenseSPDX: m.LicenseSPDX,
LicenseName: m.LicenseName,
@@ -191,7 +221,7 @@ func UpdateRepoMetadata(ctx *context.APIContext) {
TargetVersion: m.TargetVersion,
PHPMinimum: m.PHPMinimum,
Language: m.Language,
ExtensionType: m.ExtensionType,
ExtensionType: m.ExtensionType,
EntryPoint: m.EntryPoint,
DeployHost: m.DeployHost,
DeployPort: m.DeployPort,
+38
View File
@@ -159,6 +159,44 @@ type swaggerParameterBodies struct {
// in:body
UpdateBranchProtectionPriories api.UpdateBranchProtectionPriories
// in:body
CreateOrgBranchProtectionOption api.CreateOrgBranchProtectionOption
// in:body
EditOrgBranchProtectionOption api.EditOrgBranchProtectionOption
// in:body
CreateOrgTagProtectionOption api.CreateOrgTagProtectionOption
// in:body
EditOrgTagProtectionOption api.EditOrgTagProtectionOption
// in:body
EditOrgPushPolicyOption api.EditOrgPushPolicyOption
// in:body
EditOrgRepoDefaultsOption api.EditOrgRepoDefaultsOption
// in:body
EditOrgEmailDomainPolicyOption api.EditOrgEmailDomainPolicyOption
// in:body
EditAccessTokenOption api.EditAccessTokenOption
// in:body
IssueBulkAssigneesOption api.IssueBulkAssigneesOption
// in:body
IssueBulkLabelsOption api.IssueBulkLabelsOption
// in:body
IssueBulkMilestoneOption api.IssueBulkMilestoneOption
// in:body
IssueBulkStateOption api.IssueBulkStateOption
// in:body
IssuePriorityDef api.IssuePriorityDef
// in:body
IssueStatusDef api.IssueStatusDef
// in:body
IssueTypeDef api.IssueTypeDef
// in:body
CreateOAuth2ApplicationOptions api.CreateOAuth2ApplicationOptions
+49
View File
@@ -41,3 +41,52 @@ type swaggerResponseOrganizationPermissions struct {
// in:body
Body api.OrganizationPermissions `json:"body"`
}
// OrgBranchProtection
// swagger:response OrgBranchProtection
type swaggerResponseOrgBranchProtection struct {
// in:body
Body api.OrgBranchProtection `json:"body"`
}
// OrgBranchProtectionList
// swagger:response OrgBranchProtectionList
type swaggerResponseOrgBranchProtectionList struct {
// in:body
Body []*api.OrgBranchProtection `json:"body"`
}
// OrgTagProtection
// swagger:response OrgTagProtection
type swaggerResponseOrgTagProtection struct {
// in:body
Body api.OrgTagProtection `json:"body"`
}
// OrgTagProtectionList
// swagger:response OrgTagProtectionList
type swaggerResponseOrgTagProtectionList struct {
// in:body
Body []*api.OrgTagProtection `json:"body"`
}
// OrgPushPolicy
// swagger:response OrgPushPolicy
type swaggerResponseOrgPushPolicy struct {
// in:body
Body api.OrgPushPolicy `json:"body"`
}
// OrgRepoDefaults
// swagger:response OrgRepoDefaults
type swaggerResponseOrgRepoDefaults struct {
// in:body
Body api.OrgRepoDefaults `json:"body"`
}
// OrgEmailDomainPolicy
// swagger:response OrgEmailDomainPolicy
type swaggerResponseOrgEmailDomainPolicy struct {
// in:body
Body api.OrgEmailDomainPolicy `json:"body"`
}
+74 -18
View File
@@ -646,7 +646,7 @@ func (ctx *preReceiveContext) checkOrgPushPolicyBranch(oldCommitID, newCommitID,
}
if policy.MaxFileSize > 0 {
if path, size := ctx.largestBlobOverLimit(newCommitID, policy.MaxFileSize); path != "" {
if path, size := ctx.largestBlobOverLimit(oldCommitID, newCommitID, policy.MaxFileSize); path != "" {
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("Push rejected by the organization push policy: %q is %d bytes, over the %d-byte limit", path, size, policy.MaxFileSize),
})
@@ -673,34 +673,90 @@ func (ctx *preReceiveContext) checkOrgPushPolicyTag(tagName string) bool {
return true
}
// largestBlobOverLimit returns the first file (and its size) in the pushed tip tree
// that exceeds limit bytes, or ("", 0) if none — or on any error (fail open).
func (ctx *preReceiveContext) largestBlobOverLimit(commitID string, limit int64) (string, int64) {
output, _, err := gitrepo.RunCmdString(ctx,
ctx.Repo.Repository,
gitcmd.NewCommand("ls-tree", "-r", "--long").
AddDynamicArguments(commitID).
WithEnv(ctx.env),
)
if err != nil {
log.Error("org push policy ls-tree for %-v: %v", ctx.Repo.Repository, err)
// largestBlobOverLimit returns the first file (and its size) added or modified by the
// push (oldCommitID..newCommitID) whose blob exceeds limit bytes, or ("", 0) if none —
// or on any error (fail open). Only the pushed delta is inspected so a pre-existing
// oversized file committed before the policy existed cannot permanently block unrelated
// pushes. For a new branch (no old commit) the full new tree is scanned, since every
// blob is effectively introduced by the push.
func (ctx *preReceiveContext) largestBlobOverLimit(oldCommitID, newCommitID string, limit int64) (string, int64) {
emptyID := ctx.Repo.GetObjectFormat().EmptyObjectID().String()
// New branch: no base commit to diff against, so scan the full tip tree. ls-tree
// --long carries the blob size inline, so no extra sizing pass is needed.
if oldCommitID == "" || oldCommitID == emptyID {
output, _, err := gitrepo.RunCmdString(ctx, ctx.Repo.Repository,
gitcmd.NewCommand("ls-tree", "-r", "--long").AddDynamicArguments(newCommitID).WithEnv(ctx.env))
if err != nil {
log.Error("org push policy ls-tree for %-v: %v", ctx.Repo.Repository, err)
return "", 0
}
for _, line := range strings.Split(output, "\n") {
tab := strings.IndexByte(line, '\t')
if tab < 0 {
continue
}
fields := strings.Fields(line[:tab]) // mode, type, hash, size
if len(fields) < 4 || fields[1] != "blob" {
continue
}
if size, perr := strconv.ParseInt(fields[3], 10, 64); perr == nil && size > limit {
return line[tab+1:], size
}
}
return "", 0
}
// Existing branch: inspect only blobs added or modified by this push. diff-tree
// gives the changed paths and their new object ids but not sizes, so collect the
// candidate blob ids and size them in a single cat-file --batch-check pass.
output, _, err := gitrepo.RunCmdString(ctx, ctx.Repo.Repository,
gitcmd.NewCommand("diff-tree", "--no-commit-id", "-r").AddDynamicArguments(oldCommitID, newCommitID).WithEnv(ctx.env))
if err != nil {
log.Error("org push policy diff-tree for %-v: %v", ctx.Repo.Repository, err)
return "", 0
}
shaToPath := make(map[string]string)
var order []string
for _, line := range strings.Split(output, "\n") {
tab := strings.IndexByte(line, '\t')
if tab < 0 {
continue
}
fields := strings.Fields(line[:tab]) // mode, type, hash, size
if len(fields) < 4 || fields[1] != "blob" {
fields := strings.Fields(line[:tab]) // ":oldmode newmode oldsha newsha status"
if len(fields) < 5 {
continue
}
size, perr := strconv.ParseInt(fields[3], 10, 64)
if perr != nil {
newMode, newSha, status := fields[1], fields[3], fields[4]
// Only regular, executable, or symlink blobs; skip submodule gitlinks (160000).
if newMode != "100644" && newMode != "100755" && newMode != "120000" {
continue
}
if size > limit {
return line[tab+1:], size
if status == "D" || newSha == emptyID {
continue
}
if _, seen := shaToPath[newSha]; !seen {
order = append(order, newSha)
}
shaToPath[newSha] = line[tab+1:]
}
if len(order) == 0 {
return "", 0
}
output, _, err = gitrepo.RunCmdString(ctx, ctx.Repo.Repository,
gitcmd.NewCommand("cat-file", "--batch-check").WithStdinBytes([]byte(strings.Join(order, "\n")+"\n")).WithEnv(ctx.env))
if err != nil {
log.Error("org push policy cat-file for %-v: %v", ctx.Repo.Repository, err)
return "", 0
}
for _, line := range strings.Split(output, "\n") {
fields := strings.Fields(line) // "<sha> <type> <size>" or "<sha> missing"
if len(fields) != 3 || fields[1] != "blob" {
continue
}
if size, perr := strconv.ParseInt(fields[2], 10, 64); perr == nil && size > limit {
return shaToPath[fields[0]], size
}
}
return "", 0
+66 -24
View File
@@ -137,27 +137,49 @@ func BrandingUpload(ctx *context.Context) {
return
}
destPath := filepath.Join(imgDir, filename)
dest, err := os.Create(destPath)
if err != nil {
ctx.Flash.Error("Failed to save image")
log.Error("Create %s: %v", destPath, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
defer dest.Close()
if _, err := io.Copy(dest, file); err != nil {
ctx.Flash.Error("Failed to write image")
log.Error("Copy to %s: %v", destPath, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
// The nav icon doubles as the app icon (logo.png) — used as the PWA / web
// manifest icon and the navbar fallback — so branding the nav icon brands
// the app icon too. Write the uploaded image to both in a single pass. See #773.
targets := []string{filename}
if imageType == "nav-icon" {
targets = append(targets, "logo.png")
}
// Remove SVG override if present
svgPath := filepath.Join(imgDir, filename[:len(filename)-4]+".svg")
if fileExists(svgPath) {
os.Remove(svgPath)
// Write the uploaded image to each target independently (rewinding the
// stream between targets) so a failure on a later target never leaves an
// earlier one truncated/empty.
for i, name := range targets {
if i > 0 {
if _, err := file.Seek(0, io.SeekStart); err != nil {
ctx.Flash.Error("Failed to write image")
log.Error("Seek branding image %s: %v", name, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
}
destPath := filepath.Join(imgDir, name)
dest, err := os.Create(destPath)
if err != nil {
ctx.Flash.Error("Failed to save image")
log.Error("Create %s: %v", destPath, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
if _, err := io.Copy(dest, file); err != nil {
dest.Close()
ctx.Flash.Error("Failed to write image")
log.Error("Copy to %s: %v", destPath, err)
ctx.Redirect(setting.AppSubURL + "/-/admin/branding")
return
}
dest.Close()
// Remove any SVG override so the uploaded PNG takes effect.
svgPath := filepath.Join(imgDir, name[:len(name)-4]+".svg")
if fileExists(svgPath) {
os.Remove(svgPath)
}
}
ctx.Flash.Success("Branding image updated: " + imageType)
@@ -183,16 +205,36 @@ func BrandingReset(ctx *context.Context) {
return
}
path := filepath.Join(brandingImageDir(), filename)
if fileExists(path) {
// The nav icon also sets the app icon (logo.png), so resetting it reverts
// both back to the built-in default. See #773.
targets := []string{filename}
if imageType == "nav-icon" {
targets = append(targets, "logo.png")
}
removedAny := false
failed := false
for _, name := range targets {
path := filepath.Join(brandingImageDir(), name)
if !fileExists(path) {
continue
}
if err := os.Remove(path); err != nil {
ctx.Flash.Error("Failed to remove custom image")
log.Error("Remove %s: %v", path, err)
failed = true
} else {
ctx.Flash.Success("Reset to default: " + imageType)
log.Info("Branding reset to default: %s", filename)
removedAny = true
log.Info("Branding reset to default: %s", name)
}
} else {
}
switch {
case failed:
// error flash already set
case removedAny:
ctx.Flash.Success("Reset to default: " + imageType)
default:
ctx.Flash.Info("Already using default: " + imageType)
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package admin
import (
"net/http"
"strings"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
const tplMetadata templates.TplName = "admin/metadata"
// Metadata shows the admin metadata-settings page.
func Metadata(ctx *context.Context) {
ctx.Data["Title"] = "Metadata"
ctx.Data["PageIsAdminMetadata"] = true
ctx.Data["PlatformOptions"] = strings.Join(setting.Metadata.PlatformOptions, "\n")
ctx.HTML(http.StatusOK, tplMetadata)
}
// parsePlatformOptions normalizes admin input (comma- or newline-separated) into
// a clean, lowercased, de-duplicated, order-preserving list of platform values.
func parsePlatformOptions(raw string) []string {
seen := make(map[string]bool)
var out []string
for _, tok := range strings.FieldsFunc(raw, func(r rune) bool { return r == ',' || r == '\n' || r == '\r' }) {
v := strings.ToLower(strings.TrimSpace(tok))
if v == "" || seen[v] {
continue
}
seen[v] = true
out = append(out, v)
}
return out
}
// MetadataSettings persists the admin metadata settings.
func MetadataSettings(ctx *context.Context) {
opts := parsePlatformOptions(ctx.FormString("platform_options"))
if len(opts) == 0 {
ctx.Flash.Error("Platform options cannot be empty")
ctx.Redirect(setting.AppSubURL + "/-/admin/metadata")
return
}
// Apply in-memory so the change takes effect without a restart.
setting.Metadata.PlatformOptions = opts
// Persist to app.ini [metadata] PLATFORM_OPTIONS.
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
if err != nil {
ctx.Flash.Error("Failed to load config: " + err.Error())
ctx.Redirect(setting.AppSubURL + "/-/admin/metadata")
return
}
cfg.Section("metadata").Key("PLATFORM_OPTIONS").SetValue(strings.Join(opts, ","))
if err := cfg.SaveTo(setting.CustomConf); err != nil {
ctx.Flash.Error("Failed to save config: " + err.Error())
log.Error("SaveTo %s: %v", setting.CustomConf, err)
} else {
ctx.Flash.Success("Metadata settings saved")
log.Info("Metadata platform options updated: %v", opts)
}
ctx.Redirect(setting.AppSubURL + "/-/admin/metadata")
}
+1 -1
View File
@@ -185,7 +185,7 @@ func NewComment(ctx *context.Context) {
} // end if: handle close or reopen
// Handle custom status from the status dropdown (replaces close button for issues with org statuses).
if statusIDStr := ctx.Req.FormValue("status_id"); statusIDStr != "" && statusIDStr != "" {
if statusIDStr := ctx.Req.FormValue("status_id"); statusIDStr != "" {
if statusIDStr == "reopen" {
// Reopen via dropdown
if issue.IsClosed {
+27 -6
View File
@@ -10,6 +10,7 @@ import (
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
@@ -35,11 +36,32 @@ func Metadata(ctx *context.Context) {
manifest = &repo_model.RepoMetadata{
RepoID: repoID,
Name: ctx.Repo.Repository.Name,
Org: ctx.Repo.Repository.OwnerName,
Description: ctx.Repo.Repository.Description,
}
}
ctx.Data["Manifest"] = manifest
// Organization is derived from the org profile (repo owner), not stored.
// It is displayed read-only. See issue #771.
ctx.Data["DerivedOrg"] = ctx.Repo.Repository.DerivedOrgName(ctx)
// Platform dropdown options come from the admin-configurable setting
// (Admin -> Metadata). Keep the repo's current value selectable even if it
// was later removed from the list, so saving does not silently drop it.
// See issue #777.
platformOpts := append([]string(nil), setting.Metadata.PlatformOptions...)
if manifest.Platform != "" {
found := false
for _, p := range platformOpts {
if p == manifest.Platform {
found = true
break
}
}
if !found {
platformOpts = append([]string{manifest.Platform}, platformOpts...)
}
}
ctx.Data["PlatformOptions"] = platformOpts
// Load repo-scoped custom fields.
fields, _ := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo)
@@ -100,9 +122,9 @@ func saveMetadata(ctx *context.Context) {
existing, _ := repo_model.GetRepoMetadata(ctx, ctx.Repo.Repository.ID)
manifest := &repo_model.RepoMetadata{
RepoID: ctx.Repo.Repository.ID,
Name: ctx.FormString("name"),
Org: ctx.FormString("org"),
RepoID: ctx.Repo.Repository.ID,
Name: ctx.FormString("name"),
// org is not stored: it is derived from the org profile on read. See issue #771.
Description: ctx.Repo.Repository.Description,
LicenseSPDX: spdx,
LicenseName: licenseName,
@@ -111,7 +133,7 @@ func saveMetadata(ctx *context.Context) {
InfoURL: ctx.FormString("info_url"),
TargetVersion: ctx.FormString("target_version"),
PHPMinimum: ctx.FormString("php_minimum"),
ExtensionType: ctx.FormString("extension_type"),
ExtensionType: ctx.FormString("extension_type"),
EntryPoint: ctx.FormString("entry_point"),
}
@@ -158,4 +180,3 @@ func saveCustomFields(ctx *context.Context) {
ctx.Flash.Success("Custom fields saved")
ctx.Redirect(ctx.Repo.RepoLink + "/settings/metadata")
}
+6 -1
View File
@@ -786,6 +786,11 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Get("/reset", admin.BrandingReset)
})
m.Group("/metadata", func() {
m.Get("", admin.Metadata)
m.Post("/settings", admin.MetadataSettings)
})
m.Group("/monitor", func() {
m.Get("/stats", admin.MonitorStats)
m.Get("/cron", admin.CronTasks)
@@ -1239,7 +1244,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
}, repo_setting.SettingsCtxData)
m.Combo("/metadata").Get(repo_setting.Metadata).Post(repo_setting.MetadataPost)
m.Combo("/updateserver").Get(repo_setting.UpdateServerSettings).Post(repo_setting.UpdateServerSettingsPost)
m.Combo("/manifest").Get(repo_setting.LicensingSettings).Post(repo_setting.LicensingSettingsPost) // redirect
m.Combo("/manifest").Get(repo_setting.LicensingSettings).Post(repo_setting.LicensingSettingsPost) // redirect
m.Combo("/licensing").Get(repo_setting.LicensingSettings).Post(repo_setting.LicensingSettingsPost) // redirect
m.Group("/security", func() {
m.Combo("").Get(repo_setting.SecuritySettings).Post(repo_setting.SecuritySettingsPost)
+1 -1
View File
@@ -11,8 +11,8 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/container"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
-115
View File
@@ -1,115 +0,0 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repository
import (
"context"
"encoding/xml"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
)
// manifestXML mirrors the .mokogitea/manifest.xml schema for XML parsing.
type manifestXML struct {
XMLName xml.Name `xml:"mokoplatform"`
Identity manifestIdentity `xml:"identity"`
Governance manifestGovernance `xml:"governance"`
Distribution manifestDistribution `xml:"distribution"`
Build manifestBuild `xml:"build"`
}
type manifestDistribution struct {
DisplayName string `xml:"display-name"`
Maintainer string `xml:"maintainer"`
MaintainerURL string `xml:"maintainer-url"`
InfoURL string `xml:"info-url"`
TargetVersion string `xml:"target-version"`
PHPMinimum string `xml:"php-minimum"`
}
type manifestIdentity struct {
Name string `xml:"name"`
Org string `xml:"org"`
Description string `xml:"description"`
VersionPrefix string `xml:"version-prefix"`
ElementName string `xml:"element-name"`
License manifestLicense `xml:"license"`
}
type manifestLicense struct {
SPDX string `xml:"spdx,attr"`
Name string `xml:",chardata"`
}
type manifestGovernance struct {
Platform string `xml:"platform"`
StandardsVersion string `xml:"standards-version"`
StandardsSource string `xml:"standards-source"`
}
type manifestBuild struct {
Language string `xml:"language"`
ExtensionType string `xml:"package-type"`
EntryPoint string `xml:"entry-point"`
}
// SyncMetadataFromCommit reads .mokogitea/manifest.xml from the given commit
// and upserts the values into the repo_manifest database table.
// This is called on push to the default branch to keep the database in sync
// with the XML file. If no manifest.xml exists, this is a no-op.
func SyncMetadataFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit) {
if commit == nil {
return
}
entry, err := commit.GetTreeEntryByPath(".mokogitea/manifest.xml")
if err != nil || entry == nil {
return // no manifest.xml — not an error
}
reader, err := entry.Blob().DataAsync()
if err != nil {
log.Error("SyncMetadata: read blob for %s: %v", repo.FullName(), err)
return
}
defer reader.Close()
var mxml manifestXML
decoder := xml.NewDecoder(reader)
if err := decoder.Decode(&mxml); err != nil {
log.Error("SyncMetadata: parse XML for %s: %v", repo.FullName(), err)
return
}
manifest := &repo_model.RepoMetadata{
RepoID: repo.ID,
Name: mxml.Identity.Name,
Org: mxml.Identity.Org,
Description: mxml.Identity.Description,
VersionPrefix: mxml.Identity.VersionPrefix,
ElementName: mxml.Identity.ElementName,
LicenseSPDX: mxml.Identity.License.SPDX,
LicenseName: mxml.Identity.License.Name,
Platform: mxml.Governance.Platform,
StandardsVersion: mxml.Governance.StandardsVersion,
StandardsSource: mxml.Governance.StandardsSource,
Maintainer: mxml.Distribution.Maintainer,
MaintainerURL: mxml.Distribution.MaintainerURL,
InfoURL: mxml.Distribution.InfoURL,
TargetVersion: mxml.Distribution.TargetVersion,
PHPMinimum: mxml.Distribution.PHPMinimum,
Language: mxml.Build.Language,
ExtensionType: mxml.Build.ExtensionType,
EntryPoint: mxml.Build.EntryPoint,
}
if err := repo_model.CreateOrUpdateRepoMetadata(ctx, manifest); err != nil {
log.Error("SyncMetadata: save for %s: %v", repo.FullName(), err)
return
}
log.Info("SyncMetadata: synced .mokogitea/manifest.xml for %s", repo.FullName())
}
-2
View File
@@ -194,8 +194,6 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
if err := DelRepoDivergenceFromCache(ctx, repo.ID); err != nil {
log.Error("DelRepoDivergenceFromCache: %v", err)
}
// Auto-sync .mokogitea/manifest.xml to database on default branch push
SyncMetadataFromCommit(ctx, repo, newCommit)
// Run security scanners on default branch push
security_service.ScanOnPush(ctx, repo, newCommit)
} else {
+1 -1
View File
@@ -160,7 +160,7 @@ var DefaultCodeRules = []CodeRule{
{
ID: "deserialize-yaml-py", Title: "Insecure Deserialization: yaml.load() (Python)",
Severity: security_model.SeverityHigh, CWE: "CWE-502",
Pattern: regexp.MustCompile(`yaml\.load\s*\([^)]*(?:Loader\s*=\s*yaml\.(?:Unsafe|Full)Loader|[^)]*\)(?!\s*#))`),
Pattern: regexp.MustCompile(`(?i)yaml\.load\s*\(`),
Description: "yaml.load() without SafeLoader allows arbitrary code execution — use yaml.safe_load()",
Languages: []string{".py"},
},
+23
View File
@@ -0,0 +1,23 @@
// Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package security
import "testing"
// TestDefaultCodeRulesCompile forces the package init, which builds every scanner
// rule's regexp via regexp.MustCompile, and asserts each pattern is present. Go's
// regexp engine is RE2 and rejects Perl-only constructs (lookahead/lookbehind/
// backreferences) by panicking in MustCompile at init — which crash-loops the
// server at startup. This test executes that init so such a pattern fails CI here
// instead of on a live deploy.
func TestDefaultCodeRulesCompile(t *testing.T) {
if len(DefaultCodeRules) == 0 {
t.Fatal("DefaultCodeRules is empty")
}
for _, r := range DefaultCodeRules {
if r.Pattern == nil {
t.Errorf("rule %q has a nil Pattern", r.ID)
}
}
}
+2 -5
View File
@@ -269,11 +269,8 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
phpMinimum := meta.PHPMinimum
feedDescription := meta.Description
// Maintainer and URL always come from the org profile.
maintainer := repo.Owner.FullName
if maintainer == "" {
maintainer = repo.Owner.Name
}
// Maintainer/organization and URL always come from the org profile.
maintainer := repo.DerivedOrgName(ctx)
maintainerURL := repo.Owner.Website
if maintainerURL == "" {
maintainerURL = fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
+1 -1
View File
@@ -54,7 +54,7 @@
<tr>
<td>
<strong>Nav Icon</strong> {{if .HasNavIcon}}<span class="ui mini green label">Custom</span>{{else}}<span class="ui mini grey label">Default</span>{{end}}
<div class="tw-text-text-light tw-text-sm tw-mt-1">Top-left corner across all pages. Square, 30x30px.</div>
<div class="tw-text-text-light tw-text-sm tw-mt-1">Top-left corner across all pages. Square, 30x30px. Also sets the app icon (PWA / web manifest); use at least 512x512px for a crisp installed icon.</div>
</td>
<td>
<form method="post" action="{{AppSubUrl}}/-/admin/branding/upload" enctype="multipart/form-data">
+19
View File
@@ -0,0 +1,19 @@
{{template "admin/layout_head" (dict "pageClass" "admin metadata")}}
<div class="admin-setting-content">
<h4 class="ui top attached header">
{{svg "octicon-list-unordered" 16}} Metadata
</h4>
<div class="ui attached segment">
<h5>Platform Options</h5>
<p class="tw-text-text-light tw-text-sm">Allowed values for the repository metadata <strong>Platform</strong> field. One per line (or comma-separated). Changes take effect immediately and are saved to <code>app.ini</code> <code>[metadata] PLATFORM_OPTIONS</code>.</p>
<form method="post" action="{{AppSubUrl}}/-/admin/metadata/settings">
{{.CsrfTokenHtml}}
<div class="field">
<textarea name="platform_options" rows="6" class="tw-w-full tw-font-mono" placeholder="joomla&#10;dolibarr&#10;go&#10;npm&#10;generic">{{.PlatformOptions}}</textarea>
</div>
<button type="submit" class="ui primary small button tw-mt-2">{{svg "octicon-check" 14}} Save Settings</button>
</form>
<p class="tw-text-text-light tw-text-sm tw-mt-3">Existing repositories keep their stored platform value even if it is later removed from this list; it simply won't be offered to other repos.</p>
</div>
</div>
{{template "admin/layout_footer" .}}
+3
View File
@@ -87,6 +87,9 @@
<a class="{{if .PageIsAdminBranding}}active {{end}}item" href="{{AppSubUrl}}/-/admin/branding">
{{svg "octicon-paintbrush" 16}} Branding
</a>
<a class="{{if .PageIsAdminMetadata}}active {{end}}item" href="{{AppSubUrl}}/-/admin/metadata">
{{svg "octicon-list-unordered" 16}} Metadata
</a>
<a class="{{if .PageIsAdminLicenseTiers}}active {{end}}item" href="{{AppSubUrl}}/-/admin/license-tiers">
{{svg "octicon-key" 16}} License Tiers
</a>
+3 -2
View File
@@ -18,7 +18,8 @@
</div>
<div class="field">
<label>Organization</label>
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
<input value="{{.DerivedOrg}}" placeholder="Organization" readonly>
<p class="help">Derived from the organization profile (repository owner).</p>
</div>
</div>
<div class="three fields">
@@ -54,7 +55,7 @@
<select name="platform" class="ui dropdown">
<option value="">—</option>
{{$platform := .Manifest.Platform}}
{{range $val := StringUtils.Split "joomla,wordpress,dolibarr,go,mcp,platform,generic" ","}}
{{range $val := .PlatformOptions}}
<option value="{{$val}}" {{if eq $val $platform}}selected{{end}}>{{$val}}</option>
{{end}}
</select>
@@ -172,7 +172,7 @@
</div>
</div>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_delete"}}</h5>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.protect_branch_deletion"}}</h5>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="enable_delete" value="none" class="toggle-target-disabled" data-target="#delete_allowlist_box" {{if not .Rule.CanDelete}}checked{{end}}>
+2709 -2
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+11 -10
View File
@@ -6,6 +6,7 @@ package integration
import (
"fmt"
"net/http"
"strings"
"testing"
auth_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
@@ -36,7 +37,7 @@ func TestAPILicensePackages(t *testing.T) {
t.Run("CreatePackage", func(t *testing.T) {
body := `{"name":"Test Pro Annual","description":"Annual pro subscription","duration_days":365,"max_sites":5}`
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusCreated)
@@ -51,7 +52,7 @@ func TestAPILicensePackages(t *testing.T) {
t.Run("CreatePackageNoName", func(t *testing.T) {
body := `{"description":"Missing name"}`
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
MakeRequest(t, req, http.StatusUnprocessableEntity)
@@ -68,7 +69,7 @@ func TestAPILicenseKeys(t *testing.T) {
// Create a package first.
body := `{"name":"Test Package","duration_days":30}`
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusCreated)
@@ -80,7 +81,7 @@ func TestAPILicenseKeys(t *testing.T) {
t.Run("CreateKey", func(t *testing.T) {
body := fmt.Sprintf(`{"package_id":%d,"licensee_name":"John Doe","licensee_email":"john@example.com"}`, pkg.ID)
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys", strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusCreated)
@@ -104,7 +105,7 @@ func TestAPILicenseKeys(t *testing.T) {
t.Run("EditKey", func(t *testing.T) {
body := `{"licensee_name":"Jane Doe","domain_restriction":"example.com,test.com"}`
req := NewRequestWithBody(t, "PATCH", fmt.Sprintf("%s/license-keys/%d", urlPrefix, createdKeyID), []byte(body)).
req := NewRequestWithBody(t, "PATCH", fmt.Sprintf("%s/license-keys/%d", urlPrefix, createdKeyID), strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusOK)
@@ -124,7 +125,7 @@ func TestAPILicenseKeys(t *testing.T) {
t.Run("ValidateKey", func(t *testing.T) {
body := fmt.Sprintf(`{"key":"%s","domain":"example.com"}`, rawKey)
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/validate", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/validate", strings.NewReader(body)).
SetHeader("Content-Type", "application/json")
// Note: no token — this is a public endpoint.
resp := MakeRequest(t, req, http.StatusOK)
@@ -136,7 +137,7 @@ func TestAPILicenseKeys(t *testing.T) {
t.Run("ValidateInvalidKey", func(t *testing.T) {
body := `{"key":"MOKO-XXXX-XXXX-XXXX-XXXX","domain":"example.com"}`
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/validate", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/validate", strings.NewReader(body)).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusOK)
var result api.ValidateLicenseKeyResponse
@@ -161,7 +162,7 @@ func TestAPILicensePurchaseWebhook(t *testing.T) {
// Create a package.
body := `{"name":"Purchase Test","duration_days":90}`
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-packages", strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusCreated)
@@ -170,7 +171,7 @@ func TestAPILicensePurchaseWebhook(t *testing.T) {
t.Run("PurchaseNewKey", func(t *testing.T) {
body := fmt.Sprintf(`{"package_id":%d,"licensee_name":"Buyer","licensee_email":"buyer@shop.com","domain":"shop.com","payment_ref":"stripe_pi_test123"}`, pkg.ID)
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/purchase", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/purchase", strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusCreated)
@@ -183,7 +184,7 @@ func TestAPILicensePurchaseWebhook(t *testing.T) {
t.Run("PurchaseIdempotent", func(t *testing.T) {
// Same payment_ref should return existing key without raw_key.
body := fmt.Sprintf(`{"package_id":%d,"licensee_name":"Buyer","payment_ref":"stripe_pi_test123"}`, pkg.ID)
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/purchase", []byte(body)).
req := NewRequestWithBody(t, "POST", urlPrefix+"/license-keys/purchase", strings.NewReader(body)).
AddTokenAuth(token).
SetHeader("Content-Type", "application/json")
resp := MakeRequest(t, req, http.StatusOK)
@@ -255,7 +255,7 @@ func TestPackageComposer(t *testing.T) {
AddBasicAuth(user.Name)
resp = MakeRequest(t, req, http.StatusOK)
result = composer.PackageMetadataResponse{}
result = &composer.PackageMetadataResponse{}
DecodeJSON(t, resp, &result)
pkgs = result.Packages[packageName]
assert.Len(t, pkgs, 1)
@@ -268,7 +268,7 @@ func TestPackageComposer(t *testing.T) {
AddBasicAuth(otherUser.Name)
resp = MakeRequest(t, req, http.StatusOK)
result = composer.PackageMetadataResponse{}
result = &composer.PackageMetadataResponse{}
DecodeJSON(t, resp, &result)
pkgs = result.Packages[packageName]
assert.Len(t, pkgs, 1)
+2 -2
View File
@@ -253,7 +253,7 @@ func TestOAuth2CallbackReactivationGating(t *testing.T) {
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)()
srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: "test-sub", Email: "test@example.com", Name: "Test User"})
srv := newFakeOIDCServerWithConfig(t, FakeOIDCConfig{Sub: "test-sub", Email: "test@example.com", Name: "Test User"})
addOAuth2Source(t, "test-oauth-source", oauth2.Source{
Provider: "openidConnect",
ClientID: "test-client-id",
@@ -308,7 +308,7 @@ type FakeOIDCConfig struct {
}
// newFakeOIDCServer starts a httptest.Server that implements the minimum OIDC endpoints needed to complete a sign-in flow
func newFakeOIDCServer(t *testing.T, cfg FakeOIDCConfig) *httptest.Server {
func newFakeOIDCServerWithConfig(t *testing.T, cfg FakeOIDCConfig) *httptest.Server {
t.Helper()
var srv *httptest.Server
+10 -10
View File
@@ -7,16 +7,16 @@ import (
"net/http"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests"
auth_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unittest"
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/test"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/web/auth"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/oauth2"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/tests"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
+1 -1
View File
@@ -25,7 +25,7 @@ Custom CSS themes at `/var/lib/gitea/custom/public/assets/css/`:
---
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
+1 -1
View File
@@ -126,7 +126,7 @@ When a field definition is deleted, all associated values in `custom_field_value
| System | Relationship |
|--------|-------------|
| Update Server | Repo-scoped custom fields with specific names (Extension Name, Display Name, etc.) are read by the update feed generators as the highest-priority metadata source. |
| Manifest Settings | Manifest fields follow the moko-platform schema and are separate from custom fields. Custom fields are user-defined; manifest fields are standardized. |
| Manifest Settings | Manifest fields follow the mokocli schema and are separate from custom fields. Custom fields are user-defined; manifest fields are standardized. |
| Issue Statuses | Custom statuses are a separate feature with their own dedicated table and UI, not implemented as custom fields. |
---
+1 -1
View File
@@ -41,7 +41,7 @@ Located at `/var/lib/gitea/custom/`:
---
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
+2 -2
View File
@@ -309,8 +309,8 @@ Packages can be **archived** instead of permanently deleted. This is the recomme
In **Organization Settings → Licensing & Update Streams**, under **Extension Metadata**:
1. Set the **Platform** (Joomla, Dolibarr, WordPress, etc.)
2. Set the **Extension Name** (e.g., `pkg_mokowaas`) — this becomes the `<element>` in the XML feed
3. Set the **Display Name** (e.g., "Package - MokoWaaS") — shown in Joomla update manager
2. Set the **Extension Name** (e.g., `pkg_mokosuite`) — this becomes the `<element>` in the XML feed
3. Set the **Display Name** (e.g., "Package - MokoSuite") — shown in Joomla update manager
4. Set the **Extension Type** (component, module, plugin, package, template, library)
5. Set the **Target Version** regex (e.g., `(5|6)\..*` for Joomla 5 and 6)
6. Set the **PHP Minimum** if applicable (e.g., `8.1`)
+5 -5
View File
@@ -7,7 +7,7 @@ The manifest settings feature provides a centralized way to store and manage pro
Each repository can have a manifest that describes:
- **Identity** — project name, organization, description, version, and license
- **Governance** — platform type, moko-platform standards version, and standards source URL
- **Governance** — platform type, mokocli standards version, and standards source URL
- **Build** — language, package type, and entry point
These settings replace the legacy `.mokogitea/manifest.xml` file-based approach.
@@ -35,7 +35,7 @@ If a field already has a value in the database (e.g., from org-level custom fiel
## REST API
The manifest API allows Actions workflows and the moko-platform CLI to read and write manifest settings programmatically.
The manifest API allows Actions workflows and the mokocli CLI to read and write manifest settings programmatically.
### Get Manifest
@@ -57,7 +57,7 @@ Returns the current manifest settings. If no manifest has been saved, returns de
"license_name": "GNU General Public License v3",
"platform": "go",
"standards_version": "05.00.00",
"standards_source": "https://code.mokoconsulting.tech/MokoConsulting/moko-platform",
"standards_source": "https://code.mokoconsulting.tech/MokoConsulting/mokocli",
"language": "Go",
"package_type": "application",
"entry_point": "./"
@@ -101,8 +101,8 @@ Manifest settings are stored in the `repo_manifest` table (migration v347). One
| System | Relationship |
|--------|-------------|
| Update Server | The update server generators read from both manifest settings and update_stream_config. Manifest provides identity metadata; update_stream_config provides feed-specific settings. |
| Custom Fields | Repo-scoped custom fields (org settings) are separate from manifest fields. Custom fields are user-defined; manifest fields follow the moko-platform schema. |
| moko-platform CLI | The CLI reads manifest settings via the API for version bumping, build decisions, and cross-repo syncing (see issue #505). |
| Custom Fields | Repo-scoped custom fields (org settings) are separate from manifest fields. Custom fields are user-defined; manifest fields follow the mokocli schema. |
| mokocli CLI | The CLI reads manifest settings via the API for version bumping, build decisions, and cross-repo syncing (see issue #505). |
---
+1 -1
View File
@@ -195,7 +195,7 @@ curl -X POST -H "Authorization: token TOKEN" \
---
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
*Repo: [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea) · [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
| Revision | Date | Author | Description |
|---|---|---|---|
+2 -2
View File
@@ -23,9 +23,9 @@
## In Progress
- Rename moko-platform to MokoPlatform
- Rename mokocli to MokoCLI
- Granular role-based permissions for all features (#9)
- Wire moko-platform CLI to manifest API (#505)
- Wire mokocli CLI to manifest API (#505)
- Bulk migrate remaining 41 flat wikis to folders
## Planned